3374 lines
86 KiB
C
3374 lines
86 KiB
C
/* Internals of libgccjit: classes for playing back recorded API calls.
|
|
Copyright (C) 2013-2021 Free Software Foundation, Inc.
|
|
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
|
|
|
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 "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "target.h"
|
|
#include "tree.h"
|
|
#include "stringpool.h"
|
|
#include "cgraph.h"
|
|
#include "dumpfile.h"
|
|
#include "toplev.h"
|
|
#include "tree-cfg.h"
|
|
#include "convert.h"
|
|
#include "stor-layout.h"
|
|
#include "print-tree.h"
|
|
#include "gimplify.h"
|
|
#include "gcc-driver-name.h"
|
|
#include "attribs.h"
|
|
#include "context.h"
|
|
#include "fold-const.h"
|
|
#include "opt-suggestions.h"
|
|
#include "gcc.h"
|
|
#include "diagnostic.h"
|
|
#include "stmt.h"
|
|
|
|
#include <pthread.h>
|
|
|
|
#include "jit-playback.h"
|
|
#include "jit-result.h"
|
|
#include "jit-builtins.h"
|
|
#include "jit-tempdir.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "jit-w32.h"
|
|
#endif
|
|
|
|
/* Compare with gcc/c-family/c-common.h: DECL_C_BIT_FIELD,
|
|
SET_DECL_C_BIT_FIELD.
|
|
These are redefined here to avoid depending from the C frontend. */
|
|
#define DECL_JIT_BIT_FIELD(NODE) \
|
|
(DECL_LANG_FLAG_4 (FIELD_DECL_CHECK (NODE)) == 1)
|
|
#define SET_DECL_JIT_BIT_FIELD(NODE) \
|
|
(DECL_LANG_FLAG_4 (FIELD_DECL_CHECK (NODE)) = 1)
|
|
|
|
/* gcc::jit::playback::context::build_cast uses the convert.h API,
|
|
which in turn requires the frontend to provide a "convert"
|
|
function, apparently as a fallback.
|
|
|
|
Hence we provide this dummy one, with the requirement that any casts
|
|
are handled before reaching this. */
|
|
extern tree convert (tree type, tree expr);
|
|
|
|
tree
|
|
convert (tree dst_type, tree expr)
|
|
{
|
|
gcc_assert (gcc::jit::active_playback_ctxt);
|
|
gcc::jit::active_playback_ctxt->add_error (NULL, "unhandled conversion");
|
|
fprintf (stderr, "input expression:\n");
|
|
debug_tree (expr);
|
|
fprintf (stderr, "requested type:\n");
|
|
debug_tree (dst_type);
|
|
return error_mark_node;
|
|
}
|
|
|
|
namespace gcc {
|
|
namespace jit {
|
|
|
|
/**********************************************************************
|
|
Playback.
|
|
**********************************************************************/
|
|
|
|
/* Build a STRING_CST tree for STR, or return NULL if it is NULL.
|
|
The TREE_TYPE is not initialized. */
|
|
|
|
static tree
|
|
build_string (const char *str)
|
|
{
|
|
if (str)
|
|
return ::build_string (strlen (str), str);
|
|
else
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* The constructor for gcc::jit::playback::context. */
|
|
|
|
playback::context::context (recording::context *ctxt)
|
|
: log_user (ctxt->get_logger ()),
|
|
m_recording_ctxt (ctxt),
|
|
m_tempdir (NULL),
|
|
m_const_char_ptr (NULL)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
m_functions.create (0);
|
|
m_globals.create (0);
|
|
m_source_files.create (0);
|
|
m_cached_locations.create (0);
|
|
}
|
|
|
|
/* The destructor for gcc::jit::playback::context. */
|
|
|
|
playback::context::~context ()
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
|
|
/* Normally the playback::context is responsible for cleaning up the
|
|
tempdir (including "fake.so" within the filesystem).
|
|
|
|
In the normal case, clean it up now.
|
|
|
|
However m_tempdir can be NULL if the context has handed over
|
|
responsibility for the tempdir cleanup to the jit::result object, so
|
|
that the cleanup can be delayed (see PR jit/64206). If that's the
|
|
case this "delete NULL;" is a no-op. */
|
|
delete m_tempdir;
|
|
|
|
m_functions.release ();
|
|
}
|
|
|
|
/* A playback::context can reference GC-managed pointers. Mark them
|
|
("by hand", rather than by gengtype).
|
|
|
|
This is called on the active playback context (if any) by the
|
|
my_ggc_walker hook in the jit_root_table in dummy-frontend.c. */
|
|
|
|
void
|
|
playback::context::
|
|
gt_ggc_mx ()
|
|
{
|
|
int i;
|
|
function *func;
|
|
FOR_EACH_VEC_ELT (m_functions, i, func)
|
|
{
|
|
if (ggc_test_and_set_mark (func))
|
|
func->gt_ggc_mx ();
|
|
}
|
|
}
|
|
|
|
/* Given an enum gcc_jit_types value, get a "tree" type. */
|
|
|
|
static tree
|
|
get_tree_node_for_type (enum gcc_jit_types type_)
|
|
{
|
|
switch (type_)
|
|
{
|
|
case GCC_JIT_TYPE_VOID:
|
|
return void_type_node;
|
|
|
|
case GCC_JIT_TYPE_VOID_PTR:
|
|
return ptr_type_node;
|
|
|
|
case GCC_JIT_TYPE_BOOL:
|
|
return boolean_type_node;
|
|
|
|
case GCC_JIT_TYPE_CHAR:
|
|
return char_type_node;
|
|
case GCC_JIT_TYPE_SIGNED_CHAR:
|
|
return signed_char_type_node;
|
|
case GCC_JIT_TYPE_UNSIGNED_CHAR:
|
|
return unsigned_char_type_node;
|
|
|
|
case GCC_JIT_TYPE_SHORT:
|
|
return short_integer_type_node;
|
|
case GCC_JIT_TYPE_UNSIGNED_SHORT:
|
|
return short_unsigned_type_node;
|
|
|
|
case GCC_JIT_TYPE_CONST_CHAR_PTR:
|
|
{
|
|
tree const_char = build_qualified_type (char_type_node,
|
|
TYPE_QUAL_CONST);
|
|
return build_pointer_type (const_char);
|
|
}
|
|
|
|
case GCC_JIT_TYPE_INT:
|
|
return integer_type_node;
|
|
case GCC_JIT_TYPE_UNSIGNED_INT:
|
|
return unsigned_type_node;
|
|
|
|
case GCC_JIT_TYPE_LONG:
|
|
return long_integer_type_node;
|
|
case GCC_JIT_TYPE_UNSIGNED_LONG:
|
|
return long_unsigned_type_node;
|
|
|
|
case GCC_JIT_TYPE_LONG_LONG:
|
|
return long_long_integer_type_node;
|
|
case GCC_JIT_TYPE_UNSIGNED_LONG_LONG:
|
|
return long_long_unsigned_type_node;
|
|
|
|
case GCC_JIT_TYPE_FLOAT:
|
|
return float_type_node;
|
|
case GCC_JIT_TYPE_DOUBLE:
|
|
return double_type_node;
|
|
case GCC_JIT_TYPE_LONG_DOUBLE:
|
|
return long_double_type_node;
|
|
|
|
case GCC_JIT_TYPE_SIZE_T:
|
|
return size_type_node;
|
|
|
|
case GCC_JIT_TYPE_FILE_PTR:
|
|
return fileptr_type_node;
|
|
|
|
case GCC_JIT_TYPE_COMPLEX_FLOAT:
|
|
return complex_float_type_node;
|
|
case GCC_JIT_TYPE_COMPLEX_DOUBLE:
|
|
return complex_double_type_node;
|
|
case GCC_JIT_TYPE_COMPLEX_LONG_DOUBLE:
|
|
return complex_long_double_type_node;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Construct a playback::type instance (wrapping a tree) for the given
|
|
enum value. */
|
|
|
|
playback::type *
|
|
playback::context::
|
|
get_type (enum gcc_jit_types type_)
|
|
{
|
|
tree type_node = get_tree_node_for_type (type_);
|
|
if (type_node == NULL)
|
|
{
|
|
add_error (NULL, "unrecognized (enum gcc_jit_types) value: %i", type_);
|
|
return NULL;
|
|
}
|
|
|
|
return new type (type_node);
|
|
}
|
|
|
|
/* Construct a playback::type instance (wrapping a tree) for the given
|
|
array type. */
|
|
|
|
playback::type *
|
|
playback::context::
|
|
new_array_type (playback::location *loc,
|
|
playback::type *element_type,
|
|
int num_elements)
|
|
{
|
|
gcc_assert (element_type);
|
|
|
|
tree t = build_array_type_nelts (element_type->as_tree (),
|
|
num_elements);
|
|
layout_type (t);
|
|
|
|
if (loc)
|
|
set_tree_location (t, loc);
|
|
|
|
return new type (t);
|
|
}
|
|
|
|
/* Construct a playback::field instance (wrapping a tree). */
|
|
|
|
playback::field *
|
|
playback::context::
|
|
new_field (location *loc,
|
|
type *type,
|
|
const char *name)
|
|
{
|
|
gcc_assert (type);
|
|
gcc_assert (name);
|
|
|
|
/* compare with c/c-decl.c:grokfield and grokdeclarator. */
|
|
tree decl = build_decl (UNKNOWN_LOCATION, FIELD_DECL,
|
|
get_identifier (name), type->as_tree ());
|
|
|
|
if (loc)
|
|
set_tree_location (decl, loc);
|
|
|
|
return new field (decl);
|
|
}
|
|
|
|
/* Construct a playback::bitfield instance (wrapping a tree). */
|
|
|
|
playback::field *
|
|
playback::context::
|
|
new_bitfield (location *loc,
|
|
type *type,
|
|
int width,
|
|
const char *name)
|
|
{
|
|
gcc_assert (type);
|
|
gcc_assert (name);
|
|
gcc_assert (width);
|
|
|
|
/* compare with c/c-decl.c:grokfield, grokdeclarator and
|
|
check_bitfield_type_and_width. */
|
|
|
|
tree tree_type = type->as_tree ();
|
|
gcc_assert (INTEGRAL_TYPE_P (tree_type));
|
|
tree tree_width = build_int_cst (integer_type_node, width);
|
|
if (compare_tree_int (tree_width, TYPE_PRECISION (tree_type)) > 0)
|
|
{
|
|
add_error (
|
|
loc,
|
|
"width of bit-field %s (width: %i) is wider than its type (width: %i)",
|
|
name, width, TYPE_PRECISION (tree_type));
|
|
return NULL;
|
|
}
|
|
|
|
tree decl = build_decl (UNKNOWN_LOCATION, FIELD_DECL,
|
|
get_identifier (name), type->as_tree ());
|
|
DECL_NONADDRESSABLE_P (decl) = true;
|
|
DECL_INITIAL (decl) = tree_width;
|
|
SET_DECL_JIT_BIT_FIELD (decl);
|
|
|
|
if (loc)
|
|
set_tree_location (decl, loc);
|
|
|
|
return new field (decl);
|
|
}
|
|
|
|
/* Construct a playback::compound_type instance (wrapping a tree). */
|
|
|
|
playback::compound_type *
|
|
playback::context::
|
|
new_compound_type (location *loc,
|
|
const char *name,
|
|
bool is_struct) /* else is union */
|
|
{
|
|
gcc_assert (name);
|
|
|
|
/* Compare with c/c-decl.c: start_struct. */
|
|
|
|
tree t = make_node (is_struct ? RECORD_TYPE : UNION_TYPE);
|
|
TYPE_NAME (t) = get_identifier (name);
|
|
TYPE_SIZE (t) = 0;
|
|
|
|
if (loc)
|
|
set_tree_location (t, loc);
|
|
|
|
return new compound_type (t);
|
|
}
|
|
|
|
void
|
|
playback::compound_type::set_fields (const auto_vec<playback::field *> *fields)
|
|
{
|
|
/* Compare with c/c-decl.c: finish_struct. */
|
|
tree t = as_tree ();
|
|
|
|
tree fieldlist = NULL;
|
|
for (unsigned i = 0; i < fields->length (); i++)
|
|
{
|
|
field *f = (*fields)[i];
|
|
tree x = f->as_tree ();
|
|
DECL_CONTEXT (x) = t;
|
|
if (DECL_JIT_BIT_FIELD (x))
|
|
{
|
|
unsigned HOST_WIDE_INT width = tree_to_uhwi (DECL_INITIAL (x));
|
|
DECL_SIZE (x) = bitsize_int (width);
|
|
DECL_BIT_FIELD (x) = 1;
|
|
}
|
|
fieldlist = chainon (x, fieldlist);
|
|
}
|
|
fieldlist = nreverse (fieldlist);
|
|
TYPE_FIELDS (t) = fieldlist;
|
|
|
|
layout_type (t);
|
|
}
|
|
|
|
/* Construct a playback::type instance (wrapping a tree) for a function
|
|
type. */
|
|
|
|
playback::type *
|
|
playback::context::
|
|
new_function_type (type *return_type,
|
|
const auto_vec<type *> *param_types,
|
|
int is_variadic)
|
|
{
|
|
int i;
|
|
type *param_type;
|
|
|
|
tree *arg_types = (tree *)xcalloc(param_types->length (), sizeof(tree*));
|
|
|
|
FOR_EACH_VEC_ELT (*param_types, i, param_type)
|
|
arg_types[i] = param_type->as_tree ();
|
|
|
|
tree fn_type;
|
|
if (is_variadic)
|
|
fn_type =
|
|
build_varargs_function_type_array (return_type->as_tree (),
|
|
param_types->length (),
|
|
arg_types);
|
|
else
|
|
fn_type = build_function_type_array (return_type->as_tree (),
|
|
param_types->length (),
|
|
arg_types);
|
|
free (arg_types);
|
|
|
|
return new type (fn_type);
|
|
}
|
|
|
|
/* Construct a playback::param instance (wrapping a tree). */
|
|
|
|
playback::param *
|
|
playback::context::
|
|
new_param (location *loc,
|
|
type *type,
|
|
const char *name)
|
|
{
|
|
gcc_assert (type);
|
|
gcc_assert (name);
|
|
tree inner = build_decl (UNKNOWN_LOCATION, PARM_DECL,
|
|
get_identifier (name), type->as_tree ());
|
|
if (loc)
|
|
set_tree_location (inner, loc);
|
|
|
|
return new param (this, inner);
|
|
}
|
|
|
|
/* Construct a playback::function instance. */
|
|
|
|
playback::function *
|
|
playback::context::
|
|
new_function (location *loc,
|
|
enum gcc_jit_function_kind kind,
|
|
type *return_type,
|
|
const char *name,
|
|
const auto_vec<param *> *params,
|
|
int is_variadic,
|
|
enum built_in_function builtin_id)
|
|
{
|
|
int i;
|
|
param *param;
|
|
|
|
//can return_type be NULL?
|
|
gcc_assert (name);
|
|
|
|
tree *arg_types = (tree *)xcalloc(params->length (), sizeof(tree*));
|
|
FOR_EACH_VEC_ELT (*params, i, param)
|
|
arg_types[i] = TREE_TYPE (param->as_tree ());
|
|
|
|
tree fn_type;
|
|
if (is_variadic)
|
|
fn_type = build_varargs_function_type_array (return_type->as_tree (),
|
|
params->length (), arg_types);
|
|
else
|
|
fn_type = build_function_type_array (return_type->as_tree (),
|
|
params->length (), arg_types);
|
|
free (arg_types);
|
|
|
|
/* FIXME: this uses input_location: */
|
|
tree fndecl = build_fn_decl (name, fn_type);
|
|
|
|
if (loc)
|
|
set_tree_location (fndecl, loc);
|
|
|
|
tree resdecl = build_decl (UNKNOWN_LOCATION, RESULT_DECL,
|
|
NULL_TREE, return_type->as_tree ());
|
|
DECL_ARTIFICIAL (resdecl) = 1;
|
|
DECL_IGNORED_P (resdecl) = 1;
|
|
DECL_RESULT (fndecl) = resdecl;
|
|
DECL_CONTEXT (resdecl) = fndecl;
|
|
|
|
if (builtin_id)
|
|
{
|
|
gcc_assert (loc == NULL);
|
|
DECL_SOURCE_LOCATION (fndecl) = BUILTINS_LOCATION;
|
|
|
|
built_in_class fclass = builtins_manager::get_class (builtin_id);
|
|
set_decl_built_in_function (fndecl, fclass, builtin_id);
|
|
set_builtin_decl (builtin_id, fndecl,
|
|
builtins_manager::implicit_p (builtin_id));
|
|
|
|
builtins_manager *bm = get_builtins_manager ();
|
|
tree attrs = bm->get_attrs_tree (builtin_id);
|
|
if (attrs)
|
|
decl_attributes (&fndecl, attrs, ATTR_FLAG_BUILT_IN);
|
|
else
|
|
decl_attributes (&fndecl, NULL_TREE, 0);
|
|
}
|
|
|
|
if (kind != GCC_JIT_FUNCTION_IMPORTED)
|
|
{
|
|
tree param_decl_list = NULL;
|
|
FOR_EACH_VEC_ELT (*params, i, param)
|
|
{
|
|
param_decl_list = chainon (param->as_tree (), param_decl_list);
|
|
}
|
|
|
|
/* The param list was created in reverse order; fix it: */
|
|
param_decl_list = nreverse (param_decl_list);
|
|
|
|
tree t;
|
|
for (t = param_decl_list; t; t = DECL_CHAIN (t))
|
|
{
|
|
DECL_CONTEXT (t) = fndecl;
|
|
DECL_ARG_TYPE (t) = TREE_TYPE (t);
|
|
}
|
|
|
|
/* Set it up on DECL_ARGUMENTS */
|
|
DECL_ARGUMENTS(fndecl) = param_decl_list;
|
|
}
|
|
|
|
if (kind == GCC_JIT_FUNCTION_ALWAYS_INLINE)
|
|
{
|
|
DECL_DECLARED_INLINE_P (fndecl) = 1;
|
|
|
|
/* Add attribute "always_inline": */
|
|
DECL_ATTRIBUTES (fndecl) =
|
|
tree_cons (get_identifier ("always_inline"),
|
|
NULL,
|
|
DECL_ATTRIBUTES (fndecl));
|
|
}
|
|
|
|
function *func = new function (this, fndecl, kind);
|
|
m_functions.safe_push (func);
|
|
return func;
|
|
}
|
|
|
|
/* In use by new_global and new_global_initialized. */
|
|
|
|
tree
|
|
playback::context::
|
|
global_new_decl (location *loc,
|
|
enum gcc_jit_global_kind kind,
|
|
type *type,
|
|
const char *name)
|
|
{
|
|
gcc_assert (type);
|
|
gcc_assert (name);
|
|
tree inner = build_decl (UNKNOWN_LOCATION, VAR_DECL,
|
|
get_identifier (name),
|
|
type->as_tree ());
|
|
TREE_PUBLIC (inner) = (kind != GCC_JIT_GLOBAL_INTERNAL);
|
|
DECL_COMMON (inner) = 1;
|
|
switch (kind)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
|
|
case GCC_JIT_GLOBAL_EXPORTED:
|
|
TREE_STATIC (inner) = 1;
|
|
break;
|
|
|
|
case GCC_JIT_GLOBAL_INTERNAL:
|
|
TREE_STATIC (inner) = 1;
|
|
break;
|
|
|
|
case GCC_JIT_GLOBAL_IMPORTED:
|
|
DECL_EXTERNAL (inner) = 1;
|
|
break;
|
|
}
|
|
|
|
if (loc)
|
|
set_tree_location (inner, loc);
|
|
|
|
return inner;
|
|
}
|
|
|
|
/* In use by new_global and new_global_initialized. */
|
|
|
|
playback::lvalue *
|
|
playback::context::
|
|
global_finalize_lvalue (tree inner)
|
|
{
|
|
varpool_node::get_create (inner);
|
|
|
|
varpool_node::finalize_decl (inner);
|
|
|
|
m_globals.safe_push (inner);
|
|
|
|
return new lvalue (this, inner);
|
|
}
|
|
|
|
/* Construct a playback::lvalue instance (wrapping a tree). */
|
|
|
|
playback::lvalue *
|
|
playback::context::
|
|
new_global (location *loc,
|
|
enum gcc_jit_global_kind kind,
|
|
type *type,
|
|
const char *name)
|
|
{
|
|
tree inner = global_new_decl (loc, kind, type, name);
|
|
|
|
return global_finalize_lvalue (inner);
|
|
}
|
|
|
|
/* Fill 'constructor_elements' with the memory content of
|
|
'initializer'. Each element of the initializer is of the size of
|
|
type T. In use by new_global_initialized.*/
|
|
|
|
template<typename T>
|
|
static void
|
|
load_blob_in_ctor (vec<constructor_elt, va_gc> *&constructor_elements,
|
|
size_t num_elem,
|
|
const void *initializer)
|
|
{
|
|
/* Loosely based on 'output_init_element' c-typeck.c:9691. */
|
|
const T *p = (const T *)initializer;
|
|
tree node = make_unsigned_type (BITS_PER_UNIT * sizeof (T));
|
|
for (size_t i = 0; i < num_elem; i++)
|
|
{
|
|
constructor_elt celt =
|
|
{ build_int_cst (long_unsigned_type_node, i),
|
|
build_int_cst (node, p[i]) };
|
|
vec_safe_push (constructor_elements, celt);
|
|
}
|
|
}
|
|
|
|
/* Construct an initialized playback::lvalue instance (wrapping a
|
|
tree). */
|
|
|
|
playback::lvalue *
|
|
playback::context::
|
|
new_global_initialized (location *loc,
|
|
enum gcc_jit_global_kind kind,
|
|
type *type,
|
|
size_t element_size,
|
|
size_t initializer_num_elem,
|
|
const void *initializer,
|
|
const char *name)
|
|
{
|
|
tree inner = global_new_decl (loc, kind, type, name);
|
|
|
|
vec<constructor_elt, va_gc> *constructor_elements = NULL;
|
|
|
|
switch (element_size)
|
|
{
|
|
case 1:
|
|
load_blob_in_ctor<uint8_t> (constructor_elements, initializer_num_elem,
|
|
initializer);
|
|
break;
|
|
case 2:
|
|
load_blob_in_ctor<uint16_t> (constructor_elements, initializer_num_elem,
|
|
initializer);
|
|
break;
|
|
case 4:
|
|
load_blob_in_ctor<uint32_t> (constructor_elements, initializer_num_elem,
|
|
initializer);
|
|
break;
|
|
case 8:
|
|
load_blob_in_ctor<uint64_t> (constructor_elements, initializer_num_elem,
|
|
initializer);
|
|
break;
|
|
default:
|
|
/* This function is serving on sizes returned by 'get_size',
|
|
these are all covered by the previous cases. */
|
|
gcc_unreachable ();
|
|
}
|
|
/* Compare with 'pop_init_level' c-typeck.c:8780. */
|
|
tree ctor = build_constructor (type->as_tree (), constructor_elements);
|
|
constructor_elements = NULL;
|
|
|
|
/* Compare with 'store_init_value' c-typeck.c:7555. */
|
|
DECL_INITIAL (inner) = ctor;
|
|
|
|
return global_finalize_lvalue (inner);
|
|
}
|
|
|
|
/* Implementation of the various
|
|
gcc::jit::playback::context::new_rvalue_from_const <HOST_TYPE>
|
|
methods.
|
|
Each of these constructs a playback::rvalue instance (wrapping a tree).
|
|
|
|
These specializations are required to be in the same namespace
|
|
as the template, hence we now have to enter the gcc::jit::playback
|
|
namespace. */
|
|
|
|
namespace playback
|
|
{
|
|
|
|
/* Specialization of making an rvalue from a const, for host <int>. */
|
|
|
|
template <>
|
|
rvalue *
|
|
context::
|
|
new_rvalue_from_const <int> (type *type,
|
|
int value)
|
|
{
|
|
// FIXME: type-checking, or coercion?
|
|
tree inner_type = type->as_tree ();
|
|
if (INTEGRAL_TYPE_P (inner_type))
|
|
{
|
|
tree inner = build_int_cst (inner_type, value);
|
|
return new rvalue (this, inner);
|
|
}
|
|
else
|
|
{
|
|
REAL_VALUE_TYPE real_value;
|
|
real_from_integer (&real_value, VOIDmode, value, SIGNED);
|
|
tree inner = build_real (inner_type, real_value);
|
|
return new rvalue (this, inner);
|
|
}
|
|
}
|
|
|
|
/* Specialization of making an rvalue from a const, for host <long>. */
|
|
|
|
template <>
|
|
rvalue *
|
|
context::
|
|
new_rvalue_from_const <long> (type *type,
|
|
long value)
|
|
{
|
|
// FIXME: type-checking, or coercion?
|
|
tree inner_type = type->as_tree ();
|
|
if (INTEGRAL_TYPE_P (inner_type))
|
|
{
|
|
tree inner = build_int_cst (inner_type, value);
|
|
return new rvalue (this, inner);
|
|
}
|
|
else
|
|
{
|
|
REAL_VALUE_TYPE real_value;
|
|
real_from_integer (&real_value, VOIDmode, value, SIGNED);
|
|
tree inner = build_real (inner_type, real_value);
|
|
return new rvalue (this, inner);
|
|
}
|
|
}
|
|
|
|
/* Specialization of making an rvalue from a const, for host <double>. */
|
|
|
|
template <>
|
|
rvalue *
|
|
context::
|
|
new_rvalue_from_const <double> (type *type,
|
|
double value)
|
|
{
|
|
// FIXME: type-checking, or coercion?
|
|
tree inner_type = type->as_tree ();
|
|
|
|
/* We have a "double", we want a REAL_VALUE_TYPE.
|
|
|
|
real.c:real_from_target appears to require the representation to be
|
|
split into 32-bit values, and then sent as an pair of host long
|
|
ints. */
|
|
REAL_VALUE_TYPE real_value;
|
|
union
|
|
{
|
|
double as_double;
|
|
uint32_t as_uint32s[2];
|
|
} u;
|
|
u.as_double = value;
|
|
long int as_long_ints[2];
|
|
as_long_ints[0] = u.as_uint32s[0];
|
|
as_long_ints[1] = u.as_uint32s[1];
|
|
real_from_target (&real_value, as_long_ints, DFmode);
|
|
tree inner = build_real (inner_type, real_value);
|
|
return new rvalue (this, inner);
|
|
}
|
|
|
|
/* Specialization of making an rvalue from a const, for host <void *>. */
|
|
|
|
template <>
|
|
rvalue *
|
|
context::
|
|
new_rvalue_from_const <void *> (type *type,
|
|
void *value)
|
|
{
|
|
tree inner_type = type->as_tree ();
|
|
/* FIXME: how to ensure we have a wide enough type? */
|
|
tree inner = build_int_cstu (inner_type, (unsigned HOST_WIDE_INT)value);
|
|
return new rvalue (this, inner);
|
|
}
|
|
|
|
/* We're done implementing the specializations of
|
|
gcc::jit::playback::context::new_rvalue_from_const <T>
|
|
so we can exit the gcc::jit::playback namespace. */
|
|
|
|
} // namespace playback
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree). */
|
|
|
|
playback::rvalue *
|
|
playback::context::
|
|
new_string_literal (const char *value)
|
|
{
|
|
/* Compare with c-family/c-common.c: fix_string_type. */
|
|
size_t len = strlen (value);
|
|
tree i_type = build_index_type (size_int (len));
|
|
tree a_type = build_array_type (char_type_node, i_type);
|
|
/* build_string len parameter must include NUL terminator when
|
|
building C strings. */
|
|
tree t_str = ::build_string (len + 1, value);
|
|
TREE_TYPE (t_str) = a_type;
|
|
|
|
/* Convert to (const char*), loosely based on
|
|
c/c-typeck.c: array_to_pointer_conversion,
|
|
by taking address of start of string. */
|
|
tree t_addr = build1 (ADDR_EXPR, m_const_char_ptr, t_str);
|
|
|
|
return new rvalue (this, t_addr);
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for a
|
|
vector. */
|
|
|
|
playback::rvalue *
|
|
playback::context::new_rvalue_from_vector (location *,
|
|
type *type,
|
|
const auto_vec<rvalue *> &elements)
|
|
{
|
|
vec<constructor_elt, va_gc> *v;
|
|
vec_alloc (v, elements.length ());
|
|
for (unsigned i = 0; i < elements.length (); ++i)
|
|
CONSTRUCTOR_APPEND_ELT (v, NULL_TREE, elements[i]->as_tree ());
|
|
tree t_ctor = build_constructor (type->as_tree (), v);
|
|
return new rvalue (this, t_ctor);
|
|
}
|
|
|
|
/* Coerce a tree expression into a boolean tree expression. */
|
|
|
|
tree
|
|
playback::context::
|
|
as_truth_value (tree expr, location *loc)
|
|
{
|
|
/* Compare to c-typeck.c:c_objc_common_truthvalue_conversion */
|
|
tree typed_zero = fold_build1 (CONVERT_EXPR,
|
|
TREE_TYPE (expr),
|
|
integer_zero_node);
|
|
if (loc)
|
|
set_tree_location (typed_zero, loc);
|
|
|
|
expr = build2 (NE_EXPR, integer_type_node, expr, typed_zero);
|
|
if (loc)
|
|
set_tree_location (expr, loc);
|
|
|
|
return expr;
|
|
}
|
|
|
|
/* Add a "top-level" basic asm statement (i.e. one outside of any functions)
|
|
containing ASM_STMTS.
|
|
|
|
Compare with c_parser_asm_definition. */
|
|
|
|
void
|
|
playback::context::add_top_level_asm (const char *asm_stmts)
|
|
{
|
|
tree asm_str = build_string (asm_stmts);
|
|
symtab->finalize_toplevel_asm (asm_str);
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for a
|
|
unary op. */
|
|
|
|
playback::rvalue *
|
|
playback::context::
|
|
new_unary_op (location *loc,
|
|
enum gcc_jit_unary_op op,
|
|
type *result_type,
|
|
rvalue *a)
|
|
{
|
|
// FIXME: type-checking, or coercion?
|
|
enum tree_code inner_op;
|
|
|
|
gcc_assert (result_type);
|
|
gcc_assert (a);
|
|
|
|
tree node = a->as_tree ();
|
|
tree inner_result = NULL;
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
add_error (loc, "unrecognized (enum gcc_jit_unary_op) value: %i", op);
|
|
return NULL;
|
|
|
|
case GCC_JIT_UNARY_OP_MINUS:
|
|
inner_op = NEGATE_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_UNARY_OP_BITWISE_NEGATE:
|
|
inner_op = BIT_NOT_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_UNARY_OP_LOGICAL_NEGATE:
|
|
node = as_truth_value (node, loc);
|
|
inner_result = invert_truthvalue (node);
|
|
if (loc)
|
|
set_tree_location (inner_result, loc);
|
|
return new rvalue (this, inner_result);
|
|
|
|
case GCC_JIT_UNARY_OP_ABS:
|
|
inner_op = ABS_EXPR;
|
|
break;
|
|
}
|
|
|
|
inner_result = build1 (inner_op,
|
|
result_type->as_tree (),
|
|
node);
|
|
if (loc)
|
|
set_tree_location (inner_result, loc);
|
|
|
|
return new rvalue (this, inner_result);
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for a
|
|
binary op. */
|
|
|
|
playback::rvalue *
|
|
playback::context::
|
|
new_binary_op (location *loc,
|
|
enum gcc_jit_binary_op op,
|
|
type *result_type,
|
|
rvalue *a, rvalue *b)
|
|
{
|
|
// FIXME: type-checking, or coercion?
|
|
enum tree_code inner_op;
|
|
|
|
gcc_assert (result_type);
|
|
gcc_assert (a);
|
|
gcc_assert (b);
|
|
|
|
tree node_a = a->as_tree ();
|
|
tree node_b = b->as_tree ();
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
add_error (loc, "unrecognized (enum gcc_jit_binary_op) value: %i", op);
|
|
return NULL;
|
|
|
|
case GCC_JIT_BINARY_OP_PLUS:
|
|
inner_op = PLUS_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_MINUS:
|
|
inner_op = MINUS_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_MULT:
|
|
inner_op = MULT_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_DIVIDE:
|
|
if (FLOAT_TYPE_P (result_type->as_tree ()))
|
|
/* Floating-point division: */
|
|
inner_op = RDIV_EXPR;
|
|
else
|
|
/* Truncating to zero: */
|
|
inner_op = TRUNC_DIV_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_MODULO:
|
|
inner_op = TRUNC_MOD_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_BITWISE_AND:
|
|
inner_op = BIT_AND_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_BITWISE_XOR:
|
|
inner_op = BIT_XOR_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_BITWISE_OR:
|
|
inner_op = BIT_IOR_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_LOGICAL_AND:
|
|
node_a = as_truth_value (node_a, loc);
|
|
node_b = as_truth_value (node_b, loc);
|
|
inner_op = TRUTH_ANDIF_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_LOGICAL_OR:
|
|
node_a = as_truth_value (node_a, loc);
|
|
node_b = as_truth_value (node_b, loc);
|
|
inner_op = TRUTH_ORIF_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_LSHIFT:
|
|
inner_op = LSHIFT_EXPR;
|
|
break;
|
|
|
|
case GCC_JIT_BINARY_OP_RSHIFT:
|
|
inner_op = RSHIFT_EXPR;
|
|
break;
|
|
}
|
|
|
|
tree inner_expr = build2 (inner_op,
|
|
result_type->as_tree (),
|
|
node_a,
|
|
node_b);
|
|
if (loc)
|
|
set_tree_location (inner_expr, loc);
|
|
|
|
return new rvalue (this, inner_expr);
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for a
|
|
comparison. */
|
|
|
|
playback::rvalue *
|
|
playback::context::
|
|
new_comparison (location *loc,
|
|
enum gcc_jit_comparison op,
|
|
rvalue *a, rvalue *b)
|
|
{
|
|
// FIXME: type-checking, or coercion?
|
|
enum tree_code inner_op;
|
|
|
|
gcc_assert (a);
|
|
gcc_assert (b);
|
|
|
|
switch (op)
|
|
{
|
|
default:
|
|
add_error (loc, "unrecognized (enum gcc_jit_comparison) value: %i", op);
|
|
return NULL;
|
|
|
|
case GCC_JIT_COMPARISON_EQ:
|
|
inner_op = EQ_EXPR;
|
|
break;
|
|
case GCC_JIT_COMPARISON_NE:
|
|
inner_op = NE_EXPR;
|
|
break;
|
|
case GCC_JIT_COMPARISON_LT:
|
|
inner_op = LT_EXPR;
|
|
break;
|
|
case GCC_JIT_COMPARISON_LE:
|
|
inner_op = LE_EXPR;
|
|
break;
|
|
case GCC_JIT_COMPARISON_GT:
|
|
inner_op = GT_EXPR;
|
|
break;
|
|
case GCC_JIT_COMPARISON_GE:
|
|
inner_op = GE_EXPR;
|
|
break;
|
|
}
|
|
|
|
tree inner_expr = build2 (inner_op,
|
|
boolean_type_node,
|
|
a->as_tree (),
|
|
b->as_tree ());
|
|
if (loc)
|
|
set_tree_location (inner_expr, loc);
|
|
return new rvalue (this, inner_expr);
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for a
|
|
function call. */
|
|
|
|
playback::rvalue *
|
|
playback::context::
|
|
build_call (location *loc,
|
|
tree fn_ptr,
|
|
const auto_vec<rvalue *> *args,
|
|
bool require_tail_call)
|
|
{
|
|
vec<tree, va_gc> *tree_args;
|
|
vec_alloc (tree_args, args->length ());
|
|
for (unsigned i = 0; i < args->length (); i++)
|
|
tree_args->quick_push ((*args)[i]->as_tree ());
|
|
|
|
if (loc)
|
|
set_tree_location (fn_ptr, loc);
|
|
|
|
tree fn = TREE_TYPE (fn_ptr);
|
|
tree fn_type = TREE_TYPE (fn);
|
|
tree return_type = TREE_TYPE (fn_type);
|
|
|
|
tree call = build_call_vec (return_type,
|
|
fn_ptr, tree_args);
|
|
|
|
if (require_tail_call)
|
|
CALL_EXPR_MUST_TAIL_CALL (call) = 1;
|
|
|
|
return new rvalue (this, call);
|
|
|
|
/* see c-typeck.c: build_function_call
|
|
which calls build_function_call_vec
|
|
|
|
which does lots of checking, then:
|
|
result = build_call_array_loc (loc, TREE_TYPE (fntype),
|
|
function, nargs, argarray);
|
|
which is in tree.c
|
|
(see also build_call_vec)
|
|
*/
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for a
|
|
call to a specific function. */
|
|
|
|
playback::rvalue *
|
|
playback::context::
|
|
new_call (location *loc,
|
|
function *func,
|
|
const auto_vec<rvalue *> *args,
|
|
bool require_tail_call)
|
|
{
|
|
tree fndecl;
|
|
|
|
gcc_assert (func);
|
|
|
|
fndecl = func->as_fndecl ();
|
|
|
|
tree fntype = TREE_TYPE (fndecl);
|
|
|
|
tree fn = build1 (ADDR_EXPR, build_pointer_type (fntype), fndecl);
|
|
|
|
return build_call (loc, fn, args, require_tail_call);
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for a
|
|
call through a function pointer. */
|
|
|
|
playback::rvalue *
|
|
playback::context::
|
|
new_call_through_ptr (location *loc,
|
|
rvalue *fn_ptr,
|
|
const auto_vec<rvalue *> *args,
|
|
bool require_tail_call)
|
|
{
|
|
gcc_assert (fn_ptr);
|
|
tree t_fn_ptr = fn_ptr->as_tree ();
|
|
|
|
return build_call (loc, t_fn_ptr, args, require_tail_call);
|
|
}
|
|
|
|
/* Construct a tree for a cast. */
|
|
|
|
tree
|
|
playback::context::build_cast (playback::location *loc,
|
|
playback::rvalue *expr,
|
|
playback::type *type_)
|
|
{
|
|
/* For comparison, see:
|
|
- c/c-typeck.c:build_c_cast
|
|
- c/c-convert.c: convert
|
|
- convert.h
|
|
|
|
Only some kinds of cast are currently supported here. */
|
|
tree t_expr = expr->as_tree ();
|
|
tree t_dst_type = type_->as_tree ();
|
|
tree t_ret = NULL;
|
|
t_ret = targetm.convert_to_type (t_dst_type, t_expr);
|
|
if (t_ret)
|
|
return t_ret;
|
|
enum tree_code dst_code = TREE_CODE (t_dst_type);
|
|
switch (dst_code)
|
|
{
|
|
case INTEGER_TYPE:
|
|
case ENUMERAL_TYPE:
|
|
t_ret = convert_to_integer (t_dst_type, t_expr);
|
|
goto maybe_fold;
|
|
|
|
case BOOLEAN_TYPE:
|
|
/* Compare with c_objc_common_truthvalue_conversion and
|
|
c_common_truthvalue_conversion. */
|
|
/* For now, convert to: (t_expr != 0) */
|
|
t_ret = build2 (NE_EXPR, t_dst_type,
|
|
t_expr,
|
|
build_int_cst (TREE_TYPE (t_expr), 0));
|
|
goto maybe_fold;
|
|
|
|
case REAL_TYPE:
|
|
t_ret = convert_to_real (t_dst_type, t_expr);
|
|
goto maybe_fold;
|
|
|
|
case POINTER_TYPE:
|
|
t_ret = build1 (NOP_EXPR, t_dst_type, t_expr);
|
|
goto maybe_fold;
|
|
|
|
default:
|
|
add_error (loc, "couldn't handle cast during playback");
|
|
fprintf (stderr, "input expression:\n");
|
|
debug_tree (t_expr);
|
|
fprintf (stderr, "requested type:\n");
|
|
debug_tree (t_dst_type);
|
|
return error_mark_node;
|
|
|
|
maybe_fold:
|
|
if (TREE_CODE (t_ret) != C_MAYBE_CONST_EXPR)
|
|
t_ret = fold (t_ret);
|
|
return t_ret;
|
|
}
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for a
|
|
cast. */
|
|
|
|
playback::rvalue *
|
|
playback::context::
|
|
new_cast (playback::location *loc,
|
|
playback::rvalue *expr,
|
|
playback::type *type_)
|
|
{
|
|
|
|
tree t_cast = build_cast (loc, expr, type_);
|
|
if (loc)
|
|
set_tree_location (t_cast, loc);
|
|
return new rvalue (this, t_cast);
|
|
}
|
|
|
|
/* Construct a playback::lvalue instance (wrapping a tree) for an
|
|
array access. */
|
|
|
|
playback::lvalue *
|
|
playback::context::
|
|
new_array_access (location *loc,
|
|
rvalue *ptr,
|
|
rvalue *index)
|
|
{
|
|
gcc_assert (ptr);
|
|
gcc_assert (index);
|
|
|
|
/* For comparison, see:
|
|
c/c-typeck.c: build_array_ref
|
|
c-family/c-common.c: pointer_int_sum
|
|
*/
|
|
tree t_ptr = ptr->as_tree ();
|
|
tree t_index = index->as_tree ();
|
|
tree t_type_ptr = TREE_TYPE (t_ptr);
|
|
tree t_type_star_ptr = TREE_TYPE (t_type_ptr);
|
|
|
|
if (TREE_CODE (t_type_ptr) == ARRAY_TYPE)
|
|
{
|
|
tree t_result = build4 (ARRAY_REF, t_type_star_ptr, t_ptr, t_index,
|
|
NULL_TREE, NULL_TREE);
|
|
if (loc)
|
|
set_tree_location (t_result, loc);
|
|
return new lvalue (this, t_result);
|
|
}
|
|
else
|
|
{
|
|
/* Convert index to an offset in bytes. */
|
|
tree t_sizeof = size_in_bytes (t_type_star_ptr);
|
|
t_index = fold_build1 (CONVERT_EXPR, sizetype, t_index);
|
|
tree t_offset = build2 (MULT_EXPR, sizetype, t_index, t_sizeof);
|
|
|
|
/* Locate (ptr + offset). */
|
|
tree t_address = build2 (POINTER_PLUS_EXPR, t_type_ptr, t_ptr, t_offset);
|
|
|
|
tree t_indirection = build1 (INDIRECT_REF, t_type_star_ptr, t_address);
|
|
if (loc)
|
|
{
|
|
set_tree_location (t_sizeof, loc);
|
|
set_tree_location (t_offset, loc);
|
|
set_tree_location (t_address, loc);
|
|
set_tree_location (t_indirection, loc);
|
|
}
|
|
|
|
return new lvalue (this, t_indirection);
|
|
}
|
|
}
|
|
|
|
/* Construct a tree for a field access. */
|
|
|
|
tree
|
|
playback::context::
|
|
new_field_access (location *loc,
|
|
tree datum,
|
|
field *field)
|
|
{
|
|
gcc_assert (datum);
|
|
gcc_assert (field);
|
|
|
|
/* Compare with c/c-typeck.c:lookup_field, build_indirect_ref, and
|
|
build_component_ref. */
|
|
tree type = TREE_TYPE (datum);
|
|
gcc_assert (type);
|
|
gcc_assert (TREE_CODE (type) != POINTER_TYPE);
|
|
|
|
tree t_field = field->as_tree ();
|
|
tree ref = build3 (COMPONENT_REF, TREE_TYPE (t_field), datum,
|
|
t_field, NULL_TREE);
|
|
if (loc)
|
|
set_tree_location (ref, loc);
|
|
return ref;
|
|
}
|
|
|
|
/* Construct a tree for a dereference. */
|
|
|
|
tree
|
|
playback::context::
|
|
new_dereference (tree ptr,
|
|
location *loc)
|
|
{
|
|
gcc_assert (ptr);
|
|
|
|
tree type = TREE_TYPE (TREE_TYPE(ptr));
|
|
tree datum = build1 (INDIRECT_REF, type, ptr);
|
|
if (loc)
|
|
set_tree_location (datum, loc);
|
|
return datum;
|
|
}
|
|
|
|
/* Construct a playback::type instance (wrapping a tree)
|
|
with the given alignment. */
|
|
|
|
playback::type *
|
|
playback::type::
|
|
get_aligned (size_t alignment_in_bytes) const
|
|
{
|
|
tree t_new_type = build_variant_type_copy (m_inner);
|
|
|
|
SET_TYPE_ALIGN (t_new_type, alignment_in_bytes * BITS_PER_UNIT);
|
|
TYPE_USER_ALIGN (t_new_type) = 1;
|
|
|
|
return new type (t_new_type);
|
|
}
|
|
|
|
/* Construct a playback::type instance (wrapping a tree)
|
|
for the given vector type. */
|
|
|
|
playback::type *
|
|
playback::type::
|
|
get_vector (size_t num_units) const
|
|
{
|
|
tree t_new_type = build_vector_type (m_inner, num_units);
|
|
return new type (t_new_type);
|
|
}
|
|
|
|
/* Construct a playback::lvalue instance (wrapping a tree) for a
|
|
field access. */
|
|
|
|
playback::lvalue *
|
|
playback::lvalue::
|
|
access_field (location *loc,
|
|
field *field)
|
|
{
|
|
tree datum = as_tree ();
|
|
tree ref = get_context ()->new_field_access (loc, datum, field);
|
|
if (!ref)
|
|
return NULL;
|
|
return new lvalue (get_context (), ref);
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for a
|
|
field access. */
|
|
|
|
playback::rvalue *
|
|
playback::rvalue::
|
|
access_field (location *loc,
|
|
field *field)
|
|
{
|
|
tree datum = as_tree ();
|
|
tree ref = get_context ()->new_field_access (loc, datum, field);
|
|
if (!ref)
|
|
return NULL;
|
|
return new rvalue (get_context (), ref);
|
|
}
|
|
|
|
/* Construct a playback::lvalue instance (wrapping a tree) for a
|
|
dereferenced field access. */
|
|
|
|
playback::lvalue *
|
|
playback::rvalue::
|
|
dereference_field (location *loc,
|
|
field *field)
|
|
{
|
|
tree ptr = as_tree ();
|
|
tree datum = get_context ()->new_dereference (ptr, loc);
|
|
if (!datum)
|
|
return NULL;
|
|
tree ref = get_context ()->new_field_access (loc, datum, field);
|
|
if (!ref)
|
|
return NULL;
|
|
return new lvalue (get_context (), ref);
|
|
}
|
|
|
|
/* Construct a playback::lvalue instance (wrapping a tree) for a
|
|
dereference. */
|
|
|
|
playback::lvalue *
|
|
playback::rvalue::
|
|
dereference (location *loc)
|
|
{
|
|
tree ptr = as_tree ();
|
|
tree datum = get_context ()->new_dereference (ptr, loc);
|
|
return new lvalue (get_context (), datum);
|
|
}
|
|
|
|
/* Mark the lvalue saying that we need to be able to take the
|
|
address of it; it should not be allocated in a register.
|
|
Compare with e.g. c/c-typeck.c: c_mark_addressable really_atomic_lvalue.
|
|
Returns false if a failure occurred (an error will already have been
|
|
added to the active context for this case). */
|
|
|
|
bool
|
|
playback::lvalue::
|
|
mark_addressable (location *loc)
|
|
{
|
|
tree x = as_tree ();;
|
|
|
|
while (1)
|
|
switch (TREE_CODE (x))
|
|
{
|
|
case COMPONENT_REF:
|
|
if (DECL_JIT_BIT_FIELD (TREE_OPERAND (x, 1)))
|
|
{
|
|
gcc_assert (gcc::jit::active_playback_ctxt);
|
|
gcc::jit::
|
|
active_playback_ctxt->add_error (loc,
|
|
"cannot take address of "
|
|
"bit-field");
|
|
return false;
|
|
}
|
|
/* fallthrough */
|
|
case ADDR_EXPR:
|
|
case ARRAY_REF:
|
|
case REALPART_EXPR:
|
|
case IMAGPART_EXPR:
|
|
x = TREE_OPERAND (x, 0);
|
|
break;
|
|
|
|
case COMPOUND_LITERAL_EXPR:
|
|
case CONSTRUCTOR:
|
|
TREE_ADDRESSABLE (x) = 1;
|
|
return true;
|
|
|
|
case VAR_DECL:
|
|
case CONST_DECL:
|
|
case PARM_DECL:
|
|
case RESULT_DECL:
|
|
/* (we don't have a concept of a "register" declaration) */
|
|
/* fallthrough */
|
|
case FUNCTION_DECL:
|
|
TREE_ADDRESSABLE (x) = 1;
|
|
/* fallthrough */
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance (wrapping a tree) for an
|
|
address-lookup. */
|
|
|
|
playback::rvalue *
|
|
playback::lvalue::
|
|
get_address (location *loc)
|
|
{
|
|
tree t_lvalue = as_tree ();
|
|
tree t_thistype = TREE_TYPE (t_lvalue);
|
|
tree t_ptrtype = build_pointer_type (t_thistype);
|
|
tree ptr = build1 (ADDR_EXPR, t_ptrtype, t_lvalue);
|
|
if (loc)
|
|
get_context ()->set_tree_location (ptr, loc);
|
|
if (mark_addressable (loc))
|
|
return new rvalue (get_context (), ptr);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/* The wrapper subclasses are GC-managed, but can own non-GC memory.
|
|
Provide this finalization hook for calling then they are collected,
|
|
which calls the finalizer vfunc. This allows them to call "release"
|
|
on any vec<> within them. */
|
|
|
|
static void
|
|
wrapper_finalizer (void *ptr)
|
|
{
|
|
playback::wrapper *wrapper = reinterpret_cast <playback::wrapper *> (ptr);
|
|
wrapper->finalizer ();
|
|
}
|
|
|
|
/* gcc::jit::playback::wrapper subclasses are GC-managed:
|
|
allocate them using ggc_internal_cleared_alloc. */
|
|
|
|
void *
|
|
playback::wrapper::
|
|
operator new (size_t sz)
|
|
{
|
|
return ggc_internal_cleared_alloc (sz, wrapper_finalizer, 0, 1);
|
|
|
|
}
|
|
|
|
/* Constructor for gcc:jit::playback::function. */
|
|
|
|
playback::function::
|
|
function (context *ctxt,
|
|
tree fndecl,
|
|
enum gcc_jit_function_kind kind)
|
|
: m_ctxt(ctxt),
|
|
m_inner_fndecl (fndecl),
|
|
m_inner_bind_expr (NULL),
|
|
m_kind (kind),
|
|
m_blocks ()
|
|
{
|
|
if (m_kind != GCC_JIT_FUNCTION_IMPORTED)
|
|
{
|
|
/* Create a BIND_EXPR, and within it, a statement list. */
|
|
m_stmt_list = alloc_stmt_list ();
|
|
m_stmt_iter = tsi_start (m_stmt_list);
|
|
m_inner_block = make_node (BLOCK);
|
|
m_inner_bind_expr =
|
|
build3 (BIND_EXPR, void_type_node, NULL, m_stmt_list, m_inner_block);
|
|
}
|
|
else
|
|
{
|
|
m_inner_block = NULL;
|
|
m_stmt_list = NULL;
|
|
}
|
|
}
|
|
|
|
/* Hand-written GC-marking hook for playback functions. */
|
|
|
|
void
|
|
playback::function::
|
|
gt_ggc_mx ()
|
|
{
|
|
gt_ggc_m_9tree_node (m_inner_fndecl);
|
|
gt_ggc_m_9tree_node (m_inner_bind_expr);
|
|
gt_ggc_m_9tree_node (m_stmt_list);
|
|
gt_ggc_m_9tree_node (m_inner_block);
|
|
}
|
|
|
|
/* Don't leak vec's internal buffer (in non-GC heap) when we are
|
|
GC-ed. */
|
|
|
|
void
|
|
playback::function::finalizer ()
|
|
{
|
|
m_blocks.release ();
|
|
}
|
|
|
|
/* Get the return type of a playback function, in tree form. */
|
|
|
|
tree
|
|
playback::function::
|
|
get_return_type_as_tree () const
|
|
{
|
|
return TREE_TYPE (TREE_TYPE(m_inner_fndecl));
|
|
}
|
|
|
|
/* Construct a new local within this playback::function. */
|
|
|
|
playback::lvalue *
|
|
playback::function::
|
|
new_local (location *loc,
|
|
type *type,
|
|
const char *name)
|
|
{
|
|
gcc_assert (type);
|
|
gcc_assert (name);
|
|
tree inner = build_decl (UNKNOWN_LOCATION, VAR_DECL,
|
|
get_identifier (name),
|
|
type->as_tree ());
|
|
DECL_CONTEXT (inner) = this->m_inner_fndecl;
|
|
|
|
/* Prepend to BIND_EXPR_VARS: */
|
|
DECL_CHAIN (inner) = BIND_EXPR_VARS (m_inner_bind_expr);
|
|
BIND_EXPR_VARS (m_inner_bind_expr) = inner;
|
|
|
|
if (loc)
|
|
set_tree_location (inner, loc);
|
|
return new lvalue (m_ctxt, inner);
|
|
}
|
|
|
|
/* Construct a new block within this playback::function. */
|
|
|
|
playback::block *
|
|
playback::function::
|
|
new_block (const char *name)
|
|
{
|
|
gcc_assert (m_kind != GCC_JIT_FUNCTION_IMPORTED);
|
|
|
|
block *result = new playback::block (this, name);
|
|
m_blocks.safe_push (result);
|
|
return result;
|
|
}
|
|
|
|
/* Construct a playback::rvalue instance wrapping an ADDR_EXPR for
|
|
this playback::function. */
|
|
|
|
playback::rvalue *
|
|
playback::function::get_address (location *loc)
|
|
{
|
|
tree t_fndecl = as_fndecl ();
|
|
tree t_fntype = TREE_TYPE (t_fndecl);
|
|
tree t_fnptr = build1 (ADDR_EXPR, build_pointer_type (t_fntype), t_fndecl);
|
|
if (loc)
|
|
m_ctxt->set_tree_location (t_fnptr, loc);
|
|
return new rvalue (m_ctxt, t_fnptr);
|
|
}
|
|
|
|
/* Build a statement list for the function as a whole out of the
|
|
lists of statements for the individual blocks, building labels
|
|
for each block. */
|
|
|
|
void
|
|
playback::function::
|
|
build_stmt_list ()
|
|
{
|
|
int i;
|
|
block *b;
|
|
|
|
JIT_LOG_SCOPE (m_ctxt->get_logger ());
|
|
|
|
FOR_EACH_VEC_ELT (m_blocks, i, b)
|
|
{
|
|
int j;
|
|
tree stmt;
|
|
|
|
b->m_label_expr = build1 (LABEL_EXPR,
|
|
void_type_node,
|
|
b->as_label_decl ());
|
|
tsi_link_after (&m_stmt_iter, b->m_label_expr, TSI_CONTINUE_LINKING);
|
|
|
|
FOR_EACH_VEC_ELT (b->m_stmts, j, stmt)
|
|
tsi_link_after (&m_stmt_iter, stmt, TSI_CONTINUE_LINKING);
|
|
}
|
|
}
|
|
|
|
/* Finish compiling the given function, potentially running the
|
|
garbage-collector.
|
|
The function will have a statement list by now.
|
|
Amongst other things, this gimplifies the statement list,
|
|
and calls cgraph_node::finalize_function on the function. */
|
|
|
|
void
|
|
playback::function::
|
|
postprocess ()
|
|
{
|
|
JIT_LOG_SCOPE (m_ctxt->get_logger ());
|
|
|
|
if (m_ctxt->get_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_TREE))
|
|
debug_tree (m_stmt_list);
|
|
|
|
/* Do we need this to force cgraphunit.c to output the function? */
|
|
if (m_kind == GCC_JIT_FUNCTION_EXPORTED)
|
|
{
|
|
DECL_EXTERNAL (m_inner_fndecl) = 0;
|
|
DECL_PRESERVE_P (m_inner_fndecl) = 1;
|
|
}
|
|
|
|
if (m_kind == GCC_JIT_FUNCTION_INTERNAL
|
|
||m_kind == GCC_JIT_FUNCTION_ALWAYS_INLINE)
|
|
{
|
|
DECL_EXTERNAL (m_inner_fndecl) = 0;
|
|
TREE_PUBLIC (m_inner_fndecl) = 0;
|
|
}
|
|
|
|
if (m_kind != GCC_JIT_FUNCTION_IMPORTED)
|
|
{
|
|
/* Seem to need this in gimple-low.c: */
|
|
gcc_assert (m_inner_block);
|
|
DECL_INITIAL (m_inner_fndecl) = m_inner_block;
|
|
|
|
/* how to add to function? the following appears to be how to
|
|
set the body of a m_inner_fndecl: */
|
|
DECL_SAVED_TREE(m_inner_fndecl) = m_inner_bind_expr;
|
|
|
|
/* Ensure that locals appear in the debuginfo. */
|
|
BLOCK_VARS (m_inner_block) = BIND_EXPR_VARS (m_inner_bind_expr);
|
|
|
|
//debug_tree (m_inner_fndecl);
|
|
|
|
/* Convert to gimple: */
|
|
//printf("about to gimplify_function_tree\n");
|
|
gimplify_function_tree (m_inner_fndecl);
|
|
//printf("finished gimplify_function_tree\n");
|
|
|
|
current_function_decl = m_inner_fndecl;
|
|
if (m_ctxt->get_bool_option (GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE))
|
|
dump_function_to_file (m_inner_fndecl, stderr, TDF_VOPS|TDF_MEMSYMS|TDF_LINENO);
|
|
//debug_tree (m_inner_fndecl);
|
|
|
|
//printf("about to add to cgraph\n");
|
|
/* Add to cgraph: */
|
|
cgraph_node::finalize_function (m_inner_fndecl, false);
|
|
/* This can trigger a collection, so we need to have all of
|
|
the funcs as roots. */
|
|
|
|
current_function_decl = NULL;
|
|
}
|
|
}
|
|
|
|
/* Don't leak vec's internal buffer (in non-GC heap) when we are
|
|
GC-ed. */
|
|
|
|
void
|
|
playback::block::finalizer ()
|
|
{
|
|
m_stmts.release ();
|
|
}
|
|
|
|
/* Add an eval of the rvalue to the function's statement list. */
|
|
|
|
void
|
|
playback::block::
|
|
add_eval (location *loc,
|
|
rvalue *rvalue)
|
|
{
|
|
gcc_assert (rvalue);
|
|
|
|
if (loc)
|
|
set_tree_location (rvalue->as_tree (), loc);
|
|
|
|
add_stmt (rvalue->as_tree ());
|
|
}
|
|
|
|
/* Add an assignment to the function's statement list. */
|
|
|
|
void
|
|
playback::block::
|
|
add_assignment (location *loc,
|
|
lvalue *lvalue,
|
|
rvalue *rvalue)
|
|
{
|
|
gcc_assert (lvalue);
|
|
gcc_assert (rvalue);
|
|
|
|
tree t_lvalue = lvalue->as_tree ();
|
|
tree t_rvalue = rvalue->as_tree ();
|
|
if (TREE_TYPE (t_rvalue) != TREE_TYPE (t_lvalue))
|
|
{
|
|
t_rvalue = build1 (CONVERT_EXPR,
|
|
TREE_TYPE (t_lvalue),
|
|
t_rvalue);
|
|
if (loc)
|
|
set_tree_location (t_rvalue, loc);
|
|
}
|
|
|
|
tree stmt =
|
|
build2 (MODIFY_EXPR, TREE_TYPE (t_lvalue),
|
|
t_lvalue, t_rvalue);
|
|
if (loc)
|
|
set_tree_location (stmt, loc);
|
|
add_stmt (stmt);
|
|
}
|
|
|
|
/* Add a comment to the function's statement list.
|
|
For now this is done by adding a dummy label. */
|
|
|
|
void
|
|
playback::block::
|
|
add_comment (location *loc,
|
|
const char *text)
|
|
{
|
|
/* Wrap the text in C-style comment delimiters. */
|
|
size_t sz =
|
|
(3 /* opening delim */
|
|
+ strlen (text)
|
|
+ 3 /* closing delim */
|
|
+ 1 /* terminator */);
|
|
char *wrapped = (char *)ggc_internal_alloc (sz);
|
|
snprintf (wrapped, sz, "/* %s */", text);
|
|
|
|
/* For now we simply implement this by adding a dummy label with a name
|
|
containing the given text. */
|
|
tree identifier = get_identifier (wrapped);
|
|
tree label_decl = build_decl (UNKNOWN_LOCATION, LABEL_DECL,
|
|
identifier, void_type_node);
|
|
DECL_CONTEXT (label_decl) = m_func->as_fndecl ();
|
|
|
|
tree label_expr = build1 (LABEL_EXPR, void_type_node, label_decl);
|
|
if (loc)
|
|
set_tree_location (label_expr, loc);
|
|
add_stmt (label_expr);
|
|
}
|
|
|
|
/* Add a conditional jump statement to the function's statement list. */
|
|
|
|
void
|
|
playback::block::
|
|
add_conditional (location *loc,
|
|
rvalue *boolval,
|
|
block *on_true,
|
|
block *on_false)
|
|
{
|
|
gcc_assert (boolval);
|
|
gcc_assert (on_true);
|
|
gcc_assert (on_false);
|
|
|
|
/* COND_EXPR wants statement lists for the true/false operands, but we
|
|
want labels.
|
|
Shim it by creating jumps to the labels */
|
|
tree true_jump = build1 (GOTO_EXPR, void_type_node,
|
|
on_true->as_label_decl ());
|
|
if (loc)
|
|
set_tree_location (true_jump, loc);
|
|
|
|
tree false_jump = build1 (GOTO_EXPR, void_type_node,
|
|
on_false->as_label_decl ());
|
|
if (loc)
|
|
set_tree_location (false_jump, loc);
|
|
|
|
tree stmt =
|
|
build3 (COND_EXPR, void_type_node, boolval->as_tree (),
|
|
true_jump, false_jump);
|
|
if (loc)
|
|
set_tree_location (stmt, loc);
|
|
add_stmt (stmt);
|
|
}
|
|
|
|
/* Add an unconditional jump statement to the function's statement list. */
|
|
|
|
void
|
|
playback::block::
|
|
add_jump (location *loc,
|
|
block *target)
|
|
{
|
|
gcc_assert (target);
|
|
|
|
// see c_finish_loop
|
|
//tree top = build1 (LABEL_EXPR, void_type_node, NULL_TREE);
|
|
//add_stmt (top);
|
|
|
|
//tree stmt = build_and_jump (&LABEL_EXPR_LABEL (target->label_));
|
|
TREE_USED (target->as_label_decl ()) = 1;
|
|
tree stmt = build1 (GOTO_EXPR, void_type_node, target->as_label_decl ());
|
|
if (loc)
|
|
set_tree_location (stmt, loc);
|
|
add_stmt (stmt);
|
|
|
|
/*
|
|
from c-typeck.c:
|
|
tree
|
|
c_finish_goto_label (location_t loc, tree label)
|
|
{
|
|
tree decl = lookup_label_for_goto (loc, label);
|
|
if (!decl)
|
|
return NULL_TREE;
|
|
TREE_USED (decl) = 1;
|
|
{
|
|
tree t = build1 (GOTO_EXPR, void_type_node, decl);
|
|
SET_EXPR_LOCATION (t, loc);
|
|
return add_stmt (t);
|
|
}
|
|
}
|
|
*/
|
|
|
|
}
|
|
|
|
/* Add a return statement to the function's statement list. */
|
|
|
|
void
|
|
playback::block::
|
|
add_return (location *loc,
|
|
rvalue *rvalue)
|
|
{
|
|
tree modify_retval = NULL;
|
|
tree return_type = m_func->get_return_type_as_tree ();
|
|
if (rvalue)
|
|
{
|
|
tree t_lvalue = DECL_RESULT (m_func->as_fndecl ());
|
|
tree t_rvalue = rvalue->as_tree ();
|
|
if (TREE_TYPE (t_rvalue) != TREE_TYPE (t_lvalue))
|
|
t_rvalue = build1 (CONVERT_EXPR,
|
|
TREE_TYPE (t_lvalue),
|
|
t_rvalue);
|
|
modify_retval = build2 (MODIFY_EXPR, return_type,
|
|
t_lvalue, t_rvalue);
|
|
if (loc)
|
|
set_tree_location (modify_retval, loc);
|
|
}
|
|
tree return_stmt = build1 (RETURN_EXPR, return_type,
|
|
modify_retval);
|
|
if (loc)
|
|
set_tree_location (return_stmt, loc);
|
|
|
|
add_stmt (return_stmt);
|
|
}
|
|
|
|
/* Helper function for playback::block::add_switch.
|
|
Construct a case label for the given range, followed by a goto stmt
|
|
to the given block, appending them to stmt list *ptr_t_switch_body. */
|
|
|
|
static void
|
|
add_case (tree *ptr_t_switch_body,
|
|
tree t_low_value,
|
|
tree t_high_value,
|
|
playback::block *dest_block)
|
|
{
|
|
tree t_label = create_artificial_label (UNKNOWN_LOCATION);
|
|
DECL_CONTEXT (t_label) = dest_block->get_function ()->as_fndecl ();
|
|
|
|
tree t_case_label =
|
|
build_case_label (t_low_value, t_high_value, t_label);
|
|
append_to_statement_list (t_case_label, ptr_t_switch_body);
|
|
|
|
tree t_goto_stmt =
|
|
build1 (GOTO_EXPR, void_type_node, dest_block->as_label_decl ());
|
|
append_to_statement_list (t_goto_stmt, ptr_t_switch_body);
|
|
}
|
|
|
|
/* Add a switch statement to the function's statement list.
|
|
|
|
We create a switch body, and populate it with case labels, each
|
|
followed by a goto to the desired block. */
|
|
|
|
void
|
|
playback::block::
|
|
add_switch (location *loc,
|
|
rvalue *expr,
|
|
block *default_block,
|
|
const auto_vec <case_> *cases)
|
|
{
|
|
/* Compare with:
|
|
- c/c-typeck.c: c_start_case
|
|
- c-family/c-common.c:c_add_case_label
|
|
- java/expr.c:expand_java_switch and expand_java_add_case
|
|
We've already rejected overlaps and duplicates in
|
|
libgccjit.c:case_range_validator::validate. */
|
|
|
|
tree t_expr = expr->as_tree ();
|
|
tree t_type = TREE_TYPE (t_expr);
|
|
|
|
tree t_switch_body = alloc_stmt_list ();
|
|
|
|
int i;
|
|
case_ *c;
|
|
FOR_EACH_VEC_ELT (*cases, i, c)
|
|
{
|
|
tree t_low_value = c->m_min_value->as_tree ();
|
|
tree t_high_value = c->m_max_value->as_tree ();
|
|
add_case (&t_switch_body, t_low_value, t_high_value, c->m_dest_block);
|
|
}
|
|
/* Default label. */
|
|
add_case (&t_switch_body, NULL_TREE, NULL_TREE, default_block);
|
|
|
|
tree switch_stmt = build2 (SWITCH_EXPR, t_type, t_expr, t_switch_body);
|
|
if (loc)
|
|
set_tree_location (switch_stmt, loc);
|
|
add_stmt (switch_stmt);
|
|
}
|
|
|
|
/* Convert OPERANDS to a tree-based chain suitable for creating an
|
|
extended asm stmt.
|
|
Compare with c_parser_asm_operands. */
|
|
|
|
static tree
|
|
build_operand_chain (const auto_vec <playback::asm_operand> *operands)
|
|
{
|
|
tree result = NULL_TREE;
|
|
unsigned i;
|
|
playback::asm_operand *asm_op;
|
|
FOR_EACH_VEC_ELT (*operands, i, asm_op)
|
|
{
|
|
tree name = build_string (asm_op->m_asm_symbolic_name);
|
|
tree str = build_string (asm_op->m_constraint);
|
|
tree value = asm_op->m_expr;
|
|
result = chainon (result,
|
|
build_tree_list (build_tree_list (name, str),
|
|
value));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Convert CLOBBERS to a tree-based list suitable for creating an
|
|
extended asm stmt.
|
|
Compare with c_parser_asm_clobbers. */
|
|
|
|
static tree
|
|
build_clobbers (const auto_vec <const char *> *clobbers)
|
|
{
|
|
tree list = NULL_TREE;
|
|
unsigned i;
|
|
const char *clobber;
|
|
FOR_EACH_VEC_ELT (*clobbers, i, clobber)
|
|
{
|
|
tree str = build_string (clobber);
|
|
list = tree_cons (NULL_TREE, str, list);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/* Convert BLOCKS to a tree-based list suitable for creating an
|
|
extended asm stmt.
|
|
Compare with c_parser_asm_goto_operands. */
|
|
|
|
static tree
|
|
build_goto_operands (const auto_vec <playback::block *> *blocks)
|
|
{
|
|
tree list = NULL_TREE;
|
|
unsigned i;
|
|
playback::block *b;
|
|
FOR_EACH_VEC_ELT (*blocks, i, b)
|
|
{
|
|
tree label = b->as_label_decl ();
|
|
tree name = build_string (IDENTIFIER_POINTER (DECL_NAME (label)));
|
|
TREE_USED (label) = 1;
|
|
list = tree_cons (name, label, list);
|
|
}
|
|
return nreverse (list);
|
|
}
|
|
|
|
/* Add an extended asm statement to this block.
|
|
|
|
Compare with c_parser_asm_statement (in c/c-parser.c)
|
|
and build_asm_expr (in c/c-typeck.c). */
|
|
|
|
void
|
|
playback::block::add_extended_asm (location *loc,
|
|
const char *asm_template,
|
|
bool is_volatile,
|
|
bool is_inline,
|
|
const auto_vec <asm_operand> *outputs,
|
|
const auto_vec <asm_operand> *inputs,
|
|
const auto_vec <const char *> *clobbers,
|
|
const auto_vec <block *> *goto_blocks)
|
|
{
|
|
tree t_string = build_string (asm_template);
|
|
tree t_outputs = build_operand_chain (outputs);
|
|
tree t_inputs = build_operand_chain (inputs);
|
|
tree t_clobbers = build_clobbers (clobbers);
|
|
tree t_labels = build_goto_operands (goto_blocks);
|
|
t_string
|
|
= resolve_asm_operand_names (t_string, t_outputs, t_inputs, t_labels);
|
|
tree asm_stmt
|
|
= build5 (ASM_EXPR, void_type_node,
|
|
t_string, t_outputs, t_inputs, t_clobbers, t_labels);
|
|
|
|
/* asm statements without outputs, including simple ones, are treated
|
|
as volatile. */
|
|
ASM_VOLATILE_P (asm_stmt) = (outputs->length () == 0);
|
|
ASM_INPUT_P (asm_stmt) = 0; /* extended asm stmts are not "simple". */
|
|
ASM_INLINE_P (asm_stmt) = is_inline;
|
|
if (is_volatile)
|
|
ASM_VOLATILE_P (asm_stmt) = 1;
|
|
if (loc)
|
|
set_tree_location (asm_stmt, loc);
|
|
add_stmt (asm_stmt);
|
|
}
|
|
|
|
/* Constructor for gcc::jit::playback::block. */
|
|
|
|
playback::block::
|
|
block (function *func,
|
|
const char *name)
|
|
: m_func (func),
|
|
m_stmts ()
|
|
{
|
|
tree identifier;
|
|
|
|
gcc_assert (func);
|
|
// name can be NULL
|
|
if (name)
|
|
identifier = get_identifier (name);
|
|
else
|
|
identifier = NULL;
|
|
m_label_decl = build_decl (UNKNOWN_LOCATION, LABEL_DECL,
|
|
identifier, void_type_node);
|
|
DECL_CONTEXT (m_label_decl) = func->as_fndecl ();
|
|
m_label_expr = NULL;
|
|
}
|
|
|
|
/* Compile a playback::context:
|
|
|
|
- Use the context's options to cconstruct command-line options, and
|
|
call into the rest of GCC (toplev::main).
|
|
- Assuming it succeeds, we have a .s file.
|
|
- We then run the "postprocess" vfunc:
|
|
|
|
(A) In-memory compile ("gcc_jit_context_compile")
|
|
|
|
For an in-memory compile we have the playback::compile_to_memory
|
|
subclass; "postprocess" will convert the .s file to a .so DSO,
|
|
and load it in memory (via dlopen), wrapping the result up as
|
|
a jit::result and returning it.
|
|
|
|
(B) Compile to file ("gcc_jit_context_compile_to_file")
|
|
|
|
When compiling to a file, we have the playback::compile_to_file
|
|
subclass; "postprocess" will either copy the .s file to the
|
|
destination (for GCC_JIT_OUTPUT_KIND_ASSEMBLER), or invoke
|
|
the driver to convert it as necessary, copying the result. */
|
|
|
|
void
|
|
playback::context::
|
|
compile ()
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
|
|
const char *ctxt_progname;
|
|
|
|
int keep_intermediates =
|
|
get_bool_option (GCC_JIT_BOOL_OPTION_KEEP_INTERMEDIATES);
|
|
|
|
m_tempdir = new tempdir (get_logger (), keep_intermediates);
|
|
if (!m_tempdir->create ())
|
|
return;
|
|
|
|
/* Call into the rest of gcc.
|
|
For now, we have to assemble command-line options to pass into
|
|
toplev::main, so that they can be parsed. */
|
|
|
|
/* Pass in user-provided program name as argv0, if any, so that it
|
|
makes it into GCC's "progname" global, used in various diagnostics. */
|
|
ctxt_progname = get_str_option (GCC_JIT_STR_OPTION_PROGNAME);
|
|
|
|
if (!ctxt_progname)
|
|
ctxt_progname = "libgccjit.so";
|
|
|
|
auto_vec <recording::requested_dump> requested_dumps;
|
|
m_recording_ctxt->get_all_requested_dumps (&requested_dumps);
|
|
|
|
/* Acquire the JIT mutex and set "this" as the active playback ctxt. */
|
|
acquire_mutex ();
|
|
|
|
auto_string_vec fake_args;
|
|
make_fake_args (&fake_args, ctxt_progname, &requested_dumps);
|
|
if (errors_occurred ())
|
|
{
|
|
release_mutex ();
|
|
return;
|
|
}
|
|
|
|
/* This runs the compiler. */
|
|
toplev toplev (get_timer (), /* external_timer */
|
|
false); /* init_signals */
|
|
enter_scope ("toplev::main");
|
|
if (get_logger ())
|
|
for (unsigned i = 0; i < fake_args.length (); i++)
|
|
get_logger ()->log ("argv[%i]: %s", i, fake_args[i]);
|
|
toplev.main (fake_args.length (),
|
|
const_cast <char **> (fake_args.address ()));
|
|
exit_scope ("toplev::main");
|
|
|
|
/* Extracting dumps makes use of the gcc::dump_manager, hence we
|
|
need to do it between toplev::main (which creates the dump manager)
|
|
and toplev::finalize (which deletes it). */
|
|
extract_any_requested_dumps (&requested_dumps);
|
|
|
|
/* Clean up the compiler. */
|
|
enter_scope ("toplev::finalize");
|
|
toplev.finalize ();
|
|
exit_scope ("toplev::finalize");
|
|
|
|
/* Ideally we would release the jit mutex here, but we can't yet since
|
|
followup activities use timevars, which are global state. */
|
|
|
|
if (errors_occurred ())
|
|
{
|
|
release_mutex ();
|
|
return;
|
|
}
|
|
|
|
if (get_bool_option (GCC_JIT_BOOL_OPTION_DUMP_GENERATED_CODE))
|
|
dump_generated_code ();
|
|
|
|
/* We now have a .s file.
|
|
|
|
Run any postprocessing steps. This will either convert the .s file to
|
|
a .so DSO, and load it in memory (playback::compile_to_memory), or
|
|
convert the .s file to the requested output format, and copy it to a
|
|
given file (playback::compile_to_file). */
|
|
postprocess (ctxt_progname);
|
|
|
|
release_mutex ();
|
|
}
|
|
|
|
/* Implementation of class gcc::jit::playback::compile_to_memory,
|
|
a subclass of gcc::jit::playback::context. */
|
|
|
|
/* playback::compile_to_memory's trivial constructor. */
|
|
|
|
playback::compile_to_memory::compile_to_memory (recording::context *ctxt) :
|
|
playback::context (ctxt),
|
|
m_result (NULL)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
}
|
|
|
|
/* Implementation of the playback::context::process vfunc for compiling
|
|
to memory.
|
|
|
|
Convert the .s file to a .so DSO, and load it in memory (via dlopen),
|
|
wrapping the result up as a jit::result and returning it. */
|
|
|
|
void
|
|
playback::compile_to_memory::postprocess (const char *ctxt_progname)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
convert_to_dso (ctxt_progname);
|
|
if (errors_occurred ())
|
|
return;
|
|
m_result = dlopen_built_dso ();
|
|
}
|
|
|
|
/* Implementation of class gcc::jit::playback::compile_to_file,
|
|
a subclass of gcc::jit::playback::context. */
|
|
|
|
/* playback::compile_to_file's trivial constructor. */
|
|
|
|
playback::compile_to_file::compile_to_file (recording::context *ctxt,
|
|
enum gcc_jit_output_kind output_kind,
|
|
const char *output_path) :
|
|
playback::context (ctxt),
|
|
m_output_kind (output_kind),
|
|
m_output_path (output_path)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
}
|
|
|
|
/* Implementation of the playback::context::process vfunc for compiling
|
|
to a file.
|
|
|
|
Either copy the .s file to the given destination (for
|
|
GCC_JIT_OUTPUT_KIND_ASSEMBLER), or invoke the driver to convert it
|
|
as necessary, copying the result. */
|
|
|
|
void
|
|
playback::compile_to_file::postprocess (const char *ctxt_progname)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
|
|
/* The driver takes different actions based on the filename, so
|
|
we provide a filename with an appropriate suffix for the
|
|
output kind, and then copy it up to the user-provided path,
|
|
rather than directly compiling it to the requested output path. */
|
|
|
|
switch (m_output_kind)
|
|
{
|
|
default:
|
|
gcc_unreachable ();
|
|
|
|
case GCC_JIT_OUTPUT_KIND_ASSEMBLER:
|
|
copy_file (get_tempdir ()->get_path_s_file (),
|
|
m_output_path);
|
|
/* The .s file is automatically unlinked by tempdir::~tempdir. */
|
|
break;
|
|
|
|
case GCC_JIT_OUTPUT_KIND_OBJECT_FILE:
|
|
{
|
|
char *tmp_o_path = ::concat (get_tempdir ()->get_path (),
|
|
"/fake.o",
|
|
NULL);
|
|
invoke_driver (ctxt_progname,
|
|
get_tempdir ()->get_path_s_file (),
|
|
tmp_o_path,
|
|
TV_ASSEMBLE,
|
|
false, /* bool shared, */
|
|
false);/* bool run_linker */
|
|
if (!errors_occurred ())
|
|
{
|
|
copy_file (tmp_o_path,
|
|
m_output_path);
|
|
get_tempdir ()->add_temp_file (tmp_o_path);
|
|
}
|
|
else
|
|
free (tmp_o_path);
|
|
}
|
|
break;
|
|
|
|
case GCC_JIT_OUTPUT_KIND_DYNAMIC_LIBRARY:
|
|
invoke_driver (ctxt_progname,
|
|
get_tempdir ()->get_path_s_file (),
|
|
get_tempdir ()->get_path_so_file (),
|
|
TV_ASSEMBLE,
|
|
true, /* bool shared, */
|
|
true);/* bool run_linker */
|
|
if (!errors_occurred ())
|
|
copy_file (get_tempdir ()->get_path_so_file (),
|
|
m_output_path);
|
|
/* The .so file is automatically unlinked by tempdir::~tempdir. */
|
|
break;
|
|
|
|
case GCC_JIT_OUTPUT_KIND_EXECUTABLE:
|
|
{
|
|
char *tmp_exe_path = ::concat (get_tempdir ()->get_path (),
|
|
"/fake.exe",
|
|
NULL);
|
|
invoke_driver (ctxt_progname,
|
|
get_tempdir ()->get_path_s_file (),
|
|
tmp_exe_path,
|
|
TV_ASSEMBLE,
|
|
false, /* bool shared, */
|
|
true);/* bool run_linker */
|
|
if (!errors_occurred ())
|
|
{
|
|
copy_file (tmp_exe_path,
|
|
m_output_path);
|
|
get_tempdir ()->add_temp_file (tmp_exe_path);
|
|
}
|
|
else
|
|
free (tmp_exe_path);
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Copy SRC_PATH to DST_PATH, preserving permission bits (in particular,
|
|
the "executable" bits).
|
|
|
|
Any errors that occur are reported on the context and hence count as
|
|
a failure of the compile.
|
|
|
|
We can't in general hardlink or use "rename" from the tempdir since
|
|
it might be on a different filesystem to the destination. For example,
|
|
I get EXDEV: "Invalid cross-device link". */
|
|
|
|
void
|
|
playback::compile_to_file::copy_file (const char *src_path,
|
|
const char *dst_path)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
if (get_logger ())
|
|
{
|
|
get_logger ()->log ("src_path: %s", src_path);
|
|
get_logger ()->log ("dst_path: %s", dst_path);
|
|
}
|
|
|
|
FILE *f_in = NULL;
|
|
FILE *f_out = NULL;
|
|
size_t total_sz_in = 0;
|
|
size_t total_sz_out = 0;
|
|
char buf[4096];
|
|
size_t sz_in;
|
|
struct stat stat_buf;
|
|
|
|
f_in = fopen (src_path, "rb");
|
|
if (!f_in)
|
|
{
|
|
add_error (NULL,
|
|
"unable to open %s for reading: %s",
|
|
src_path,
|
|
xstrerror (errno));
|
|
return;
|
|
}
|
|
|
|
/* Use stat on the filedescriptor to get the mode,
|
|
so that we can copy it over (in particular, the
|
|
"executable" bits). */
|
|
if (fstat (fileno (f_in), &stat_buf) == -1)
|
|
{
|
|
add_error (NULL,
|
|
"unable to fstat %s: %s",
|
|
src_path,
|
|
xstrerror (errno));
|
|
fclose (f_in);
|
|
return;
|
|
}
|
|
|
|
f_out = fopen (dst_path, "wb");
|
|
if (!f_out)
|
|
{
|
|
add_error (NULL,
|
|
"unable to open %s for writing: %s",
|
|
dst_path,
|
|
xstrerror (errno));
|
|
fclose (f_in);
|
|
return;
|
|
}
|
|
|
|
while ( (sz_in = fread (buf, 1, sizeof (buf), f_in)) )
|
|
{
|
|
total_sz_in += sz_in;
|
|
size_t sz_out_remaining = sz_in;
|
|
size_t sz_out_so_far = 0;
|
|
while (sz_out_remaining)
|
|
{
|
|
size_t sz_out = fwrite (buf + sz_out_so_far,
|
|
1,
|
|
sz_out_remaining,
|
|
f_out);
|
|
gcc_assert (sz_out <= sz_out_remaining);
|
|
if (!sz_out)
|
|
{
|
|
add_error (NULL,
|
|
"error writing to %s: %s",
|
|
dst_path,
|
|
xstrerror (errno));
|
|
fclose (f_in);
|
|
fclose (f_out);
|
|
return;
|
|
}
|
|
total_sz_out += sz_out;
|
|
sz_out_so_far += sz_out;
|
|
sz_out_remaining -= sz_out;
|
|
}
|
|
gcc_assert (sz_out_so_far == sz_in);
|
|
}
|
|
|
|
if (!feof (f_in))
|
|
add_error (NULL,
|
|
"error reading from %s: %s",
|
|
src_path,
|
|
xstrerror (errno));
|
|
|
|
fclose (f_in);
|
|
|
|
gcc_assert (total_sz_in == total_sz_out);
|
|
if (get_logger ())
|
|
get_logger ()->log ("total bytes copied: %zu", total_sz_out);
|
|
|
|
/* fchmod does not exist in Windows. */
|
|
#ifndef _WIN32
|
|
/* Set the permissions of the copy to those of the original file,
|
|
in particular the "executable" bits. */
|
|
if (fchmod (fileno (f_out), stat_buf.st_mode) == -1)
|
|
add_error (NULL,
|
|
"error setting mode of %s: %s",
|
|
dst_path,
|
|
xstrerror (errno));
|
|
#endif
|
|
|
|
fclose (f_out);
|
|
}
|
|
|
|
/* Helper functions for gcc::jit::playback::context::compile. */
|
|
|
|
/* This mutex guards gcc::jit::recording::context::compile, so that only
|
|
one thread can be accessing the bulk of GCC's state at once. */
|
|
|
|
static pthread_mutex_t jit_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
/* Acquire jit_mutex and set "this" as the active playback ctxt. */
|
|
|
|
void
|
|
playback::context::acquire_mutex ()
|
|
{
|
|
auto_timevar tv (get_timer (), TV_JIT_ACQUIRING_MUTEX);
|
|
|
|
/* Acquire the big GCC mutex. */
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
pthread_mutex_lock (&jit_mutex);
|
|
gcc_assert (active_playback_ctxt == NULL);
|
|
active_playback_ctxt = this;
|
|
}
|
|
|
|
/* Release jit_mutex and clear the active playback ctxt. */
|
|
|
|
void
|
|
playback::context::release_mutex ()
|
|
{
|
|
/* Release the big GCC mutex. */
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
gcc_assert (active_playback_ctxt == this);
|
|
active_playback_ctxt = NULL;
|
|
pthread_mutex_unlock (&jit_mutex);
|
|
}
|
|
|
|
/* Callback used by gcc::jit::playback::context::make_fake_args when
|
|
invoking driver_get_configure_time_options.
|
|
Populate a vec <char * > with the configure-time options. */
|
|
|
|
static void
|
|
append_arg_from_driver (const char *option, void *user_data)
|
|
{
|
|
gcc_assert (option);
|
|
gcc_assert (user_data);
|
|
vec <char *> *argvec = static_cast <vec <char *> *> (user_data);
|
|
argvec->safe_push (concat ("-", option, NULL));
|
|
}
|
|
|
|
/* Build a fake argv for toplev::main from the options set
|
|
by the user on the context . */
|
|
|
|
void
|
|
playback::context::
|
|
make_fake_args (vec <char *> *argvec,
|
|
const char *ctxt_progname,
|
|
vec <recording::requested_dump> *requested_dumps)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
|
|
#define ADD_ARG(arg) argvec->safe_push (xstrdup (arg))
|
|
#define ADD_ARG_TAKE_OWNERSHIP(arg) argvec->safe_push (arg)
|
|
|
|
ADD_ARG (ctxt_progname);
|
|
ADD_ARG (get_path_c_file ());
|
|
ADD_ARG ("-fPIC");
|
|
|
|
/* Handle int options: */
|
|
switch (get_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL))
|
|
{
|
|
default:
|
|
add_error (NULL,
|
|
"unrecognized optimization level: %i",
|
|
get_int_option (GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL));
|
|
return;
|
|
|
|
case 0:
|
|
ADD_ARG ("-O0");
|
|
break;
|
|
|
|
case 1:
|
|
ADD_ARG ("-O1");
|
|
break;
|
|
|
|
case 2:
|
|
ADD_ARG ("-O2");
|
|
break;
|
|
|
|
case 3:
|
|
ADD_ARG ("-O3");
|
|
break;
|
|
}
|
|
/* What about -Os? */
|
|
|
|
/* Handle bool options: */
|
|
if (get_bool_option (GCC_JIT_BOOL_OPTION_DEBUGINFO))
|
|
ADD_ARG ("-g");
|
|
|
|
/* Suppress timing (and other) info. */
|
|
if (!get_bool_option (GCC_JIT_BOOL_OPTION_DUMP_SUMMARY))
|
|
{
|
|
ADD_ARG ("-quiet");
|
|
quiet_flag = 1;
|
|
}
|
|
|
|
/* Aggressively garbage-collect, to shake out bugs: */
|
|
if (get_bool_option (GCC_JIT_BOOL_OPTION_SELFCHECK_GC))
|
|
{
|
|
ADD_ARG ("--param=ggc-min-expand=0");
|
|
ADD_ARG ("--param=ggc-min-heapsize=0");
|
|
}
|
|
|
|
if (get_bool_option (GCC_JIT_BOOL_OPTION_DUMP_EVERYTHING))
|
|
{
|
|
ADD_ARG ("-fdump-tree-all");
|
|
ADD_ARG ("-fdump-rtl-all");
|
|
ADD_ARG ("-fdump-ipa-all");
|
|
}
|
|
|
|
/* Add "-fdump-" options for any calls to
|
|
gcc_jit_context_enable_dump. */
|
|
{
|
|
int i;
|
|
recording::requested_dump *d;
|
|
FOR_EACH_VEC_ELT (*requested_dumps, i, d)
|
|
{
|
|
char *arg = concat ("-fdump-", d->m_dumpname, NULL);
|
|
ADD_ARG_TAKE_OWNERSHIP (arg);
|
|
}
|
|
}
|
|
|
|
/* PR jit/64810: Add any target-specific default options
|
|
from OPTION_DEFAULT_SPECS, normally provided by the driver
|
|
in the non-jit case.
|
|
|
|
The target-specific code can define OPTION_DEFAULT_SPECS:
|
|
default command options in the form of spec macros for the
|
|
driver to expand ().
|
|
|
|
For cc1 etc, the driver processes OPTION_DEFAULT_SPECS and,
|
|
if not overriden, injects the defaults as extra arguments to
|
|
cc1 etc.
|
|
For the jit case, we need to add these arguments here. The
|
|
input format (using the specs language) means that we have to run
|
|
part of the driver code here (driver_get_configure_time_options).
|
|
|
|
To avoid running the spec-expansion code every time, we just do
|
|
it the first time (via a function-static flag), saving the result
|
|
into a function-static vec.
|
|
This flag and vec are global state (i.e. per-process).
|
|
They are guarded by the jit mutex. */
|
|
{
|
|
static bool have_configure_time_options = false;
|
|
static vec <char *> configure_time_options;
|
|
|
|
if (have_configure_time_options)
|
|
log ("reusing cached configure-time options");
|
|
else
|
|
{
|
|
have_configure_time_options = true;
|
|
log ("getting configure-time options from driver");
|
|
driver_get_configure_time_options (append_arg_from_driver,
|
|
&configure_time_options);
|
|
}
|
|
|
|
int i;
|
|
char *opt;
|
|
|
|
if (get_logger ())
|
|
FOR_EACH_VEC_ELT (configure_time_options, i, opt)
|
|
log ("configure_time_options[%i]: %s", i, opt);
|
|
|
|
/* configure_time_options should now contain the expanded options
|
|
from OPTION_DEFAULT_SPECS (if any). */
|
|
FOR_EACH_VEC_ELT (configure_time_options, i, opt)
|
|
{
|
|
gcc_assert (opt);
|
|
gcc_assert (opt[0] == '-');
|
|
ADD_ARG (opt);
|
|
}
|
|
}
|
|
|
|
if (get_timer ())
|
|
ADD_ARG ("-ftime-report");
|
|
|
|
/* Add any user-provided extra options, starting with any from
|
|
parent contexts. */
|
|
m_recording_ctxt->append_command_line_options (argvec);
|
|
|
|
#undef ADD_ARG
|
|
#undef ADD_ARG_TAKE_OWNERSHIP
|
|
}
|
|
|
|
/* The second half of the implementation of gcc_jit_context_enable_dump.
|
|
Iterate through the requested dumps, reading the underlying files
|
|
into heap-allocated buffers, writing pointers to the buffers into
|
|
the char ** pointers provided by client code.
|
|
Client code is responsible for calling free on the results. */
|
|
|
|
void
|
|
playback::context::
|
|
extract_any_requested_dumps (vec <recording::requested_dump> *requested_dumps)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
|
|
int i;
|
|
recording::requested_dump *d;
|
|
FOR_EACH_VEC_ELT (*requested_dumps, i, d)
|
|
{
|
|
dump_file_info *dfi;
|
|
char *filename;
|
|
char *content;
|
|
|
|
dfi = g->get_dumps ()->get_dump_file_info_by_switch (d->m_dumpname);
|
|
if (!dfi)
|
|
{
|
|
add_error (NULL, "unrecognized dump: %s", d->m_dumpname);
|
|
continue;
|
|
}
|
|
|
|
filename = g->get_dumps ()->get_dump_file_name (dfi);
|
|
content = read_dump_file (filename);
|
|
*(d->m_out_ptr) = content;
|
|
m_tempdir->add_temp_file (filename);
|
|
}
|
|
}
|
|
|
|
/* Helper function for playback::context::extract_any_requested_dumps
|
|
(itself for use in implementation of gcc_jit_context_enable_dump).
|
|
|
|
Attempt to read the complete file at the given path, returning the
|
|
bytes found there as a buffer.
|
|
The caller is responsible for calling free on the result.
|
|
Errors will be reported on the context, and lead to NULL being
|
|
returned; an out-of-memory error will terminate the process. */
|
|
|
|
char *
|
|
playback::context::read_dump_file (const char *path)
|
|
{
|
|
char *result = NULL;
|
|
size_t total_sz = 0;
|
|
char buf[4096];
|
|
size_t sz;
|
|
FILE *f_in;
|
|
|
|
f_in = fopen (path, "r");
|
|
if (!f_in)
|
|
{
|
|
add_error (NULL, "unable to open %s for reading", path);
|
|
return NULL;
|
|
}
|
|
|
|
while ( (sz = fread (buf, 1, sizeof (buf), f_in)) )
|
|
{
|
|
size_t old_total_sz = total_sz;
|
|
total_sz += sz;
|
|
result = reinterpret_cast <char *> (xrealloc (result, total_sz + 1));
|
|
memcpy (result + old_total_sz, buf, sz);
|
|
}
|
|
|
|
if (!feof (f_in))
|
|
{
|
|
add_error (NULL, "error reading from %s", path);
|
|
free (result);
|
|
fclose (f_in);
|
|
return NULL;
|
|
}
|
|
|
|
fclose (f_in);
|
|
|
|
if (result)
|
|
{
|
|
result[total_sz] = '\0';
|
|
return result;
|
|
}
|
|
else
|
|
return xstrdup ("");
|
|
}
|
|
|
|
/* Part of playback::context::compile ().
|
|
|
|
We have a .s file; we want a .so file.
|
|
We could reuse parts of gcc/gcc.c to do this.
|
|
For now, just use the driver binary from the install, as
|
|
named in gcc-driver-name.h
|
|
e.g. "x86_64-unknown-linux-gnu-gcc-5.0.0". */
|
|
|
|
void
|
|
playback::context::
|
|
convert_to_dso (const char *ctxt_progname)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
|
|
invoke_driver (ctxt_progname,
|
|
m_tempdir->get_path_s_file (),
|
|
m_tempdir->get_path_so_file (),
|
|
TV_ASSEMBLE,
|
|
true, /* bool shared, */
|
|
true);/* bool run_linker */
|
|
}
|
|
|
|
static const char * const gcc_driver_name = GCC_DRIVER_NAME;
|
|
|
|
void
|
|
playback::context::
|
|
invoke_driver (const char *ctxt_progname,
|
|
const char *input_file,
|
|
const char *output_file,
|
|
timevar_id_t tv_id,
|
|
bool shared,
|
|
bool run_linker)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
|
|
bool embedded_driver
|
|
= !get_inner_bool_option (INNER_BOOL_OPTION_USE_EXTERNAL_DRIVER);
|
|
|
|
/* Currently this lumps together both assembling and linking into
|
|
TV_ASSEMBLE. */
|
|
auto_timevar assemble_timevar (get_timer (), tv_id);
|
|
auto_string_vec argvec;
|
|
#define ADD_ARG(arg) argvec.safe_push (xstrdup (arg))
|
|
|
|
ADD_ARG (gcc_driver_name);
|
|
|
|
add_multilib_driver_arguments (&argvec);
|
|
|
|
if (shared)
|
|
ADD_ARG ("-shared");
|
|
|
|
if (!run_linker)
|
|
ADD_ARG ("-c");
|
|
|
|
ADD_ARG (input_file);
|
|
ADD_ARG ("-o");
|
|
ADD_ARG (output_file);
|
|
|
|
/* Don't use the linker plugin.
|
|
If running with just a "make" and not a "make install", then we'd
|
|
run into
|
|
"fatal error: -fuse-linker-plugin, but liblto_plugin.so not found"
|
|
libto_plugin is a .la at build time, with it becoming installed with
|
|
".so" suffix: i.e. it doesn't exist with a .so suffix until install
|
|
time. */
|
|
ADD_ARG ("-fno-use-linker-plugin");
|
|
|
|
#if defined (DARWIN_X86) || defined (DARWIN_PPC)
|
|
/* OS X's linker defaults to treating undefined symbols as errors.
|
|
If the context has any imported functions or globals they will be
|
|
undefined until the .so is dynamically-linked into the process.
|
|
Ensure that the driver passes in "-undefined dynamic_lookup" to the
|
|
linker. */
|
|
ADD_ARG ("-Wl,-undefined,dynamic_lookup");
|
|
#endif
|
|
|
|
if (0)
|
|
ADD_ARG ("-v");
|
|
|
|
/* Add any user-provided driver extra options. */
|
|
|
|
m_recording_ctxt->append_driver_options (&argvec);
|
|
|
|
#undef ADD_ARG
|
|
|
|
/* pex_one's error-handling requires pname to be non-NULL. */
|
|
gcc_assert (ctxt_progname);
|
|
|
|
if (get_logger ())
|
|
for (unsigned i = 0; i < argvec.length (); i++)
|
|
get_logger ()->log ("argv[%i]: %s", i, argvec[i]);
|
|
|
|
if (embedded_driver)
|
|
invoke_embedded_driver (&argvec);
|
|
else
|
|
invoke_external_driver (ctxt_progname, &argvec);
|
|
}
|
|
|
|
void
|
|
playback::context::
|
|
invoke_embedded_driver (const vec <char *> *argvec)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
driver d (true, /* can_finalize */
|
|
false); /* debug */
|
|
int result = d.main (argvec->length (),
|
|
const_cast <char **> (argvec->address ()));
|
|
d.finalize ();
|
|
if (result)
|
|
add_error (NULL, "error invoking gcc driver");
|
|
}
|
|
|
|
void
|
|
playback::context::
|
|
invoke_external_driver (const char *ctxt_progname,
|
|
vec <char *> *argvec)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
const char *errmsg;
|
|
int exit_status = 0;
|
|
int err = 0;
|
|
|
|
/* pex argv arrays are NULL-terminated. */
|
|
argvec->safe_push (NULL);
|
|
|
|
errmsg = pex_one (PEX_SEARCH, /* int flags, */
|
|
gcc_driver_name,
|
|
const_cast <char *const *> (argvec->address ()),
|
|
ctxt_progname, /* const char *pname */
|
|
NULL, /* const char *outname */
|
|
NULL, /* const char *errname */
|
|
&exit_status, /* int *status */
|
|
&err); /* int *err*/
|
|
if (errmsg)
|
|
{
|
|
add_error (NULL, "error invoking gcc driver: %s", errmsg);
|
|
return;
|
|
}
|
|
|
|
/* pex_one can return a NULL errmsg when the executable wasn't
|
|
found (or doesn't exist), so trap these cases also. */
|
|
if (exit_status || err)
|
|
{
|
|
add_error (NULL,
|
|
"error invoking gcc driver: exit_status: %i err: %i",
|
|
exit_status, err);
|
|
add_error (NULL,
|
|
"whilst attempting to run a driver named: %s",
|
|
gcc_driver_name);
|
|
add_error (NULL,
|
|
"PATH was: %s",
|
|
getenv ("PATH"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Extract the target-specific MULTILIB_DEFAULTS to
|
|
multilib_defaults_raw for use by
|
|
playback::context::add_multilib_driver_arguments (). */
|
|
|
|
#ifndef MULTILIB_DEFAULTS
|
|
#define MULTILIB_DEFAULTS { "" }
|
|
#endif
|
|
|
|
static const char *const multilib_defaults_raw[] = MULTILIB_DEFAULTS;
|
|
|
|
/* Helper function for playback::context::invoke_driver ().
|
|
|
|
32-bit and 64-bit multilib peer builds of libgccjit.so may share
|
|
a driver binary. We need to pass in options to the shared driver
|
|
to get the appropriate assembler/linker options for this multilib
|
|
peer. */
|
|
|
|
void
|
|
playback::context::
|
|
add_multilib_driver_arguments (vec <char *> *argvec)
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
|
|
/* Add copies of the arguments in multilib_defaults_raw to argvec,
|
|
prepending each with a "-". */
|
|
for (size_t i = 0; i < ARRAY_SIZE (multilib_defaults_raw); i++)
|
|
if (multilib_defaults_raw[i][0])
|
|
argvec->safe_push (concat ("-", multilib_defaults_raw[i], NULL));
|
|
}
|
|
|
|
/* Dynamically-link the built DSO file into this process, using dlopen.
|
|
Wrap it up within a jit::result *, and return that.
|
|
Return NULL if any errors occur, reporting them on this context. */
|
|
|
|
result *
|
|
playback::context::
|
|
dlopen_built_dso ()
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
auto_timevar load_timevar (get_timer (), TV_LOAD);
|
|
result::handle handle = NULL;
|
|
result *result_obj = NULL;
|
|
|
|
#ifdef _WIN32
|
|
/* Clear any existing error. */
|
|
SetLastError(0);
|
|
|
|
handle = LoadLibrary(m_tempdir->get_path_so_file ());
|
|
if (GetLastError() != 0) {
|
|
print_last_error();
|
|
}
|
|
#else
|
|
const char *error = NULL;
|
|
/* Clear any existing error. */
|
|
dlerror ();
|
|
|
|
handle = dlopen (m_tempdir->get_path_so_file (),
|
|
RTLD_NOW | RTLD_LOCAL);
|
|
if ((error = dlerror()) != NULL) {
|
|
add_error (NULL, "%s", error);
|
|
}
|
|
#endif
|
|
|
|
if (handle)
|
|
{
|
|
/* We've successfully dlopened the result; create a
|
|
jit::result object to wrap it.
|
|
|
|
We're done with the tempdir for now, but if the user
|
|
has requested debugging, the user's debugger might not
|
|
be capable of dealing with the .so file being unlinked
|
|
immediately, so keep it around until after the result
|
|
is released. We do this by handing over ownership of
|
|
the jit::tempdir to the result. See PR jit/64206. */
|
|
tempdir *handover_tempdir;
|
|
if (get_bool_option (GCC_JIT_BOOL_OPTION_DEBUGINFO))
|
|
{
|
|
handover_tempdir = m_tempdir;
|
|
m_tempdir = NULL;
|
|
/* The tempdir will eventually be cleaned up in the
|
|
jit::result's dtor. */
|
|
log ("GCC_JIT_BOOL_OPTION_DEBUGINFO was set:"
|
|
" handing over tempdir to jit::result");
|
|
}
|
|
else
|
|
{
|
|
handover_tempdir = NULL;
|
|
/* ... and retain ownership of m_tempdir so we clean it
|
|
up it the playback::context's dtor. */
|
|
log ("GCC_JIT_BOOL_OPTION_DEBUGINFO was not set:"
|
|
" retaining ownership of tempdir");
|
|
}
|
|
|
|
result_obj = new result (get_logger (), handle, handover_tempdir);
|
|
}
|
|
else
|
|
result_obj = NULL;
|
|
|
|
return result_obj;
|
|
}
|
|
|
|
/* Top-level hook for playing back a recording context.
|
|
|
|
This plays back m_recording_ctxt, and, if no errors
|
|
occurred builds statement lists for and then postprocesses
|
|
every function in the result. */
|
|
|
|
void
|
|
playback::context::
|
|
replay ()
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
|
|
m_const_char_ptr
|
|
= build_pointer_type (build_qualified_type (char_type_node,
|
|
TYPE_QUAL_CONST));
|
|
|
|
/* Replay the recorded events: */
|
|
timevar_push (TV_JIT_REPLAY);
|
|
|
|
/* Ensure that builtins that could be needed during optimization
|
|
get created ahead of time. */
|
|
builtins_manager *bm = m_recording_ctxt->get_builtins_manager ();
|
|
bm->ensure_optimization_builtins_exist ();
|
|
|
|
m_recording_ctxt->replay_into (this);
|
|
|
|
/* Clean away the temporary references from recording objects
|
|
to playback objects. We have to do this now since the
|
|
latter are GC-allocated, but the former don't mark these
|
|
refs. Hence we must stop using them before the GC can run. */
|
|
m_recording_ctxt->disassociate_from_playback ();
|
|
|
|
/* The builtins_manager is associated with the recording::context
|
|
and might be reused for future compiles on other playback::contexts,
|
|
but its m_attributes array is not GTY-labeled and hence will become
|
|
nonsense if the GC runs. Purge this state. */
|
|
bm->finish_playback ();
|
|
|
|
timevar_pop (TV_JIT_REPLAY);
|
|
|
|
if (!errors_occurred ())
|
|
{
|
|
int i;
|
|
function *func;
|
|
|
|
/* No GC can happen yet; process the cached source locations. */
|
|
handle_locations ();
|
|
|
|
/* We've now created tree nodes for the stmts in the various blocks
|
|
in each function, but we haven't built each function's single stmt
|
|
list yet. Do so now. */
|
|
FOR_EACH_VEC_ELT (m_functions, i, func)
|
|
func->build_stmt_list ();
|
|
|
|
/* No GC can have happened yet. */
|
|
|
|
/* Postprocess the functions. This could trigger GC. */
|
|
FOR_EACH_VEC_ELT (m_functions, i, func)
|
|
{
|
|
gcc_assert (func);
|
|
func->postprocess ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Dump the generated .s file to stderr. */
|
|
|
|
void
|
|
playback::context::
|
|
dump_generated_code ()
|
|
{
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
char buf[4096];
|
|
size_t sz;
|
|
FILE *f_in = fopen (get_path_s_file (), "r");
|
|
if (!f_in)
|
|
return;
|
|
|
|
while ( (sz = fread (buf, 1, sizeof (buf), f_in)) )
|
|
fwrite (buf, 1, sz, stderr);
|
|
|
|
fclose (f_in);
|
|
}
|
|
|
|
/* Get the supposed path of the notional "fake.c" file within the
|
|
tempdir. This file doesn't exist, but the rest of the compiler
|
|
needs a name. */
|
|
|
|
const char *
|
|
playback::context::
|
|
get_path_c_file () const
|
|
{
|
|
return m_tempdir->get_path_c_file ();
|
|
}
|
|
|
|
/* Get the path of the assembler output file "fake.s" file within the
|
|
tempdir. */
|
|
|
|
const char *
|
|
playback::context::
|
|
get_path_s_file () const
|
|
{
|
|
return m_tempdir->get_path_s_file ();
|
|
}
|
|
|
|
/* Get the path of the DSO object file "fake.so" file within the
|
|
tempdir. */
|
|
|
|
const char *
|
|
playback::context::
|
|
get_path_so_file () const
|
|
{
|
|
return m_tempdir->get_path_so_file ();
|
|
}
|
|
|
|
/* qsort comparator for comparing pairs of playback::source_line *,
|
|
ordering them by line number. */
|
|
|
|
static int
|
|
line_comparator (const void *lhs, const void *rhs)
|
|
{
|
|
const playback::source_line *line_lhs = \
|
|
*static_cast<const playback::source_line * const*> (lhs);
|
|
const playback::source_line *line_rhs = \
|
|
*static_cast<const playback::source_line * const*> (rhs);
|
|
return line_lhs->get_line_num () - line_rhs->get_line_num ();
|
|
}
|
|
|
|
/* qsort comparator for comparing pairs of playback::location *,
|
|
ordering them by column number. */
|
|
|
|
static int
|
|
location_comparator (const void *lhs, const void *rhs)
|
|
{
|
|
const playback::location *loc_lhs = \
|
|
*static_cast<const playback::location * const *> (lhs);
|
|
const playback::location *loc_rhs = \
|
|
*static_cast<const playback::location * const *> (rhs);
|
|
return loc_lhs->get_column_num () - loc_rhs->get_column_num ();
|
|
}
|
|
|
|
/* Our API allows locations to be created in arbitrary orders, but the
|
|
linemap API requires locations to be created in ascending order
|
|
as if we were tokenizing files.
|
|
|
|
This hook sorts all of the locations that have been created, and
|
|
calls into the linemap API, creating linemap entries in sorted order
|
|
for our locations. */
|
|
|
|
void
|
|
playback::context::
|
|
handle_locations ()
|
|
{
|
|
/* Create the source code locations, following the ordering rules
|
|
imposed by the linemap API.
|
|
|
|
line_table is a global. */
|
|
JIT_LOG_SCOPE (get_logger ());
|
|
int i;
|
|
source_file *file;
|
|
|
|
FOR_EACH_VEC_ELT (m_source_files, i, file)
|
|
{
|
|
linemap_add (line_table, LC_ENTER, false, file->get_filename (), 0);
|
|
|
|
/* Sort lines by ascending line numbers. */
|
|
file->m_source_lines.qsort (&line_comparator);
|
|
|
|
int j;
|
|
source_line *line;
|
|
FOR_EACH_VEC_ELT (file->m_source_lines, j, line)
|
|
{
|
|
int k;
|
|
location *loc;
|
|
|
|
/* Sort locations in line by ascending column numbers. */
|
|
line->m_locations.qsort (&location_comparator);
|
|
|
|
/* Determine maximum column within this line. */
|
|
gcc_assert (line->m_locations.length () > 0);
|
|
location *final_column =
|
|
line->m_locations[line->m_locations.length () - 1];
|
|
int max_col = final_column->get_column_num ();
|
|
|
|
linemap_line_start (line_table, line->get_line_num (), max_col);
|
|
FOR_EACH_VEC_ELT (line->m_locations, k, loc)
|
|
{
|
|
loc->m_srcloc = \
|
|
linemap_position_for_column (line_table, loc->get_column_num ());
|
|
}
|
|
}
|
|
|
|
linemap_add (line_table, LC_LEAVE, false, NULL, 0);
|
|
}
|
|
|
|
/* line_table should now be populated; every playback::location should
|
|
now have an m_srcloc. */
|
|
|
|
/* Now assign them to tree nodes as appropriate. */
|
|
std::pair<tree, location *> *cached_location;
|
|
|
|
FOR_EACH_VEC_ELT (m_cached_locations, i, cached_location)
|
|
{
|
|
tree t = cached_location->first;
|
|
location_t srcloc = cached_location->second->m_srcloc;
|
|
|
|
/* This covers expressions: */
|
|
if (CAN_HAVE_LOCATION_P (t))
|
|
SET_EXPR_LOCATION (t, srcloc);
|
|
else if (CODE_CONTAINS_STRUCT(TREE_CODE(t), TS_DECL_MINIMAL))
|
|
DECL_SOURCE_LOCATION (t) = srcloc;
|
|
else
|
|
{
|
|
/* Don't know how to set location on this node. */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We handle errors on a playback::context by adding them to the
|
|
corresponding recording::context. */
|
|
|
|
void
|
|
playback::context::
|
|
add_error (location *loc, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start (ap, fmt);
|
|
m_recording_ctxt->add_error_va (loc ? loc->get_recording_loc () : NULL,
|
|
fmt, ap);
|
|
va_end (ap);
|
|
}
|
|
|
|
/* We handle errors on a playback::context by adding them to the
|
|
corresponding recording::context. */
|
|
|
|
void
|
|
playback::context::
|
|
add_error_va (location *loc, const char *fmt, va_list ap)
|
|
{
|
|
m_recording_ctxt->add_error_va (loc ? loc->get_recording_loc () : NULL,
|
|
fmt, ap);
|
|
}
|
|
|
|
/* Report a diagnostic up to the jit context as an error,
|
|
so that the compilation is treated as a failure.
|
|
For now, any kind of diagnostic is treated as an error by the jit
|
|
API. */
|
|
|
|
void
|
|
playback::context::
|
|
add_diagnostic (struct diagnostic_context *diag_context,
|
|
struct diagnostic_info *diagnostic)
|
|
{
|
|
/* At this point the text has been formatted into the pretty-printer's
|
|
output buffer. */
|
|
pretty_printer *pp = diag_context->printer;
|
|
const char *text = pp_formatted_text (pp);
|
|
|
|
/* Get location information (if any) from the diagnostic.
|
|
The recording::context::add_error[_va] methods require a
|
|
recording::location. We can't lookup the playback::location
|
|
from the file/line/column since any playback location instances
|
|
may have been garbage-collected away by now, so instead we create
|
|
another recording::location directly. */
|
|
location_t gcc_loc = diagnostic_location (diagnostic);
|
|
recording::location *rec_loc = NULL;
|
|
if (gcc_loc)
|
|
{
|
|
expanded_location exploc = expand_location (gcc_loc);
|
|
if (exploc.file)
|
|
rec_loc = m_recording_ctxt->new_location (exploc.file,
|
|
exploc.line,
|
|
exploc.column,
|
|
false);
|
|
}
|
|
|
|
m_recording_ctxt->add_error (rec_loc, "%s", text);
|
|
pp_clear_output_area (pp);
|
|
}
|
|
|
|
/* Dealing with the linemap API. */
|
|
|
|
/* Construct a playback::location for a recording::location, if it
|
|
doesn't exist already. */
|
|
|
|
playback::location *
|
|
playback::context::
|
|
new_location (recording::location *rloc,
|
|
const char *filename,
|
|
int line,
|
|
int column)
|
|
{
|
|
/* Get the source_file for filename, creating if necessary. */
|
|
source_file *src_file = get_source_file (filename);
|
|
/* Likewise for the line within the file. */
|
|
source_line *src_line = src_file->get_source_line (line);
|
|
/* Likewise for the column within the line. */
|
|
location *loc = src_line->get_location (rloc, column);
|
|
return loc;
|
|
}
|
|
|
|
/* Deferred setting of the location for a given tree, by adding the
|
|
(tree, playback::location) pair to a list of deferred associations.
|
|
We will actually set the location on the tree later on once
|
|
the location_t for the playback::location exists. */
|
|
|
|
void
|
|
playback::context::
|
|
set_tree_location (tree t, location *loc)
|
|
{
|
|
gcc_assert (loc);
|
|
m_cached_locations.safe_push (std::make_pair (t, loc));
|
|
}
|
|
|
|
|
|
/* Construct a playback::source_file for the given source
|
|
filename, if it doesn't exist already. */
|
|
|
|
playback::source_file *
|
|
playback::context::
|
|
get_source_file (const char *filename)
|
|
{
|
|
/* Locate the file.
|
|
For simplicitly, this is currently a linear search.
|
|
Replace with a hash if this shows up in the profile. */
|
|
int i;
|
|
source_file *file;
|
|
tree ident_filename = get_identifier (filename);
|
|
|
|
FOR_EACH_VEC_ELT (m_source_files, i, file)
|
|
if (file->filename_as_tree () == ident_filename)
|
|
return file;
|
|
|
|
/* Not found. */
|
|
file = new source_file (ident_filename);
|
|
m_source_files.safe_push (file);
|
|
return file;
|
|
}
|
|
|
|
/* Constructor for gcc::jit::playback::source_file. */
|
|
|
|
playback::source_file::source_file (tree filename) :
|
|
m_source_lines (),
|
|
m_filename (filename)
|
|
{
|
|
}
|
|
|
|
/* Don't leak vec's internal buffer (in non-GC heap) when we are
|
|
GC-ed. */
|
|
|
|
void
|
|
playback::source_file::finalizer ()
|
|
{
|
|
m_source_lines.release ();
|
|
}
|
|
|
|
/* Construct a playback::source_line for the given line
|
|
within this source file, if one doesn't exist already. */
|
|
|
|
playback::source_line *
|
|
playback::source_file::
|
|
get_source_line (int line_num)
|
|
{
|
|
/* Locate the line.
|
|
For simplicitly, this is currently a linear search.
|
|
Replace with a hash if this shows up in the profile. */
|
|
int i;
|
|
source_line *line;
|
|
|
|
FOR_EACH_VEC_ELT (m_source_lines, i, line)
|
|
if (line->get_line_num () == line_num)
|
|
return line;
|
|
|
|
/* Not found. */
|
|
line = new source_line (this, line_num);
|
|
m_source_lines.safe_push (line);
|
|
return line;
|
|
}
|
|
|
|
/* Constructor for gcc::jit::playback::source_line. */
|
|
|
|
playback::source_line::source_line (source_file *file, int line_num) :
|
|
m_locations (),
|
|
m_source_file (file),
|
|
m_line_num (line_num)
|
|
{
|
|
}
|
|
|
|
/* Don't leak vec's internal buffer (in non-GC heap) when we are
|
|
GC-ed. */
|
|
|
|
void
|
|
playback::source_line::finalizer ()
|
|
{
|
|
m_locations.release ();
|
|
}
|
|
|
|
/* Construct a playback::location for the given column
|
|
within this line of a specific source file, if one doesn't exist
|
|
already. */
|
|
|
|
playback::location *
|
|
playback::source_line::
|
|
get_location (recording::location *rloc, int column_num)
|
|
{
|
|
int i;
|
|
location *loc;
|
|
|
|
/* Another linear search that probably should be a hash table. */
|
|
FOR_EACH_VEC_ELT (m_locations, i, loc)
|
|
if (loc->get_column_num () == column_num)
|
|
return loc;
|
|
|
|
/* Not found. */
|
|
loc = new location (rloc, this, column_num);
|
|
m_locations.safe_push (loc);
|
|
return loc;
|
|
}
|
|
|
|
/* Constructor for gcc::jit::playback::location. */
|
|
|
|
playback::location::location (recording::location *loc,
|
|
source_line *line,
|
|
int column_num) :
|
|
m_srcloc (UNKNOWN_LOCATION),
|
|
m_recording_loc (loc),
|
|
m_line (line),
|
|
m_column_num(column_num)
|
|
{
|
|
}
|
|
|
|
/* The active gcc::jit::playback::context instance. This is a singleton,
|
|
guarded by jit_mutex. */
|
|
|
|
playback::context *active_playback_ctxt;
|
|
|
|
} // namespace gcc::jit
|
|
|
|
} // namespace gcc
|