blob: f17e19a2dfcff9c5a260e5ae57247fd20888ed11 [file] [log] [blame]
// rust-gcc.cc -- Rust frontend to gcc IR.
// Copyright (C) 2011-2024 Free Software Foundation, Inc.
// Contributed by Ian Lance Taylor, Google.
// forked from gccgo
// This file is part of GCC.
// 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 "rust-system.h"
// This has to be included outside of extern "C", so we have to
// include it here before tree.h includes it later.
#include <gmp.h>
#include "tree.h"
#include "opts.h"
#include "fold-const.h"
#include "stringpool.h"
#include "stor-layout.h"
#include "varasm.h"
#include "tree-iterator.h"
#include "tm.h"
#include "function.h"
#include "cgraph.h"
#include "convert.h"
#include "gimple-expr.h"
#include "gimplify.h"
#include "langhooks.h"
#include "toplev.h"
#include "output.h"
#include "realmpfr.h"
#include "builtins.h"
#include "print-tree.h"
#include "attribs.h"
#include "rust-location.h"
#include "rust-linemap.h"
#include "rust-backend.h"
#include "rust-object-export.h"
#include "rust-gcc.h"
#include "backend/rust-tree.h"
#include "backend/rust-builtins.h"
// Get the tree of a variable for use as an expression. If this is a
// zero-sized global, create an expression that refers to the decl but
// has zero size.
tree
Bvariable::get_tree (location_t location) const
{
if (this->t_ == error_mark_node)
return error_mark_node;
TREE_USED (this->t_) = 1;
if (this->orig_type_ == NULL || TREE_TYPE (this->t_) == this->orig_type_)
{
return this->t_;
}
// Return *(orig_type*)&decl. */
tree t = build_fold_addr_expr_loc (location, this->t_);
t = fold_build1_loc (location, NOP_EXPR,
build_pointer_type (this->orig_type_), t);
return build_fold_indirect_ref_loc (location, t);
}
Bvariable *
Bvariable::error_variable ()
{
return new Bvariable (error_mark_node);
}
// This file implements the interface between the Rust frontend proper
// and the gcc IR. This implements specific instantiations of
// abstract classes defined by the Rust frontend proper. The Rust
// frontend proper class methods of these classes to generate the
// backend representation.
// A helper function to create a GCC identifier from a C++ string.
static inline tree
get_identifier_from_string (const std::string &str)
{
return get_identifier_with_length (str.data (), str.length ());
}
namespace Backend {
// Define the built-in functions that are exposed to GCCRust.
void
init ()
{
/* We need to define the fetch_and_add functions, since we use them
for ++ and --. */
// tree t = this->integer_type (true, BITS_PER_UNIT)->get_tree ();
// tree p = build_pointer_type (build_qualified_type (t, TYPE_QUAL_VOLATILE));
// this->define_builtin (BUILT_IN_SYNC_ADD_AND_FETCH_1,
// "__sync_fetch_and_add_1",
// NULL, build_function_type_list (t, p, t, NULL_TREE), 0);
// t = this->integer_type (true, BITS_PER_UNIT * 2)->get_tree ();
// p = build_pointer_type (build_qualified_type (t, TYPE_QUAL_VOLATILE));
// this->define_builtin (BUILT_IN_SYNC_ADD_AND_FETCH_2,
// "__sync_fetch_and_add_2",
// NULL, build_function_type_list (t, p, t, NULL_TREE), 0);
// t = this->integer_type (true, BITS_PER_UNIT * 4)->get_tree ();
// p = build_pointer_type (build_qualified_type (t, TYPE_QUAL_VOLATILE));
// this->define_builtin (BUILT_IN_SYNC_ADD_AND_FETCH_4,
// "__sync_fetch_and_add_4",
// NULL, build_function_type_list (t, p, t, NULL_TREE), 0);
// t = this->integer_type (true, BITS_PER_UNIT * 8)->get_tree ();
// p = build_pointer_type (build_qualified_type (t, TYPE_QUAL_VOLATILE));
// this->define_builtin (BUILT_IN_SYNC_ADD_AND_FETCH_8,
// "__sync_fetch_and_add_8",
// NULL, build_function_type_list (t, p, t, NULL_TREE), 0);
// // We use __builtin_expect for magic import functions.
// this->define_builtin (BUILT_IN_EXPECT, "__builtin_expect", NULL,
// build_function_type_list (long_integer_type_node,
// long_integer_type_node,
// long_integer_type_node,
// NULL_TREE),
// builtin_const);
// // We use __builtin_memcmp for struct comparisons.
// this->define_builtin (BUILT_IN_MEMCMP, "__builtin_memcmp", "memcmp",
// build_function_type_list (integer_type_node,
// const_ptr_type_node,
// const_ptr_type_node,
// size_type_node, NULL_TREE),
// 0);
// // We use __builtin_memmove for copying data.
// this->define_builtin (BUILT_IN_MEMMOVE, "__builtin_memmove", "memmove",
// build_function_type_list (void_type_node, ptr_type_node,
// const_ptr_type_node,
// size_type_node, NULL_TREE),
// 0);
// // We use __builtin_memset for zeroing data.
// this->define_builtin (BUILT_IN_MEMSET, "__builtin_memset", "memset",
// build_function_type_list (void_type_node, ptr_type_node,
// integer_type_node,
// size_type_node, NULL_TREE),
// 0);
// // Used by runtime/internal/sys and math/bits.
// this->define_builtin (BUILT_IN_CTZ, "__builtin_ctz", "ctz",
// build_function_type_list (integer_type_node,
// unsigned_type_node,
// NULL_TREE),
// builtin_const);
// this->define_builtin (BUILT_IN_CTZLL, "__builtin_ctzll", "ctzll",
// build_function_type_list (integer_type_node,
// long_long_unsigned_type_node,
// NULL_TREE),
// builtin_const);
// this->define_builtin (BUILT_IN_CLZ, "__builtin_clz", "clz",
// build_function_type_list (integer_type_node,
// unsigned_type_node,
// NULL_TREE),
// builtin_const);
// this->define_builtin (BUILT_IN_CLZLL, "__builtin_clzll", "clzll",
// build_function_type_list (integer_type_node,
// long_long_unsigned_type_node,
// NULL_TREE),
// builtin_const);
// this->define_builtin (BUILT_IN_POPCOUNT, "__builtin_popcount", "popcount",
// build_function_type_list (integer_type_node,
// unsigned_type_node,
// NULL_TREE),
// builtin_const);
// this->define_builtin (BUILT_IN_POPCOUNTLL, "__builtin_popcountll",
// "popcountll",
// build_function_type_list (integer_type_node,
// long_long_unsigned_type_node,
// NULL_TREE),
// builtin_const);
// this->define_builtin (BUILT_IN_BSWAP16, "__builtin_bswap16", "bswap16",
// build_function_type_list (uint16_type_node,
// uint16_type_node, NULL_TREE),
// builtin_const);
// this->define_builtin (BUILT_IN_BSWAP32, "__builtin_bswap32", "bswap32",
// build_function_type_list (uint32_type_node,
// uint32_type_node, NULL_TREE),
// builtin_const);
// this->define_builtin (BUILT_IN_BSWAP64, "__builtin_bswap64", "bswap64",
// build_function_type_list (uint64_type_node,
// uint64_type_node, NULL_TREE),
// builtin_const);
// We provide some functions for the math library.
// We use __builtin_return_address in the thunk we build for
// functions which call recover, and for runtime.getcallerpc.
// t = build_function_type_list (ptr_type_node, unsigned_type_node,
// NULL_TREE); this->define_builtin (BUILT_IN_RETURN_ADDRESS,
// "__builtin_return_address",
// NULL, t, 0);
// The runtime calls __builtin_dwarf_cfa for runtime.getcallersp.
// t = build_function_type_list (ptr_type_node, NULL_TREE);
// this->define_builtin (BUILT_IN_DWARF_CFA, "__builtin_dwarf_cfa", NULL, t,
// 0);
// The runtime calls __builtin_extract_return_addr when recording
// the address to which a function returns.
// this->define_builtin (
// BUILT_IN_EXTRACT_RETURN_ADDR, "__builtin_extract_return_addr", NULL,
// build_function_type_list (ptr_type_node, ptr_type_node, NULL_TREE), 0);
// The compiler uses __builtin_trap for some exception handling
// cases.
// this->define_builtin (BUILT_IN_TRAP, "__builtin_trap", NULL,
// build_function_type (void_type_node, void_list_node),
// builtin_noreturn);
// The runtime uses __builtin_prefetch.
// this->define_builtin (BUILT_IN_PREFETCH, "__builtin_prefetch", NULL,
// build_varargs_function_type_list (void_type_node,
// const_ptr_type_node,
// NULL_TREE),
// builtin_novops);
// The compiler uses __builtin_unreachable for cases that cannot
// occur.
// this->define_builtin (BUILT_IN_UNREACHABLE, "__builtin_unreachable", NULL,
// build_function_type (void_type_node, void_list_node),
// builtin_const | builtin_noreturn);
// We provide some atomic functions.
// t = build_function_type_list (uint32_type_node, ptr_type_node,
// integer_type_node, NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_LOAD_4, "__atomic_load_4", NULL, t,
// 0);
// t = build_function_type_list (uint64_type_node, ptr_type_node,
// integer_type_node, NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_LOAD_8, "__atomic_load_8", NULL, t,
// 0);
// t = build_function_type_list (void_type_node, ptr_type_node,
// uint32_type_node,
// integer_type_node, NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_STORE_4, "__atomic_store_4", NULL, t,
// 0);
// t = build_function_type_list (void_type_node, ptr_type_node,
// uint64_type_node,
// integer_type_node, NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_STORE_8, "__atomic_store_8", NULL, t,
// 0);
// t = build_function_type_list (uint32_type_node, ptr_type_node,
// uint32_type_node, integer_type_node, NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_EXCHANGE_4, "__atomic_exchange_4",
// NULL,
// t, 0);
// t = build_function_type_list (uint64_type_node, ptr_type_node,
// uint64_type_node, integer_type_node, NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_EXCHANGE_8, "__atomic_exchange_8",
// NULL,
// t, 0);
// t = build_function_type_list (boolean_type_node, ptr_type_node,
// ptr_type_node,
// uint32_type_node, boolean_type_node,
// integer_type_node, integer_type_node,
// NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_COMPARE_EXCHANGE_4,
// "__atomic_compare_exchange_4", NULL, t, 0);
// t = build_function_type_list (boolean_type_node, ptr_type_node,
// ptr_type_node,
// uint64_type_node, boolean_type_node,
// integer_type_node, integer_type_node,
// NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_COMPARE_EXCHANGE_8,
// "__atomic_compare_exchange_8", NULL, t, 0);
// t = build_function_type_list (uint32_type_node, ptr_type_node,
// uint32_type_node, integer_type_node, NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_ADD_FETCH_4, "__atomic_add_fetch_4",
// NULL, t, 0);
// t = build_function_type_list (uint64_type_node, ptr_type_node,
// uint64_type_node, integer_type_node, NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_ADD_FETCH_8, "__atomic_add_fetch_8",
// NULL, t, 0);
// t = build_function_type_list (unsigned_char_type_node, ptr_type_node,
// unsigned_char_type_node, integer_type_node,
// NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_AND_FETCH_1, "__atomic_and_fetch_1",
// NULL, t, 0);
// this->define_builtin (BUILT_IN_ATOMIC_FETCH_AND_1, "__atomic_fetch_and_1",
// NULL, t, 0);
// t = build_function_type_list (unsigned_char_type_node, ptr_type_node,
// unsigned_char_type_node, integer_type_node,
// NULL_TREE);
// this->define_builtin (BUILT_IN_ATOMIC_OR_FETCH_1, "__atomic_or_fetch_1",
// NULL,
// t, 0);
// this->define_builtin (BUILT_IN_ATOMIC_FETCH_OR_1, "__atomic_fetch_or_1",
// NULL,
// t, 0);
}
void
debug (tree t)
{
debug_tree (t);
};
void
debug (Bvariable *t)
{
debug_tree (t->get_decl ());
};
tree
get_identifier_node (const std::string &str)
{
return get_identifier_with_length (str.data (), str.length ());
}
tree
wchar_type ()
{
static tree wchar;
if (wchar == NULL_TREE)
{
wchar = make_unsigned_type (32);
TYPE_STRING_FLAG (wchar) = 1;
}
return wchar;
}
// Get an unnamed integer type.
int
get_pointer_size ()
{
return POINTER_SIZE;
}
tree
raw_str_type ()
{
tree char_ptr = build_pointer_type (char_type_node);
tree const_char_type = build_qualified_type (char_ptr, TYPE_QUAL_CONST);
return const_char_type;
}
tree
integer_type (bool is_unsigned, int bits)
{
tree type;
if (is_unsigned)
{
if (bits == INT_TYPE_SIZE)
type = unsigned_type_node;
else if (bits == SHORT_TYPE_SIZE)
type = short_unsigned_type_node;
else if (bits == LONG_TYPE_SIZE)
type = long_unsigned_type_node;
else if (bits == LONG_LONG_TYPE_SIZE)
type = long_long_unsigned_type_node;
else
type = make_unsigned_type (bits);
}
else
{
if (bits == INT_TYPE_SIZE)
type = integer_type_node;
else if (bits == SHORT_TYPE_SIZE)
type = short_integer_type_node;
else if (bits == LONG_TYPE_SIZE)
type = long_integer_type_node;
else if (bits == LONG_LONG_TYPE_SIZE)
type = long_long_integer_type_node;
else
type = make_signed_type (bits);
}
return type;
}
// Get an unnamed float type.
tree
float_type (int bits)
{
tree type;
if (bits == FLOAT_TYPE_SIZE)
type = float_type_node;
else if (bits == DOUBLE_TYPE_SIZE)
type = double_type_node;
else if (bits == LONG_DOUBLE_TYPE_SIZE)
type = long_double_type_node;
else
{
type = make_node (REAL_TYPE);
TYPE_PRECISION (type) = bits;
layout_type (type);
}
return type;
}
// Get a pointer type.
tree
pointer_type (tree to_type)
{
if (to_type == error_mark_node)
return error_mark_node;
tree type = build_pointer_type (to_type);
return type;
}
// Get a reference type.
tree
reference_type (tree to_type)
{
if (to_type == error_mark_node)
return error_mark_node;
tree type = build_reference_type (to_type);
return type;
}
// Get immutable type
tree
immutable_type (tree base)
{
if (base == error_mark_node)
return error_mark_node;
tree constified = build_qualified_type (base, TYPE_QUAL_CONST);
return constified;
}
// Make a function type.
tree
function_type (const typed_identifier &receiver,
const std::vector<typed_identifier> &parameters,
const std::vector<typed_identifier> &results, tree result_struct,
location_t)
{
tree args = NULL_TREE;
tree *pp = &args;
if (receiver.type != NULL_TREE)
{
tree t = receiver.type;
if (t == error_mark_node)
return error_mark_node;
*pp = tree_cons (NULL_TREE, t, NULL_TREE);
pp = &TREE_CHAIN (*pp);
}
for (std::vector<typed_identifier>::const_iterator p = parameters.begin ();
p != parameters.end (); ++p)
{
tree t = p->type;
if (t == error_mark_node)
return error_mark_node;
*pp = tree_cons (NULL_TREE, t, NULL_TREE);
pp = &TREE_CHAIN (*pp);
}
// Varargs is handled entirely at the Rust level. When converted to
// GENERIC functions are not varargs.
*pp = void_list_node;
tree result;
if (results.empty ())
result = void_type_node;
else if (results.size () == 1)
result = results.front ().type;
else
{
gcc_assert (result_struct != NULL);
result = result_struct;
}
if (result == error_mark_node)
return error_mark_node;
tree fntype = build_function_type (result, args);
if (fntype == error_mark_node)
return error_mark_node;
return build_pointer_type (fntype);
}
tree
function_type_variadic (const typed_identifier &receiver,
const std::vector<typed_identifier> &parameters,
const std::vector<typed_identifier> &results,
tree result_struct, location_t)
{
size_t n = parameters.size () + (receiver.type != NULL_TREE ? 1 : 0);
tree *args = XALLOCAVEC (tree, n);
size_t offs = 0;
if (receiver.type != NULL_TREE)
{
tree t = receiver.type;
if (t == error_mark_node)
return error_mark_node;
args[offs++] = t;
}
for (std::vector<typed_identifier>::const_iterator p = parameters.begin ();
p != parameters.end (); ++p)
{
tree t = p->type;
if (t == error_mark_node)
return error_mark_node;
args[offs++] = t;
}
tree result;
if (results.empty ())
result = void_type_node;
else if (results.size () == 1)
result = results.front ().type;
else
{
gcc_assert (result_struct != NULL_TREE);
result = result_struct;
}
if (result == error_mark_node)
return error_mark_node;
tree fntype = build_varargs_function_type_array (result, n, args);
if (fntype == error_mark_node)
return error_mark_node;
return build_pointer_type (fntype);
}
tree
function_ptr_type (tree result_type, const std::vector<tree> &parameters,
location_t /* locus */)
{
tree args = NULL_TREE;
tree *pp = &args;
for (auto &param : parameters)
{
if (param == error_mark_node)
return error_mark_node;
*pp = tree_cons (NULL_TREE, param, NULL_TREE);
pp = &TREE_CHAIN (*pp);
}
*pp = void_list_node;
tree result = result_type;
if (result != void_type_node && int_size_in_bytes (result) == 0)
result = void_type_node;
tree fntype = build_function_type (result, args);
if (fntype == error_mark_node)
return error_mark_node;
return build_pointer_type (fntype);
}
// Make a struct type.
tree
struct_type (const std::vector<typed_identifier> &fields)
{
return fill_in_fields (make_node (RECORD_TYPE), fields);
}
// Make a union type.
tree
union_type (const std::vector<typed_identifier> &fields)
{
return fill_in_fields (make_node (UNION_TYPE), fields);
}
// Fill in the fields of a struct or union type.
tree
fill_in_fields (tree fill, const std::vector<typed_identifier> &fields)
{
tree field_trees = NULL_TREE;
tree *pp = &field_trees;
for (std::vector<typed_identifier>::const_iterator p = fields.begin ();
p != fields.end (); ++p)
{
tree name_tree = get_identifier_from_string (p->name);
tree type_tree = p->type;
if (type_tree == error_mark_node)
return error_mark_node;
tree field = build_decl (p->location, FIELD_DECL, name_tree, type_tree);
DECL_CONTEXT (field) = fill;
*pp = field;
pp = &DECL_CHAIN (field);
}
TYPE_FIELDS (fill) = field_trees;
layout_type (fill);
// Because Rust permits converting between named struct types and
// equivalent struct types, for which we use VIEW_CONVERT_EXPR, and
// because we don't try to maintain TYPE_CANONICAL for struct types,
// we need to tell the middle-end to use structural equality.
SET_TYPE_STRUCTURAL_EQUALITY (fill);
return fill;
}
// Make an array type.
tree
array_type (tree element_type, tree length)
{
return fill_in_array (make_node (ARRAY_TYPE), element_type, length);
}
// Fill in an array type.
tree
fill_in_array (tree fill, tree element_type, tree length_tree)
{
if (element_type == error_mark_node || length_tree == error_mark_node)
return error_mark_node;
gcc_assert (TYPE_SIZE (element_type) != NULL_TREE);
length_tree = fold_convert (sizetype, length_tree);
// build_index_type takes the maximum index, which is one less than
// the length.
tree index_type_tree = build_index_type (
fold_build2 (MINUS_EXPR, sizetype, length_tree, size_one_node));
TREE_TYPE (fill) = element_type;
TYPE_DOMAIN (fill) = index_type_tree;
TYPE_ADDR_SPACE (fill) = TYPE_ADDR_SPACE (element_type);
layout_type (fill);
if (TYPE_STRUCTURAL_EQUALITY_P (element_type))
SET_TYPE_STRUCTURAL_EQUALITY (fill);
else if (TYPE_CANONICAL (element_type) != element_type
|| TYPE_CANONICAL (index_type_tree) != index_type_tree)
TYPE_CANONICAL (fill) = build_array_type (TYPE_CANONICAL (element_type),
TYPE_CANONICAL (index_type_tree));
return fill;
}
// Return a named version of a type.
tree
named_type (const std::string &name, tree type, location_t location)
{
if (type == error_mark_node)
return error_mark_node;
// The middle-end expects a basic type to have a name. In Rust every
// basic type will have a name. The first time we see a basic type,
// give it whatever Rust name we have at this point.
if (TYPE_NAME (type) == NULL_TREE && location == BUILTINS_LOCATION
&& (TREE_CODE (type) == INTEGER_TYPE || TREE_CODE (type) == REAL_TYPE
|| TREE_CODE (type) == COMPLEX_TYPE
|| TREE_CODE (type) == BOOLEAN_TYPE))
{
tree decl = build_decl (BUILTINS_LOCATION, TYPE_DECL,
get_identifier_from_string (name), type);
TYPE_NAME (type) = decl;
return type;
}
tree copy = build_variant_type_copy (type);
tree decl
= build_decl (location, TYPE_DECL, get_identifier_from_string (name), copy);
DECL_ORIGINAL_TYPE (decl) = type;
TYPE_NAME (copy) = decl;
return copy;
}
// Return the size of a type.
int64_t
type_size (tree t)
{
if (t == error_mark_node)
return 1;
if (t == void_type_node)
return 0;
t = TYPE_SIZE_UNIT (t);
gcc_assert (tree_fits_uhwi_p (t));
unsigned HOST_WIDE_INT val_wide = TREE_INT_CST_LOW (t);
int64_t ret = static_cast<int64_t> (val_wide);
if (ret < 0 || static_cast<unsigned HOST_WIDE_INT> (ret) != val_wide)
return -1;
return ret;
}
// Return the alignment of a type.
int64_t
type_alignment (tree t)
{
if (t == error_mark_node)
return 1;
return TYPE_ALIGN_UNIT (t);
}
// Return the alignment of a struct field of type BTYPE.
int64_t
type_field_alignment (tree t)
{
if (t == error_mark_node)
return 1;
return rust_field_alignment (t);
}
// Return the offset of a field in a struct.
int64_t
type_field_offset (tree struct_tree, size_t index)
{
if (struct_tree == error_mark_node)
return 0;
gcc_assert (TREE_CODE (struct_tree) == RECORD_TYPE);
tree field = TYPE_FIELDS (struct_tree);
for (; index > 0; --index)
{
field = DECL_CHAIN (field);
gcc_assert (field != NULL_TREE);
}
HOST_WIDE_INT offset_wide = int_byte_position (field);
int64_t ret = static_cast<int64_t> (offset_wide);
gcc_assert (ret == offset_wide);
return ret;
}
// Return the zero value for a type.
tree
zero_expression (tree t)
{
tree ret;
if (t == error_mark_node)
ret = error_mark_node;
else
ret = build_zero_cst (t);
return ret;
}
// An expression that references a variable.
tree
var_expression (Bvariable *var, location_t location)
{
return var->get_tree (location);
}
// Return a typed value as a constant floating-point number.
tree
float_constant_expression (tree t, mpfr_t val)
{
tree ret;
if (t == error_mark_node)
return error_mark_node;
REAL_VALUE_TYPE r1;
real_from_mpfr (&r1, val, t, GMP_RNDN);
REAL_VALUE_TYPE r2;
real_convert (&r2, TYPE_MODE (t), &r1);
ret = build_real (t, r2);
return ret;
}
// Make a constant string expression.
tree
string_constant_expression (const std::string &val)
{
tree index_type = build_index_type (size_int (val.length ()));
tree const_char_type = build_qualified_type (char_type_node, TYPE_QUAL_CONST);
tree string_type = build_array_type (const_char_type, index_type);
TYPE_STRING_FLAG (string_type) = 1;
tree string_val = build_string (val.length (), val.data ());
TREE_TYPE (string_val) = string_type;
return string_val;
}
tree
wchar_constant_expression (wchar_t c)
{
return build_int_cst (wchar_type (), c);
}
tree
char_constant_expression (char c)
{
return build_int_cst (char_type_node, c);
}
// Make a constant boolean expression.
tree
boolean_constant_expression (bool val)
{
return val ? boolean_true_node : boolean_false_node;
}
// An expression that converts an expression to a different type.
tree
convert_expression (tree type_tree, tree expr_tree, location_t location)
{
if (type_tree == error_mark_node || expr_tree == error_mark_node
|| TREE_TYPE (expr_tree) == error_mark_node)
return error_mark_node;
tree ret;
if (type_size (type_tree) == 0 || TREE_TYPE (expr_tree) == void_type_node)
{
// Do not convert zero-sized types.
ret = expr_tree;
}
else if (TREE_CODE (type_tree) == INTEGER_TYPE)
ret = convert_to_integer (type_tree, expr_tree);
else if (TREE_CODE (type_tree) == REAL_TYPE)
ret = convert_to_real (type_tree, expr_tree);
else if (TREE_CODE (type_tree) == COMPLEX_TYPE)
ret = convert_to_complex (type_tree, expr_tree);
else if (TREE_CODE (type_tree) == POINTER_TYPE
&& TREE_CODE (TREE_TYPE (expr_tree)) == INTEGER_TYPE)
ret = convert_to_pointer (type_tree, expr_tree);
else if (TREE_CODE (type_tree) == RECORD_TYPE
|| TREE_CODE (type_tree) == ARRAY_TYPE)
ret = fold_build1_loc (location, VIEW_CONVERT_EXPR, type_tree, expr_tree);
else
ret = fold_convert_loc (location, type_tree, expr_tree);
return ret;
}
// Return an expression for the field at INDEX in BSTRUCT.
tree
struct_field_expression (tree struct_tree, size_t index, location_t location)
{
if (struct_tree == error_mark_node
|| TREE_TYPE (struct_tree) == error_mark_node)
return error_mark_node;
gcc_assert (TREE_CODE (TREE_TYPE (struct_tree)) == RECORD_TYPE
|| TREE_CODE (TREE_TYPE (struct_tree)) == UNION_TYPE);
tree field = TYPE_FIELDS (TREE_TYPE (struct_tree));
if (field == NULL_TREE)
{
// This can happen for a type which refers to itself indirectly
// and then turns out to be erroneous.
return error_mark_node;
}
for (unsigned int i = index; i > 0; --i)
{
field = DECL_CHAIN (field);
gcc_assert (field != NULL_TREE);
}
if (TREE_TYPE (field) == error_mark_node)
return error_mark_node;
tree ret = fold_build3_loc (location, COMPONENT_REF, TREE_TYPE (field),
struct_tree, field, NULL_TREE);
if (TREE_CONSTANT (struct_tree))
TREE_CONSTANT (ret) = 1;
return ret;
}
// Return an expression that executes BSTAT before BEXPR.
tree
compound_expression (tree stat, tree expr, location_t location)
{
if (stat == error_mark_node || expr == error_mark_node)
return error_mark_node;
tree ret
= fold_build2_loc (location, COMPOUND_EXPR, TREE_TYPE (expr), stat, expr);
return ret;
}
// Return an expression that executes THEN_EXPR if CONDITION is true, or
// ELSE_EXPR otherwise.
tree
conditional_expression (tree, tree type_tree, tree cond_expr, tree then_expr,
tree else_expr, location_t location)
{
if (type_tree == error_mark_node || cond_expr == error_mark_node
|| then_expr == error_mark_node || else_expr == error_mark_node)
return error_mark_node;
tree ret = build3_loc (location, COND_EXPR, type_tree, cond_expr, then_expr,
else_expr);
return ret;
}
/* Helper function that converts rust operators to equivalent GCC tree_code.
Note that CompoundAssignmentOperator don't get their corresponding tree_code,
because they get compiled away when we lower AST to HIR. */
static enum tree_code
operator_to_tree_code (NegationOperator op)
{
switch (op)
{
case NegationOperator::NEGATE:
return NEGATE_EXPR;
case NegationOperator::NOT:
return TRUTH_NOT_EXPR;
default:
rust_unreachable ();
}
}
/* Note that GCC tree code distinguishes floating point division and integer
division. These two types of division are represented as the same rust
operator, and can only be distinguished via context(i.e. the TREE_TYPE of the
operands). */
static enum tree_code
operator_to_tree_code (ArithmeticOrLogicalOperator op, bool floating_point)
{
switch (op)
{
case ArithmeticOrLogicalOperator::ADD:
return PLUS_EXPR;
case ArithmeticOrLogicalOperator::SUBTRACT:
return MINUS_EXPR;
case ArithmeticOrLogicalOperator::MULTIPLY:
return MULT_EXPR;
case ArithmeticOrLogicalOperator::DIVIDE:
if (floating_point)
return RDIV_EXPR;
else
return TRUNC_DIV_EXPR;
case ArithmeticOrLogicalOperator::MODULUS:
return TRUNC_MOD_EXPR;
case ArithmeticOrLogicalOperator::BITWISE_AND:
return BIT_AND_EXPR;
case ArithmeticOrLogicalOperator::BITWISE_OR:
return BIT_IOR_EXPR;
case ArithmeticOrLogicalOperator::BITWISE_XOR:
return BIT_XOR_EXPR;
case ArithmeticOrLogicalOperator::LEFT_SHIFT:
return LSHIFT_EXPR;
case ArithmeticOrLogicalOperator::RIGHT_SHIFT:
return RSHIFT_EXPR;
default:
rust_unreachable ();
}
}
static enum tree_code
operator_to_tree_code (ComparisonOperator op)
{
switch (op)
{
case ComparisonOperator::EQUAL:
return EQ_EXPR;
case ComparisonOperator::NOT_EQUAL:
return NE_EXPR;
case ComparisonOperator::GREATER_THAN:
return GT_EXPR;
case ComparisonOperator::LESS_THAN:
return LT_EXPR;
case ComparisonOperator::GREATER_OR_EQUAL:
return GE_EXPR;
case ComparisonOperator::LESS_OR_EQUAL:
return LE_EXPR;
default:
rust_unreachable ();
}
}
static enum tree_code
operator_to_tree_code (LazyBooleanOperator op)
{
switch (op)
{
case LazyBooleanOperator::LOGICAL_OR:
return TRUTH_ORIF_EXPR;
case LazyBooleanOperator::LOGICAL_AND:
return TRUTH_ANDIF_EXPR;
default:
rust_unreachable ();
}
}
/* Helper function for deciding if a tree is a floating point node. */
bool
is_floating_point (tree t)
{
auto tree_type = TREE_CODE (TREE_TYPE (t));
return tree_type == REAL_TYPE || tree_type == COMPLEX_TYPE;
}
// Return an expression for the negation operation OP EXPR.
tree
negation_expression (NegationOperator op, tree expr_tree, location_t location)
{
/* Check if the expression is an error, in which case we return an error
expression. */
if (expr_tree == error_mark_node || TREE_TYPE (expr_tree) == error_mark_node)
return error_mark_node;
/* For negation operators, the resulting type should be the same as its
operand. */
auto tree_type = TREE_TYPE (expr_tree);
auto original_type = tree_type;
auto tree_code = operator_to_tree_code (op);
/* For floating point operations we may need to extend the precision of type.
For example, a 64-bit machine may not support operations on float32. */
bool floating_point = is_floating_point (expr_tree);
auto extended_type = NULL_TREE;
if (floating_point)
{
extended_type = excess_precision_type (tree_type);
if (extended_type != NULL_TREE)
{
expr_tree = convert (extended_type, expr_tree);
tree_type = extended_type;
}
}
/* Construct a new tree and build an expression from it. */
auto new_tree = fold_build1_loc (location, tree_code, tree_type, expr_tree);
if (floating_point && extended_type != NULL_TREE)
new_tree = convert (original_type, expr_tree);
return new_tree;
}
tree
arithmetic_or_logical_expression (ArithmeticOrLogicalOperator op, tree left,
tree right, location_t location)
{
/* Check if either expression is an error, in which case we return an error
expression. */
if (left == error_mark_node || right == error_mark_node)
return error_mark_node;
/* We need to determine if we're doing floating point arithmetics of integer
arithmetics. */
bool floating_point = is_floating_point (left);
auto ret = NULL_TREE;
/* For arithmetic or logical operators, the resulting type should be the same
as the lhs operand. */
auto tree_type = TREE_TYPE (left);
auto original_type = tree_type;
auto tree_code = operator_to_tree_code (op, floating_point);
/* For floating point operations we may need to extend the precision of type.
For example, a 64-bit machine may not support operations on float32. */
auto extended_type = NULL_TREE;
if (floating_point)
{
extended_type = excess_precision_type (tree_type);
if (extended_type != NULL_TREE)
{
left = convert (extended_type, left);
right = convert (extended_type, right);
tree_type = extended_type;
}
}
ret = fold_build2_loc (location, tree_code, tree_type, left, right);
TREE_CONSTANT (ret) = TREE_CONSTANT (left) & TREE_CONSTANT (right);
// TODO: How do we handle floating point?
if (floating_point && extended_type != NULL_TREE)
ret = convert (original_type, ret);
return ret;
}
static bool
is_overflowing_expr (ArithmeticOrLogicalOperator op)
{
switch (op)
{
case ArithmeticOrLogicalOperator::ADD:
case ArithmeticOrLogicalOperator::SUBTRACT:
case ArithmeticOrLogicalOperator::MULTIPLY:
return true;
default:
return false;
}
}
static std::pair<tree, tree>
fetch_overflow_builtins (ArithmeticOrLogicalOperator op)
{
auto builtin_ctx = Rust::Compile::BuiltinsContext::get ();
auto builtin = NULL_TREE;
auto abort = NULL_TREE;
switch (op)
{
case ArithmeticOrLogicalOperator::ADD:
builtin_ctx.lookup_simple_builtin ("__builtin_add_overflow", &builtin);
break;
case ArithmeticOrLogicalOperator::SUBTRACT:
builtin_ctx.lookup_simple_builtin ("__builtin_sub_overflow", &builtin);
break;
case ArithmeticOrLogicalOperator::MULTIPLY:
builtin_ctx.lookup_simple_builtin ("__builtin_mul_overflow", &builtin);
break;
default:
rust_unreachable ();
break;
};
builtin_ctx.lookup_simple_builtin ("__builtin_abort", &abort);
rust_assert (abort);
rust_assert (builtin);
// FIXME: ARTHUR: This is really ugly. The builtin context should take care of
// that
TREE_SIDE_EFFECTS (abort) = 1;
TREE_READONLY (abort) = 0;
// FIXME: ARTHUR: Same here. Remove these!
TREE_SIDE_EFFECTS (builtin) = 1;
TREE_READONLY (builtin) = 0;
return {abort, builtin};
}
// Return an expression for the arithmetic or logical operation LEFT OP RIGHT
// with overflow checking when possible
tree
arithmetic_or_logical_expression_checked (ArithmeticOrLogicalOperator op,
tree left, tree right,
location_t location,
Bvariable *receiver_var)
{
/* Check if either expression is an error, in which case we return an error
expression. */
if (left == error_mark_node || right == error_mark_node)
return error_mark_node;
// FIXME: Add `if (!debug_mode)`
// No overflow checks for floating point operations or divisions. In that
// case, simply assign the result of the operation to the receiver variable
if (is_floating_point (left) || !is_overflowing_expr (op))
return assignment_statement (
receiver_var->get_tree (location),
arithmetic_or_logical_expression (op, left, right, location), location);
auto receiver = receiver_var->get_tree (location);
TREE_ADDRESSABLE (receiver) = 1;
auto result_ref = build_fold_addr_expr_loc (location, receiver);
auto builtins = fetch_overflow_builtins (op);
auto abort = builtins.first;
auto builtin = builtins.second;
auto abort_call = build_call_expr_loc (location, abort, 0);
// FIXME: ARTHUR: Is that needed?
TREE_SIDE_EFFECTS (abort_call) = 1;
TREE_READONLY (abort_call) = 0;
auto builtin_call
= build_call_expr_loc (location, builtin, 3, left, right, result_ref);
auto overflow_check
= build2_loc (location, EQ_EXPR, boolean_type_node, builtin_call,
boolean_constant_expression (true));
auto if_block = build3_loc (location, COND_EXPR, void_type_node,
overflow_check, abort_call, NULL_TREE);
// FIXME: ARTHUR: Needed?
TREE_SIDE_EFFECTS (if_block) = 1;
TREE_READONLY (if_block) = 0;
return if_block;
}
// Return an expression for the comparison operation LEFT OP RIGHT.
tree
comparison_expression (ComparisonOperator op, tree left_tree, tree right_tree,
location_t location)
{
/* Check if either expression is an error, in which case we return an error
expression. */
if (left_tree == error_mark_node || right_tree == error_mark_node)
return error_mark_node;
/* For comparison operators, the resulting type should be boolean. */
auto tree_type = boolean_type_node;
auto tree_code = operator_to_tree_code (op);
/* Construct a new tree and build an expression from it. */
auto new_tree
= fold_build2_loc (location, tree_code, tree_type, left_tree, right_tree);
return new_tree;
}
// Return an expression for the lazy boolean operation LEFT OP RIGHT.
tree
lazy_boolean_expression (LazyBooleanOperator op, tree left_tree,
tree right_tree, location_t location)
{
/* Check if either expression is an error, in which case we return an error
expression. */
if (left_tree == error_mark_node || right_tree == error_mark_node)
return error_mark_node;
/* For lazy boolean operators, the resulting type should be the same as the
rhs operand. */
auto tree_type = TREE_TYPE (right_tree);
auto tree_code = operator_to_tree_code (op);
/* Construct a new tree and build an expression from it. */
auto new_tree
= fold_build2_loc (location, tree_code, tree_type, left_tree, right_tree);
return new_tree;
}
// Return an expression that constructs BTYPE with VALS.
tree
constructor_expression (tree type_tree, bool is_variant,
const std::vector<tree> &vals, int union_index,
location_t location)
{
if (type_tree == error_mark_node)
return error_mark_node;
vec<constructor_elt, va_gc> *init;
vec_alloc (init, vals.size ());
tree sink = NULL_TREE;
bool is_constant = true;
tree field = TYPE_FIELDS (type_tree);
if (is_variant)
{
gcc_assert (union_index != -1);
gcc_assert (TREE_CODE (type_tree) == UNION_TYPE);
for (int i = 0; i < union_index; i++)
{
gcc_assert (field != NULL_TREE);
field = DECL_CHAIN (field);
}
tree nested_ctor
= constructor_expression (TREE_TYPE (field), false, vals, -1, location);
constructor_elt empty = {NULL, NULL};
constructor_elt *elt = init->quick_push (empty);
elt->index = field;
elt->value = convert_tree (TREE_TYPE (field), nested_ctor, location);
if (!TREE_CONSTANT (elt->value))
is_constant = false;
}
else
{
if (union_index != -1)
{
gcc_assert (TREE_CODE (type_tree) == UNION_TYPE);
tree val = vals.front ();
for (int i = 0; i < union_index; i++)
{
gcc_assert (field != NULL_TREE);
field = DECL_CHAIN (field);
}
if (TREE_TYPE (field) == error_mark_node || val == error_mark_node
|| TREE_TYPE (val) == error_mark_node)
return error_mark_node;
if (int_size_in_bytes (TREE_TYPE (field)) == 0)
{
// GIMPLE cannot represent indices of zero-sized types so
// trying to construct a map with zero-sized keys might lead
// to errors. Instead, we evaluate each expression that
// would have been added as a map element for its
// side-effects and construct an empty map.
append_to_statement_list (val, &sink);
}
else
{
constructor_elt empty = {NULL, NULL};
constructor_elt *elt = init->quick_push (empty);
elt->index = field;
elt->value = convert_tree (TREE_TYPE (field), val, location);
if (!TREE_CONSTANT (elt->value))
is_constant = false;
}
}
else
{
gcc_assert (TREE_CODE (type_tree) == RECORD_TYPE);
for (std::vector<tree>::const_iterator p = vals.begin ();
p != vals.end (); ++p, field = DECL_CHAIN (field))
{
gcc_assert (field != NULL_TREE);
tree val = (*p);
if (TREE_TYPE (field) == error_mark_node || val == error_mark_node
|| TREE_TYPE (val) == error_mark_node)
return error_mark_node;
if (int_size_in_bytes (TREE_TYPE (field)) == 0)
{
// GIMPLE cannot represent indices of zero-sized types so
// trying to construct a map with zero-sized keys might lead
// to errors. Instead, we evaluate each expression that
// would have been added as a map element for its
// side-effects and construct an empty map.
append_to_statement_list (val, &sink);
continue;
}
constructor_elt empty = {NULL, NULL};
constructor_elt *elt = init->quick_push (empty);
elt->index = field;
elt->value = convert_tree (TREE_TYPE (field), val, location);
if (!TREE_CONSTANT (elt->value))
is_constant = false;
}
gcc_assert (field == NULL_TREE);
}
}
tree ret = build_constructor (type_tree, init);
if (is_constant)
TREE_CONSTANT (ret) = 1;
if (sink != NULL_TREE)
ret = fold_build2_loc (location, COMPOUND_EXPR, type_tree, sink, ret);
return ret;
}
tree
array_constructor_expression (tree type_tree,
const std::vector<unsigned long> &indexes,
const std::vector<tree> &vals,
location_t location)
{
if (type_tree == error_mark_node)
return error_mark_node;
gcc_assert (indexes.size () == vals.size ());
tree element_type = TREE_TYPE (type_tree);
HOST_WIDE_INT element_size = int_size_in_bytes (element_type);
vec<constructor_elt, va_gc> *init;
vec_alloc (init, element_size == 0 ? 0 : vals.size ());
tree sink = NULL_TREE;
bool is_constant = true;
for (size_t i = 0; i < vals.size (); ++i)
{
tree index = size_int (indexes[i]);
tree val = vals[i];
if (index == error_mark_node || val == error_mark_node)
return error_mark_node;
if (element_size == 0)
{
// GIMPLE cannot represent arrays of zero-sized types so trying
// to construct an array of zero-sized values might lead to errors.
// Instead, we evaluate each expression that would have been added as
// an array value for its side-effects and construct an empty array.
append_to_statement_list (val, &sink);
continue;
}
if (!TREE_CONSTANT (val))
is_constant = false;
constructor_elt empty = {NULL, NULL};
constructor_elt *elt = init->quick_push (empty);
elt->index = index;
elt->value = val;
}
tree ret = build_constructor (type_tree, init);
if (is_constant)
TREE_CONSTANT (ret) = 1;
if (sink != NULL_TREE)
ret = fold_build2_loc (location, COMPOUND_EXPR, type_tree, sink, ret);
return ret;
}
// Build insns to create an array, initialize all elements of the array to
// value, and return it
tree
array_initializer (tree fndecl, tree block, tree array_type, tree length,
tree value, tree *tmp, location_t locus)
{
std::vector<tree> stmts;
// Temporary array we initialize with the desired value.
tree t = NULL_TREE;
Bvariable *tmp_array = temporary_variable (fndecl, block, array_type,
NULL_TREE, true, locus, &t);
tree arr = tmp_array->get_tree (locus);
stmts.push_back (t);
// Temporary for the array length used for initialization loop guard.
Bvariable *tmp_len = temporary_variable (fndecl, block, size_type_node,
length, true, locus, &t);
tree len = tmp_len->get_tree (locus);
stmts.push_back (t);
// Temporary variable for pointer used to initialize elements.
tree ptr_type = pointer_type (TREE_TYPE (array_type));
tree ptr_init
= build1_loc (locus, ADDR_EXPR, ptr_type,
array_index_expression (arr, integer_zero_node, locus));
Bvariable *tmp_ptr
= temporary_variable (fndecl, block, ptr_type, ptr_init, false, locus, &t);
tree ptr = tmp_ptr->get_tree (locus);
stmts.push_back (t);
// push statement list for the loop
std::vector<tree> loop_stmts;
// Loop exit condition:
// if (length == 0) break;
t = comparison_expression (ComparisonOperator::EQUAL, len,
zero_expression (TREE_TYPE (len)), locus);
t = exit_expression (t, locus);
loop_stmts.push_back (t);
// Assign value to the current pointer position
// *ptr = value;
t = assignment_statement (build_fold_indirect_ref (ptr), value, locus);
loop_stmts.push_back (t);
// Move pointer to next element
// ptr++;
tree size = TYPE_SIZE_UNIT (TREE_TYPE (ptr_type));
t = build2 (POSTINCREMENT_EXPR, ptr_type, ptr, convert (ptr_type, size));
loop_stmts.push_back (t);
// Decrement loop counter.
// length--;
t = build2 (POSTDECREMENT_EXPR, TREE_TYPE (len), len,
convert (TREE_TYPE (len), integer_one_node));
loop_stmts.push_back (t);
// pop statments and finish loop
tree loop_body = statement_list (loop_stmts);
stmts.push_back (loop_expression (loop_body, locus));
// Return the temporary in the provided pointer and the statement list which
// initializes it.
*tmp = tmp_array->get_tree (locus);
return statement_list (stmts);
}
// Return an expression representing ARRAY[INDEX]
tree
array_index_expression (tree array_tree, tree index_tree, location_t location)
{
if (array_tree == error_mark_node || TREE_TYPE (array_tree) == error_mark_node
|| index_tree == error_mark_node)
return error_mark_node;
// A function call that returns a zero sized object will have been
// changed to return void. If we see void here, assume we are
// dealing with a zero sized type and just evaluate the operands.
tree ret;
if (TREE_TYPE (array_tree) != void_type_node)
ret = build4_loc (location, ARRAY_REF, TREE_TYPE (TREE_TYPE (array_tree)),
array_tree, index_tree, NULL_TREE, NULL_TREE);
else
ret = fold_build2_loc (location, COMPOUND_EXPR, void_type_node, array_tree,
index_tree);
return ret;
}
// Create an expression for a call to FN_EXPR with FN_ARGS.
tree
call_expression (tree fn, const std::vector<tree> &fn_args, tree chain_expr,
location_t location)
{
if (fn == error_mark_node || TREE_TYPE (fn) == error_mark_node)
return error_mark_node;
gcc_assert (FUNCTION_POINTER_TYPE_P (TREE_TYPE (fn)));
tree rettype = TREE_TYPE (TREE_TYPE (TREE_TYPE (fn)));
size_t nargs = fn_args.size ();
tree *args = nargs == 0 ? NULL : new tree[nargs];
for (size_t i = 0; i < nargs; ++i)
{
args[i] = fn_args.at (i);
}
tree fndecl = fn;
if (TREE_CODE (fndecl) == ADDR_EXPR)
fndecl = TREE_OPERAND (fndecl, 0);
// This is to support builtin math functions when using 80387 math.
tree excess_type = NULL_TREE;
if (optimize && TREE_CODE (fndecl) == FUNCTION_DECL
&& fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)
&& DECL_IS_UNDECLARED_BUILTIN (fndecl) && nargs > 0
&& ((SCALAR_FLOAT_TYPE_P (rettype)
&& SCALAR_FLOAT_TYPE_P (TREE_TYPE (args[0])))
|| (COMPLEX_FLOAT_TYPE_P (rettype)
&& COMPLEX_FLOAT_TYPE_P (TREE_TYPE (args[0])))))
{
excess_type = excess_precision_type (TREE_TYPE (args[0]));
if (excess_type != NULL_TREE)
{
tree excess_fndecl
= mathfn_built_in (excess_type, DECL_FUNCTION_CODE (fndecl));
if (excess_fndecl == NULL_TREE)
excess_type = NULL_TREE;
else
{
fn = build_fold_addr_expr_loc (location, excess_fndecl);
for (size_t i = 0; i < nargs; ++i)
{
if (SCALAR_FLOAT_TYPE_P (TREE_TYPE (args[i]))
|| COMPLEX_FLOAT_TYPE_P (TREE_TYPE (args[i])))
args[i] = ::convert (excess_type, args[i]);
}
}
}
}
tree ret
= build_call_array_loc (location,
excess_type != NULL_TREE ? excess_type : rettype,
fn, nargs, args);
// check for deprecated function usage
if (fndecl && TREE_DEPRECATED (fndecl))
{
// set up the call-site information for `warn_deprecated_use`
input_location = location;
warn_deprecated_use (fndecl, NULL_TREE);
}
if (chain_expr)
CALL_EXPR_STATIC_CHAIN (ret) = chain_expr;
if (excess_type != NULL_TREE)
{
// Calling convert here can undo our excess precision change.
// That may or may not be a bug in convert_to_real.
ret = build1_loc (location, NOP_EXPR, rettype, ret);
}
delete[] args;
return ret;
}
// Variable initialization.
tree
init_statement (tree, Bvariable *var, tree init_tree)
{
tree var_tree = var->get_decl ();
if (var_tree == error_mark_node || init_tree == error_mark_node)
return error_mark_node;
gcc_assert (TREE_CODE (var_tree) == VAR_DECL);
// To avoid problems with GNU ld, we don't make zero-sized
// externally visible variables. That might lead us to doing an
// initialization of a zero-sized expression to a non-zero sized
// variable, or vice-versa. Avoid crashes by omitting the
// initializer. Such initializations don't mean anything anyhow.
if (int_size_in_bytes (TREE_TYPE (var_tree)) != 0 && init_tree != NULL_TREE
&& TREE_TYPE (init_tree) != void_type_node
&& int_size_in_bytes (TREE_TYPE (init_tree)) != 0)
{
DECL_INITIAL (var_tree) = init_tree;
init_tree = NULL_TREE;
}
tree ret = build1_loc (DECL_SOURCE_LOCATION (var_tree), DECL_EXPR,
void_type_node, var_tree);
if (init_tree != NULL_TREE)
ret = build2_loc (DECL_SOURCE_LOCATION (var_tree), COMPOUND_EXPR,
void_type_node, init_tree, ret);
return ret;
}
// Assignment.
tree
assignment_statement (tree lhs, tree rhs, location_t location)
{
if (lhs == error_mark_node || rhs == error_mark_node)
return error_mark_node;
// To avoid problems with GNU ld, we don't make zero-sized
// externally visible variables. That might lead us to doing an
// assignment of a zero-sized expression to a non-zero sized
// expression; avoid crashes here by avoiding assignments of
// zero-sized expressions. Such assignments don't really mean
// anything anyhow.
if (TREE_TYPE (lhs) == void_type_node
|| int_size_in_bytes (TREE_TYPE (lhs)) == 0
|| TREE_TYPE (rhs) == void_type_node
|| int_size_in_bytes (TREE_TYPE (rhs)) == 0)
return compound_statement (lhs, rhs);
rhs = convert_tree (TREE_TYPE (lhs), rhs, location);
return fold_build2_loc (location, MODIFY_EXPR, void_type_node, lhs, rhs);
}
// Return.
tree
return_statement (tree fntree, tree val, location_t location)
{
if (fntree == error_mark_node)
return error_mark_node;
tree result = DECL_RESULT (fntree);
if (result == error_mark_node)
return error_mark_node;
if (val == error_mark_node)
return error_mark_node;
tree set
= fold_build2_loc (location, MODIFY_EXPR, void_type_node, result, val);
return fold_build1_loc (location, RETURN_EXPR, void_type_node, set);
}
// Create a statement that attempts to execute BSTAT and calls EXCEPT_STMT if an
// error occurs. EXCEPT_STMT may be NULL. FINALLY_STMT may be NULL and if not
// NULL, it will always be executed. This is used for handling defers in Rust
// functions. In C++, the resulting code is of this form:
// try { BSTAT; } catch { EXCEPT_STMT; } finally { FINALLY_STMT; }
tree
exception_handler_statement (tree try_stmt, tree except_stmt, tree finally_stmt,
location_t location)
{
if (try_stmt == error_mark_node || except_stmt == error_mark_node
|| finally_stmt == error_mark_node)
return error_mark_node;
if (except_stmt != NULL_TREE)
try_stmt = build2_loc (location, TRY_CATCH_EXPR, void_type_node, try_stmt,
build2_loc (location, CATCH_EXPR, void_type_node,
NULL, except_stmt));
if (finally_stmt != NULL_TREE)
try_stmt = build2_loc (location, TRY_FINALLY_EXPR, void_type_node, try_stmt,
finally_stmt);
return try_stmt;
}
// If.
tree
if_statement (tree, tree cond_tree, tree then_tree, tree else_tree,
location_t location)
{
if (cond_tree == error_mark_node || then_tree == error_mark_node
|| else_tree == error_mark_node)
return error_mark_node;
tree ret = build3_loc (location, COND_EXPR, void_type_node, cond_tree,
then_tree, else_tree);
return ret;
}
// Loops
tree
loop_expression (tree body, location_t locus)
{
return fold_build1_loc (locus, LOOP_EXPR, void_type_node, body);
}
tree
exit_expression (tree cond_tree, location_t locus)
{
return fold_build1_loc (locus, EXIT_EXPR, void_type_node, cond_tree);
}
// Pair of statements.
tree
compound_statement (tree s1, tree s2)
{
tree stmt_list = NULL_TREE;
tree t = s1;
if (t == error_mark_node)
return error_mark_node;
append_to_statement_list (t, &stmt_list);
t = s2;
if (t == error_mark_node)
return error_mark_node;
append_to_statement_list (t, &stmt_list);
// If neither statement has any side effects, stmt_list can be NULL
// at this point.
if (stmt_list == NULL_TREE)
stmt_list = integer_zero_node;
return stmt_list;
}
// List of statements.
tree
statement_list (const std::vector<tree> &statements)
{
tree stmt_list = NULL_TREE;
for (std::vector<tree>::const_iterator p = statements.begin ();
p != statements.end (); ++p)
{
tree t = (*p);
if (t == error_mark_node)
return error_mark_node;
append_to_statement_list (t, &stmt_list);
}
return stmt_list;
}
// Make a block. For some reason gcc uses a dual structure for
// blocks: BLOCK tree nodes and BIND_EXPR tree nodes. Since the
// BIND_EXPR node points to the BLOCK node, we store the BIND_EXPR in
// the Bblock.
tree
block (tree fndecl, tree enclosing, const std::vector<Bvariable *> &vars,
location_t start_location, location_t)
{
tree block_tree = make_node (BLOCK);
if (enclosing == NULL)
{
gcc_assert (fndecl != NULL_TREE);
// We may have already created a block for local variables when
// we take the address of a parameter.
if (DECL_INITIAL (fndecl) == NULL_TREE)
{
BLOCK_SUPERCONTEXT (block_tree) = fndecl;
DECL_INITIAL (fndecl) = block_tree;
}
else
{
tree superblock_tree = DECL_INITIAL (fndecl);
BLOCK_SUPERCONTEXT (block_tree) = superblock_tree;
tree *pp;
for (pp = &BLOCK_SUBBLOCKS (superblock_tree); *pp != NULL_TREE;
pp = &BLOCK_CHAIN (*pp))
;
*pp = block_tree;
}
}
else
{
tree superblock_tree = BIND_EXPR_BLOCK (enclosing);
gcc_assert (TREE_CODE (superblock_tree) == BLOCK);
BLOCK_SUPERCONTEXT (block_tree) = superblock_tree;
tree *pp;
for (pp = &BLOCK_SUBBLOCKS (superblock_tree); *pp != NULL_TREE;
pp = &BLOCK_CHAIN (*pp))
;
*pp = block_tree;
}
tree *pp = &BLOCK_VARS (block_tree);
for (std::vector<Bvariable *>::const_iterator pv = vars.begin ();
pv != vars.end (); ++pv)
{
*pp = (*pv)->get_decl ();
if (*pp != error_mark_node)
pp = &DECL_CHAIN (*pp);
}
*pp = NULL_TREE;
TREE_USED (block_tree) = 1;
tree bind_tree = build3_loc (start_location, BIND_EXPR, void_type_node,
BLOCK_VARS (block_tree), NULL_TREE, block_tree);
TREE_SIDE_EFFECTS (bind_tree) = 1;
return bind_tree;
}
// Add statements to a block.
void
block_add_statements (tree bind_tree, const std::vector<tree> &statements)
{
tree stmt_list = NULL_TREE;
for (std::vector<tree>::const_iterator p = statements.begin ();
p != statements.end (); ++p)
{
tree s = (*p);
if (s != error_mark_node)
append_to_statement_list (s, &stmt_list);
}
gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR);
BIND_EXPR_BODY (bind_tree) = stmt_list;
}
// This is not static because we declare it with GTY(()) in rust-c.h.
tree rust_non_zero_struct;
// Return a type corresponding to TYPE with non-zero size.
tree
non_zero_size_type (tree type)
{
if (int_size_in_bytes (type) != 0)
return type;
switch (TREE_CODE (type))
{
case RECORD_TYPE:
if (TYPE_FIELDS (type) != NULL_TREE)
{
tree ns = make_node (RECORD_TYPE);
tree field_trees = NULL_TREE;
tree *pp = &field_trees;
for (tree field = TYPE_FIELDS (type); field != NULL_TREE;
field = DECL_CHAIN (field))
{
tree ft = TREE_TYPE (field);
if (field == TYPE_FIELDS (type))
ft = non_zero_size_type (ft);
tree f = build_decl (DECL_SOURCE_LOCATION (field), FIELD_DECL,
DECL_NAME (field), ft);
DECL_CONTEXT (f) = ns;
*pp = f;
pp = &DECL_CHAIN (f);
}
TYPE_FIELDS (ns) = field_trees;
layout_type (ns);
return ns;
}
if (rust_non_zero_struct == NULL_TREE)
{
type = make_node (RECORD_TYPE);
tree field = build_decl (UNKNOWN_LOCATION, FIELD_DECL,
get_identifier ("dummy"), boolean_type_node);
DECL_CONTEXT (field) = type;
TYPE_FIELDS (type) = field;
layout_type (type);
rust_non_zero_struct = type;
}
return rust_non_zero_struct;
case ARRAY_TYPE: {
tree element_type = non_zero_size_type (TREE_TYPE (type));
return build_array_type_nelts (element_type, 1);
}
default:
rust_unreachable ();
}
rust_unreachable ();
}
// Convert EXPR_TREE to TYPE_TREE. Sometimes the same unnamed Rust type
// can be created multiple times and thus have multiple tree
// representations. Make sure this does not confuse the middle-end.
tree
convert_tree (tree type_tree, tree expr_tree, location_t location)
{
if (type_tree == TREE_TYPE (expr_tree))
return expr_tree;
if (type_tree == error_mark_node || expr_tree == error_mark_node
|| TREE_TYPE (expr_tree) == error_mark_node)
return error_mark_node;
if (POINTER_TYPE_P (type_tree) || INTEGRAL_TYPE_P (type_tree)
|| SCALAR_FLOAT_TYPE_P (type_tree) || COMPLEX_FLOAT_TYPE_P (type_tree))
return fold_convert_loc (location, type_tree, expr_tree);
else if (TREE_CODE (type_tree) == RECORD_TYPE
|| TREE_CODE (type_tree) == UNION_TYPE
|| TREE_CODE (type_tree) == ARRAY_TYPE)
{
gcc_assert (int_size_in_bytes (type_tree)
== int_size_in_bytes (TREE_TYPE (expr_tree)));
if (TYPE_MAIN_VARIANT (type_tree)
== TYPE_MAIN_VARIANT (TREE_TYPE (expr_tree)))
return fold_build1_loc (location, NOP_EXPR, type_tree, expr_tree);
return fold_build1_loc (location, VIEW_CONVERT_EXPR, type_tree,
expr_tree);
}
rust_unreachable ();
}
// Make a global variable.
Bvariable *
global_variable (const std::string &var_name, const std::string &asm_name,
tree type_tree, bool is_external, bool is_hidden,
bool in_unique_section, location_t location)
{
if (type_tree == error_mark_node)
return Bvariable::error_variable ();
// The GNU linker does not like dynamic variables with zero size.
tree orig_type_tree = type_tree;
if ((is_external || !is_hidden) && int_size_in_bytes (type_tree) == 0)
type_tree = non_zero_size_type (type_tree);
tree decl = build_decl (location, VAR_DECL,
get_identifier_from_string (var_name), type_tree);
if (is_external)
DECL_EXTERNAL (decl) = 1;
else
TREE_STATIC (decl) = 1;
if (!is_hidden)
{
TREE_PUBLIC (decl) = 1;
SET_DECL_ASSEMBLER_NAME (decl, get_identifier_from_string (asm_name));
}
else
{
SET_DECL_ASSEMBLER_NAME (decl, get_identifier_from_string (asm_name));
}
TREE_USED (decl) = 1;
if (in_unique_section)
resolve_unique_section (decl, 0, 1);
rust_preserve_from_gc (decl);
return new Bvariable (decl, orig_type_tree);
}
// Set the initial value of a global variable.
void
global_variable_set_init (Bvariable *var, tree expr_tree)
{
if (expr_tree == error_mark_node)
return;
gcc_assert (TREE_CONSTANT (expr_tree));
tree var_decl = var->get_decl ();
if (var_decl == error_mark_node)
return;
DECL_INITIAL (var_decl) = expr_tree;
// If this variable goes in a unique section, it may need to go into
// a different one now that DECL_INITIAL is set.
if (symtab_node::get (var_decl)
&& symtab_node::get (var_decl)->implicit_section)
{
set_decl_section_name (var_decl, (const char *) NULL);
resolve_unique_section (var_decl, compute_reloc_for_constant (expr_tree),
1);
}
}
// Make a local variable.
Bvariable *
local_variable (tree function, const std::string &name, tree type_tree,
Bvariable *decl_var, location_t location)
{
if (type_tree == error_mark_node)
return Bvariable::error_variable ();
tree decl = build_decl (location, VAR_DECL, get_identifier_from_string (name),
type_tree);
DECL_CONTEXT (decl) = function;
if (decl_var != NULL)
{
DECL_HAS_VALUE_EXPR_P (decl) = 1;
SET_DECL_VALUE_EXPR (decl, decl_var->get_decl ());
}
rust_preserve_from_gc (decl);
return new Bvariable (decl);
}
// Make a function parameter variable.
Bvariable *
parameter_variable (tree function, const std::string &name, tree type_tree,
location_t location)
{
if (type_tree == error_mark_node)
return Bvariable::error_variable ();
tree decl = build_decl (location, PARM_DECL,
get_identifier_from_string (name), type_tree);
DECL_CONTEXT (decl) = function;
DECL_ARG_TYPE (decl) = type_tree;
rust_preserve_from_gc (decl);
return new Bvariable (decl);
}
// Make a static chain variable.
Bvariable *
static_chain_variable (tree fndecl, const std::string &name, tree type_tree,
location_t location)
{
if (type_tree == error_mark_node)
return Bvariable::error_variable ();
tree decl = build_decl (location, PARM_DECL,
get_identifier_from_string (name), type_tree);
DECL_CONTEXT (decl) = fndecl;
DECL_ARG_TYPE (decl) = type_tree;
TREE_USED (decl) = 1;
DECL_ARTIFICIAL (decl) = 1;
DECL_IGNORED_P (decl) = 1;
TREE_READONLY (decl) = 1;
struct function *f = DECL_STRUCT_FUNCTION (fndecl);
if (f == NULL)
{
push_struct_function (fndecl);
pop_cfun ();
f = DECL_STRUCT_FUNCTION (fndecl);
}
gcc_assert (f->static_chain_decl == NULL);
f->static_chain_decl = decl;
DECL_STATIC_CHAIN (fndecl) = 1;
rust_preserve_from_gc (decl);
return new Bvariable (decl);
}
// Make a temporary variable.
Bvariable *
temporary_variable (tree fndecl, tree bind_tree, tree type_tree, tree init_tree,
bool is_address_taken, location_t location,
tree *pstatement)
{
gcc_assert (fndecl != NULL_TREE);
if (type_tree == error_mark_node || init_tree == error_mark_node
|| fndecl == error_mark_node)
{
*pstatement = error_mark_node;
return Bvariable::error_variable ();
}
tree var;
// We can only use create_tmp_var if the type is not addressable.
if (!TREE_ADDRESSABLE (type_tree))
{
if (DECL_STRUCT_FUNCTION (fndecl) == NULL)
push_struct_function (fndecl);
else
push_cfun (DECL_STRUCT_FUNCTION (fndecl));
var = create_tmp_var (type_tree, "RUSTTMP");
pop_cfun ();
}
else
{
gcc_assert (bind_tree != NULL_TREE);
var = build_decl (location, VAR_DECL, create_tmp_var_name ("RUSTTMP"),
type_tree);
DECL_ARTIFICIAL (var) = 1;
DECL_IGNORED_P (var) = 1;
TREE_USED (var) = 1;
DECL_CONTEXT (var) = fndecl;
// We have to add this variable to the BLOCK and the BIND_EXPR.
gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR);
tree block_tree = BIND_EXPR_BLOCK (bind_tree);
gcc_assert (TREE_CODE (block_tree) == BLOCK);
DECL_CHAIN (var) = BLOCK_VARS (block_tree);
BLOCK_VARS (block_tree) = var;
BIND_EXPR_VARS (bind_tree) = BLOCK_VARS (block_tree);
}
if (type_size (type_tree) != 0 && init_tree != NULL_TREE
&& TREE_TYPE (init_tree) != void_type_node)
DECL_INITIAL (var) = convert_tree (type_tree, init_tree, location);
if (is_address_taken)
TREE_ADDRESSABLE (var) = 1;
*pstatement = build1_loc (location, DECL_EXPR, void_type_node, var);
// For a zero sized type, don't initialize VAR with BINIT, but still
// evaluate BINIT for its side effects.
if (init_tree != NULL_TREE
&& (type_size (type_tree) == 0
|| TREE_TYPE (init_tree) == void_type_node))
*pstatement = compound_statement (init_tree, *pstatement);
return new Bvariable (var);
}
// Make a label.
tree
label (tree func_tree, const std::string &name, location_t location)
{
tree decl;
if (name.empty ())
{
if (DECL_STRUCT_FUNCTION (func_tree) == NULL)
push_struct_function (func_tree);
else
push_cfun (DECL_STRUCT_FUNCTION (func_tree));
decl = create_artificial_label (location);
pop_cfun ();
}
else
{
tree id = get_identifier_from_string (name);
decl = build_decl (location, LABEL_DECL, id, void_type_node);
DECL_CONTEXT (decl) = func_tree;
}
return decl;
}
// Make a statement which defines a label.
tree
label_definition_statement (tree label)
{
return fold_build1_loc (DECL_SOURCE_LOCATION (label), LABEL_EXPR,
void_type_node, label);
}
// Make a goto statement.
tree
goto_statement (tree label, location_t location)
{
return fold_build1_loc (location, GOTO_EXPR, void_type_node, label);
}
// Get the address of a label.
tree
label_address (tree label, location_t location)
{
TREE_USED (label) = 1;
TREE_ADDRESSABLE (label) = 1;
tree ret = fold_convert_loc (location, ptr_type_node,
build_fold_addr_expr_loc (location, label));
return ret;
}
// Declare or define a new function.
tree
function (tree functype, const std::string &name, const std::string &asm_name,
unsigned int flags, location_t location)
{
if (functype != error_mark_node)
{
gcc_assert (FUNCTION_POINTER_TYPE_P (functype));
functype = TREE_TYPE (functype);
}
tree id = get_identifier_from_string (name);
if (functype == error_mark_node || id == error_mark_node)
return error_mark_node;
tree decl = build_decl (location, FUNCTION_DECL, id, functype);
if (!asm_name.empty ())
SET_DECL_ASSEMBLER_NAME (decl, get_identifier_from_string (asm_name));
if ((flags & function_is_declaration) != 0)
DECL_EXTERNAL (decl) = 1;
else
{
tree restype = TREE_TYPE (functype);
tree resdecl = build_decl (location, RESULT_DECL, NULL_TREE, restype);
DECL_ARTIFICIAL (resdecl) = 1;
DECL_IGNORED_P (resdecl) = 1;
DECL_CONTEXT (resdecl) = decl;
DECL_RESULT (decl) = resdecl;
}
if ((flags & function_is_uninlinable) != 0)
DECL_UNINLINABLE (decl) = 1;
if ((flags & function_does_not_return) != 0)
TREE_THIS_VOLATILE (decl) = 1;
if ((flags & function_in_unique_section) != 0)
resolve_unique_section (decl, 0, 1);
rust_preserve_from_gc (decl);
return decl;
}
// Create a statement that runs all deferred calls for FUNCTION. This should
// be a statement that looks like this in C++:
// finish:
// try { UNDEFER; } catch { CHECK_DEFER; goto finish; }
tree
function_defer_statement (tree function, tree undefer_tree, tree defer_tree,
location_t location)
{
if (undefer_tree == error_mark_node || defer_tree == error_mark_node
|| function == error_mark_node)
return error_mark_node;
if (DECL_STRUCT_FUNCTION (function) == NULL)
push_struct_function (function);
else
push_cfun (DECL_STRUCT_FUNCTION (function));
tree stmt_list = NULL;
tree label = Backend::label (function, "", location);
tree label_def = label_definition_statement (label);
append_to_statement_list (label_def, &stmt_list);
tree jump_stmt = goto_statement (label, location);
tree catch_body
= build2 (COMPOUND_EXPR, void_type_node, defer_tree, jump_stmt);
catch_body = build2 (CATCH_EXPR, void_type_node, NULL, catch_body);
tree try_catch
= build2 (TRY_CATCH_EXPR, void_type_node, undefer_tree, catch_body);
append_to_statement_list (try_catch, &stmt_list);
pop_cfun ();
return stmt_list;
}
// Record PARAM_VARS as the variables to use for the parameters of FUNCTION.
// This will only be called for a function definition.
bool
function_set_parameters (tree function,
const std::vector<Bvariable *> &param_vars)
{
if (function == error_mark_node)
return false;
tree params = NULL_TREE;
tree *pp = &params;
for (std::vector<Bvariable *>::const_iterator pv = param_vars.begin ();
pv != param_vars.end (); ++pv)
{
*pp = (*pv)->get_decl ();
gcc_assert (*pp != error_mark_node);
pp = &DECL_CHAIN (*pp);
}
*pp = NULL_TREE;
DECL_ARGUMENTS (function) = params;
return true;
}
// Write the definitions for all TYPE_DECLS, CONSTANT_DECLS,
// FUNCTION_DECLS, and VARIABLE_DECLS declared globally, as well as
// emit early debugging information.
void
write_global_definitions (const std::vector<tree> &type_decls,
const std::vector<tree> &constant_decls,
const std::vector<tree> &function_decls,
const std::vector<Bvariable *> &variable_decls)
{
size_t count_definitions = type_decls.size () + constant_decls.size ()
+ function_decls.size () + variable_decls.size ();
tree *defs = new tree[count_definitions];
// Convert all non-erroneous declarations into Gimple form.
size_t i = 0;
for (std::vector<Bvariable *>::const_iterator p = variable_decls.begin ();
p != variable_decls.end (); ++p)
{
tree v = (*p)->get_decl ();
if (v != error_mark_node)
{
defs[i] = v;
rust_preserve_from_gc (defs[i]);
++i;
}
}
for (std::vector<tree>::const_iterator p = type_decls.begin ();
p != type_decls.end (); ++p)
{
tree type_tree = (*p);
if (type_tree != error_mark_node && IS_TYPE_OR_DECL_P (type_tree))
{
defs[i] = TYPE_NAME (type_tree);
gcc_assert (defs[i] != NULL);
rust_preserve_from_gc (defs[i]);
++i;
}
}
for (std::vector<tree>::const_iterator p = constant_decls.begin ();
p != constant_decls.end (); ++p)
{
if ((*p) != error_mark_node)
{
defs[i] = (*p);
rust_preserve_from_gc (defs[i]);
++i;
}
}
for (std::vector<tree>::const_iterator p = function_decls.begin ();
p != function_decls.end (); ++p)
{
tree decl = (*p);
if (decl != error_mark_node)
{
rust_preserve_from_gc (decl);
if (DECL_STRUCT_FUNCTION (decl) == NULL)
allocate_struct_function (decl, false);
dump_function (TDI_original, decl);
cgraph_node::finalize_function (decl, true);
defs[i] = decl;
++i;
}
}
// Pass everything back to the middle-end.
wrapup_global_declarations (defs, i);
delete[] defs;
}
} // namespace Backend