/* GCC-StarPU Copyright (C) 2011, 2012 INRIA GCC-StarPU 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 of the License, or (at your option) any later version. GCC-StarPU 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-StarPU. If not, see . */ /* Use extensions of the GNU C Library. */ #define _GNU_SOURCE 1 #include /* We must include starpu.h here, otherwise gcc will complain about a poisoned malloc in xmmintrin.h. */ #include /* for `STARPU_CPU' & co. */ /* #define ENABLE_TREE_CHECKING 1 */ #include #include #include #include #include #include #include /* for `optimize' */ #ifdef HAVE_C_FAMILY_C_COMMON_H # include #elif HAVE_C_COMMON_H # include #endif #ifdef HAVE_C_FAMILY_C_PRAGMA_H # include #elif HAVE_C_PRAGMA_H # include #endif #include #include #include #include #include #include #include #include #include #include #include /* Don't include the dreaded proprietary headers that we don't need anyway. In particular, this waives the obligation to reproduce their silly disclaimer. */ #define STARPU_DONT_INCLUDE_CUDA_HEADERS #ifndef STRINGIFY # define STRINGIFY_(x) # x # define STRINGIFY(x) STRINGIFY_ (x) #endif #ifdef __cplusplus extern "C" { #endif /* Declared with `C' linkage in . */ int plugin_is_GPL_compatible; /* The name of this plug-in. */ static const char plugin_name[] = "starpu"; /* Names of public attributes. */ static const char heap_allocated_attribute_name[] = "heap_allocated"; static const char registered_attribute_name[] = "registered"; /* Names of attributes used internally. */ static const char heap_allocated_orig_type_attribute_name[] = ".heap_allocated_original_type"; /* Cached function declarations. */ static tree unpack_fn; /* Targets supported by GCC-StarPU. */ static int supported_targets = 0 #ifdef STARPU_USE_CPU | STARPU_CPU #endif #ifdef STARPU_USE_CUDA | STARPU_CUDA #endif #ifdef STARPU_USE_OPENCL | STARPU_OPENCL #endif ; /* Forward declarations. */ static tree build_cpu_codelet_identifier (const_tree task); static bool implicit_cpu_task_implementation_p (const_tree fn); static bool heap_allocated_p (const_tree var_decl); static bool registered_p (const_tree var_decl); /* Compile-time assertions. */ #if STARPU_GNUC_PREREQ (4, 6) # define verify(cond, msg) _Static_assert ((cond), msg) #else # define verify(cond, msg) assert (cond); #endif /* Helpers. */ /* Return POINTER plus OFFSET, where OFFSET is in bytes. */ static tree pointer_plus (tree pointer, size_t offset) { gcc_assert (POINTER_TYPE_P (TREE_TYPE (pointer))); if (offset == 0) return pointer; else return build_binary_op (UNKNOWN_LOCATION, PLUS_EXPR, pointer, build_int_cstu (integer_type_node, offset), false); } /* Build a reference to the INDEXth element of ARRAY. `build_array_ref' is not exported, so we roll our own. FIXME: This version may not work for array types and doesn't do as much type-checking as `build_array_ref'. */ static tree array_ref (tree array, size_t index) { gcc_assert (POINTER_TYPE_P (TREE_TYPE (array))); return build_indirect_ref (UNKNOWN_LOCATION, pointer_plus (array, index), RO_ARRAY_INDEXING); } /* Return the number of elements of ARRAY_TYPE, or NULL_TREE if ARRAY_TYPE is an incomplete type. */ static tree array_type_element_count (location_t loc, const_tree array_type) { gcc_assert (TREE_CODE (array_type) == ARRAY_TYPE); tree count, domain = TYPE_DOMAIN (array_type); if (domain != NULL_TREE) { count = build_binary_op (loc, MINUS_EXPR, TYPE_MAX_VALUE (domain), TYPE_MIN_VALUE (domain), false); count = build_binary_op (loc, PLUS_EXPR, count, build_int_cstu (integer_type_node, 1), false); count = fold_convert (size_type_node, count); } else count = NULL_TREE; return count; } /* Debugging helpers. */ static tree build_printf (const char *, ...) __attribute__ ((format (printf, 1, 2))); static tree build_printf (const char *fmt, ...) { tree call; char *str; va_list args; va_start (args, fmt); vasprintf (&str, fmt, args); call = build_call_expr (builtin_decl_explicit (BUILT_IN_PUTS), 1, build_string_literal (strlen (str) + 1, str)); free (str); va_end (args); return call; } static tree build_hello_world (void) { return build_printf ("Hello, StarPU!"); } /* Pragmas. */ #define STARPU_PRAGMA_NAME_SPACE "starpu" static void handle_pragma_hello (struct cpp_reader *reader) { add_stmt (build_hello_world ()); } /* Process `#pragma starpu initialize'. TODO: Parse and initialize some of the fields of `starpu_conf'. */ static void handle_pragma_initialize (struct cpp_reader *reader) { static tree init_fn; LOOKUP_STARPU_FUNCTION (init_fn, "starpu_init"); location_t loc = cpp_peek_token (reader, 0)->src_loc; /* Call `starpu_init (NULL)'. */ tree init = build_call_expr (init_fn, 1, build_zero_cst (ptr_type_node)); /* Introduce a local variable to hold the error code. */ tree error_var = build_decl (loc, VAR_DECL, create_tmp_var_name (".initialize_error"), integer_type_node); DECL_CONTEXT (error_var) = current_function_decl; DECL_ARTIFICIAL (error_var) = true; tree assignment = build2 (INIT_EXPR, TREE_TYPE (error_var), error_var, init); tree cond = build3 (COND_EXPR, void_type_node, build2 (NE_EXPR, boolean_type_node, error_var, integer_zero_node), build_error_statements (loc, error_var, build_starpu_error_string, "failed to initialize StarPU"), NULL_TREE); tree stmts = NULL_TREE; append_to_statement_list (assignment, &stmts); append_to_statement_list (cond, &stmts); tree bind = build3 (BIND_EXPR, void_type_node, error_var, stmts, NULL_TREE); add_stmt (bind); } /* Process `#pragma starpu shutdown'. */ static void handle_pragma_shutdown (struct cpp_reader *reader) { static tree shutdown_fn; LOOKUP_STARPU_FUNCTION (shutdown_fn, "starpu_shutdown"); tree token; if (pragma_lex (&token) != CPP_EOF) error_at (cpp_peek_token (reader, 0)->src_loc, "junk after % pragma"); else /* Call `starpu_shutdown ()'. */ add_stmt (build_call_expr (shutdown_fn, 0)); } static void handle_pragma_wait (struct cpp_reader *reader) { if (task_implementation_p (current_function_decl)) { location_t loc; loc = cpp_peek_token (reader, 0)->src_loc; /* TODO: In the future we could generate a task for the continuation and have it depend on what's before here. */ error_at (loc, "task implementation is not allowed to wait"); } else { tree fndecl; fndecl = lookup_name (get_identifier ("starpu_task_wait_for_all")); gcc_assert (TREE_CODE (fndecl) == FUNCTION_DECL); add_stmt (build_call_expr (fndecl, 0)); } } /* Build a `starpu_vector_data_register' call for the COUNT elements pointed to by POINTER. */ static tree build_data_register_call (location_t loc, tree pointer, tree count) { tree pointer_type = TREE_TYPE (pointer); gcc_assert ((TREE_CODE (pointer_type) == ARRAY_TYPE && TYPE_DOMAIN (pointer_type) != NULL_TREE) || POINTER_TYPE_P (pointer_type)); gcc_assert (INTEGRAL_TYPE_P (TREE_TYPE (count))); static tree register_fn; LOOKUP_STARPU_FUNCTION (register_fn, "starpu_vector_data_register"); /* Introduce a local variable to hold the handle. */ tree handle_var = build_decl (loc, VAR_DECL, create_tmp_var_name (".handle"), ptr_type_node); DECL_CONTEXT (handle_var) = current_function_decl; DECL_ARTIFICIAL (handle_var) = true; DECL_INITIAL (handle_var) = NULL_TREE; /* If PTR is an array, take its address. */ tree actual_pointer = POINTER_TYPE_P (pointer_type) ? pointer : build_addr (pointer, current_function_decl); /* Build `starpu_vector_data_register (&HANDLE_VAR, 0, POINTER, COUNT, sizeof *POINTER)' */ tree call = build_call_expr (register_fn, 5, build_addr (handle_var, current_function_decl), build_zero_cst (uintptr_type_node), /* home node */ actual_pointer, count, size_in_bytes (TREE_TYPE (pointer_type))); return build3 (BIND_EXPR, void_type_node, handle_var, call, NULL_TREE); } /* Return a `starpu_data_unregister' call for VAR. */ static tree build_data_unregister_call (location_t loc, tree var) { static tree unregister_fn; LOOKUP_STARPU_FUNCTION (unregister_fn, "starpu_data_unregister"); /* If VAR is an array, take its address. */ tree pointer = POINTER_TYPE_P (TREE_TYPE (var)) ? var : build_addr (var, current_function_decl); /* Call `starpu_data_unregister (starpu_data_lookup (ptr))'. */ return build_call_expr (unregister_fn, 1, build_pointer_lookup (pointer)); } /* Process `#pragma starpu register VAR [COUNT]' and emit the corresponding `starpu_vector_data_register' call. */ static void handle_pragma_register (struct cpp_reader *reader) { tree args, ptr, count_arg; location_t loc; loc = cpp_peek_token (reader, 0)->src_loc; args = read_pragma_expressions ("register", loc); if (args == NULL_TREE) /* Parse error, presumably already handled by the parser. */ return; /* First argument should be a pointer expression. */ ptr = TREE_VALUE (args); args = TREE_CHAIN (args); if (ptr == error_mark_node) return; tree ptr_type; if (DECL_P (ptr)) { tree heap_attr = lookup_attribute (heap_allocated_orig_type_attribute_name, DECL_ATTRIBUTES (ptr)); if (heap_attr != NULL_TREE) /* PTR is `heap_allocated' so use its original array type to determine its size. */ ptr_type = TREE_VALUE (heap_attr); else ptr_type = TREE_TYPE (ptr); } else ptr_type = TREE_TYPE (ptr); if (ptr_type == NULL_TREE) { /* PTR is a type-less thing, such as a STRING_CST. */ error_at (loc, "invalid % argument"); return; } if (!POINTER_TYPE_P (ptr_type) && TREE_CODE (ptr_type) != ARRAY_TYPE) { error_at (loc, "%qE is neither a pointer nor an array", ptr); return; } /* Since we implicitly use sizeof (*PTR), `void *' is not allowed. */ if (VOID_TYPE_P (TREE_TYPE (ptr_type))) { error_at (loc, "pointers to % not allowed " "in % pragma"); return; } TREE_USED (ptr) = true; #ifdef DECL_READ_P if (DECL_P (ptr)) DECL_READ_P (ptr) = true; #endif if (TREE_CODE (ptr_type) == ARRAY_TYPE && !DECL_EXTERNAL (ptr) && !TREE_STATIC (ptr) && !(TREE_CODE (ptr) == VAR_DECL && heap_allocated_p (ptr)) && !MAIN_NAME_P (DECL_NAME (current_function_decl))) warning_at (loc, 0, "using an on-stack array as a task input " "considered unsafe"); /* Determine the number of elements in the vector. */ tree count = NULL_TREE; if (TREE_CODE (ptr_type) == ARRAY_TYPE) count = array_type_element_count (loc, ptr_type); /* Second argument is optional but should be an integer. */ count_arg = (args == NULL_TREE) ? NULL_TREE : TREE_VALUE (args); if (args != NULL_TREE) args = TREE_CHAIN (args); if (count_arg == NULL_TREE) { /* End of line reached: check whether the array size was determined. */ if (count == NULL_TREE) { error_at (loc, "cannot determine size of array %qE", ptr); return; } } else if (count_arg == error_mark_node) /* COUNT_ARG could not be parsed and an error was already reported. */ return; else if (!INTEGRAL_TYPE_P (TREE_TYPE (count_arg))) { error_at (loc, "%qE is not an integer", count_arg); return; } else { TREE_USED (count_arg) = true; #ifdef DECL_READ_P if (DECL_P (count_arg)) DECL_READ_P (count_arg) = true; #endif if (count != NULL_TREE) { /* The number of elements of this array was already determined. */ inform (loc, "element count can be omitted for bounded array %qE", ptr); if (count_arg != NULL_TREE) { if (TREE_CODE (count_arg) == INTEGER_CST) { if (!tree_int_cst_equal (count, count_arg)) error_at (loc, "specified element count differs " "from actual size of array %qE", ptr); } else /* Using a variable to determine the array size whereas the array size is actually known statically. This looks like unreasonable code, so error out. */ error_at (loc, "determining array size at run-time " "although array size is known at compile-time"); } } else count = count_arg; } /* Any remaining args? */ if (args != NULL_TREE) error_at (loc, "junk after % pragma"); /* Add a data register call. */ add_stmt (build_data_register_call (loc, ptr, count)); } /* Process `#pragma starpu acquire VAR' and emit the corresponding `starpu_data_acquire' call. */ static void handle_pragma_acquire (struct cpp_reader *reader) { static tree acquire_fn; LOOKUP_STARPU_FUNCTION (acquire_fn, "starpu_data_acquire"); tree args, var; location_t loc; loc = cpp_peek_token (reader, 0)->src_loc; args = read_pragma_expressions ("acquire", loc); if (args == NULL_TREE) return; var = TREE_VALUE (args); if (var == error_mark_node) return; else if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE && TREE_CODE (TREE_TYPE (var)) != ARRAY_TYPE) { error_at (loc, "%qE is neither a pointer nor an array", var); return; } else if (TREE_CHAIN (args) != NULL_TREE) error_at (loc, "junk after % pragma"); /* If VAR is an array, take its address. */ tree pointer = POINTER_TYPE_P (TREE_TYPE (var)) ? var : build_addr (var, current_function_decl); /* Call `starpu_data_acquire (starpu_data_lookup (ptr), STARPU_RW)'. TODO: Support modes other than RW. */ add_stmt (build_call_expr (acquire_fn, 2, build_pointer_lookup (pointer), build_int_cst (integer_type_node, STARPU_RW))); } /* Process `#pragma starpu release VAR' and emit the corresponding `starpu_data_release' call. */ static void handle_pragma_release (struct cpp_reader *reader) { static tree release_fn; LOOKUP_STARPU_FUNCTION (release_fn, "starpu_data_release"); tree args, var; location_t loc; loc = cpp_peek_token (reader, 0)->src_loc; args = read_pragma_expressions ("release", loc); if (args == NULL_TREE) return; var = TREE_VALUE (args); if (var == error_mark_node) return; else if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE && TREE_CODE (TREE_TYPE (var)) != ARRAY_TYPE) { error_at (loc, "%qE is neither a pointer nor an array", var); return; } else if (TREE_CHAIN (args) != NULL_TREE) error_at (loc, "junk after % pragma"); /* If VAR is an array, take its address. */ tree pointer = POINTER_TYPE_P (TREE_TYPE (var)) ? var : build_addr (var, current_function_decl); /* Call `starpu_data_release (starpu_data_lookup (ptr))'. */ add_stmt (build_call_expr (release_fn, 1, build_pointer_lookup (pointer))); } /* Process `#pragma starpu unregister VAR' and emit the corresponding `starpu_data_unregister' call. */ static void handle_pragma_unregister (struct cpp_reader *reader) { tree args, var; location_t loc; loc = cpp_peek_token (reader, 0)->src_loc; args = read_pragma_expressions ("unregister", loc); if (args == NULL_TREE) return; var = TREE_VALUE (args); if (var == error_mark_node) return; else if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE && TREE_CODE (TREE_TYPE (var)) != ARRAY_TYPE) { error_at (loc, "%qE is neither a pointer nor an array", var); return; } else if (TREE_CHAIN (args) != NULL_TREE) error_at (loc, "junk after % pragma"); add_stmt (build_data_unregister_call (loc, var)); } /* Handle the `debug_tree' pragma (for debugging purposes.) */ static void handle_pragma_debug_tree (struct cpp_reader *reader) { tree args, obj; location_t loc; loc = cpp_peek_token (reader, 0)->src_loc; args = read_pragma_expressions ("debug_tree", loc); if (args == NULL_TREE) /* Parse error, presumably already handled by the parser. */ return; obj = TREE_VALUE (args); args = TREE_CHAIN (args); if (obj == error_mark_node) return; if (args != NULL_TREE) warning_at (loc, 0, "extraneous arguments ignored"); inform (loc, "debug_tree:"); debug_tree (obj); printf ("\n"); } /* Handle the `#pragma starpu add_target TARGET', which tells GCC-StarPU to consider TARGET ("cpu", "opencl", etc.) as supported. This pragma is undocumented and only meant to be used for testing purposes. */ static void handle_pragma_add_target (struct cpp_reader *reader) { tree args, obj; location_t loc; loc = cpp_peek_token (reader, 0)->src_loc; args = read_pragma_expressions ("add_target", loc); if (args == NULL_TREE) /* Parse error, presumably already handled by the parser. */ return; obj = TREE_VALUE (args); args = TREE_CHAIN (args); if (obj == error_mark_node) return; if (args != NULL_TREE) warning_at (loc, 0, "extraneous arguments ignored"); if (TREE_CODE (obj) == STRING_CST) { int new_target = task_implementation_target_to_int (obj); if (obj == 0) error_at (loc, "unsupported target %qE", obj); else supported_targets |= new_target; } else error_at (loc, "expecting string literal"); } static void register_pragmas (void *gcc_data, void *user_data) { c_register_pragma (STARPU_PRAGMA_NAME_SPACE, "hello", handle_pragma_hello); c_register_pragma (STARPU_PRAGMA_NAME_SPACE, "debug_tree", handle_pragma_debug_tree); c_register_pragma (STARPU_PRAGMA_NAME_SPACE, "add_target", handle_pragma_add_target); c_register_pragma_with_expansion (STARPU_PRAGMA_NAME_SPACE, "initialize", handle_pragma_initialize); c_register_pragma (STARPU_PRAGMA_NAME_SPACE, "wait", handle_pragma_wait); c_register_pragma_with_expansion (STARPU_PRAGMA_NAME_SPACE, "register", handle_pragma_register); c_register_pragma_with_expansion (STARPU_PRAGMA_NAME_SPACE, "acquire", handle_pragma_acquire); c_register_pragma_with_expansion (STARPU_PRAGMA_NAME_SPACE, "release", handle_pragma_release); c_register_pragma_with_expansion (STARPU_PRAGMA_NAME_SPACE, "unregister", handle_pragma_unregister); c_register_pragma_with_expansion (STARPU_PRAGMA_NAME_SPACE, "opencl", handle_pragma_opencl); c_register_pragma (STARPU_PRAGMA_NAME_SPACE, "shutdown", handle_pragma_shutdown); } /* Attributes. */ /* Handle the `task' function attribute. */ static tree handle_task_attribute (tree *node, tree name, tree args, int flags, bool *no_add_attrs) { tree fn; fn = *node; /* Get rid of the `task' attribute by default so that FN isn't further processed when it's erroneous. */ *no_add_attrs = true; if (TREE_CODE (fn) != FUNCTION_DECL) error_at (DECL_SOURCE_LOCATION (fn), "% attribute only applies to functions"); else { if (!VOID_TYPE_P (TREE_TYPE (TREE_TYPE (fn)))) /* Raise an error but keep going to avoid spitting out too many errors at the user's face. */ error_at (DECL_SOURCE_LOCATION (fn), "task return type must be %"); if (count (pointer_type_p, TYPE_ARG_TYPES (TREE_TYPE (fn))) > STARPU_NMAXBUFS) error_at (DECL_SOURCE_LOCATION (fn), "maximum number of pointer parameters exceeded"); /* Turn FN into an actual task. */ taskify_function (fn); } /* Lookup & cache function declarations for later reuse. */ LOOKUP_STARPU_FUNCTION (unpack_fn, "starpu_codelet_unpack_args"); return NULL_TREE; } /* Handle the `task_implementation (WHERE, TASK)' attribute. WHERE is a string constant ("cpu", "cuda", etc.), and TASK is the identifier of a function declared with the `task' attribute. */ static tree handle_task_implementation_attribute (tree *node, tree name, tree args, int flags, bool *no_add_attrs) { location_t loc; tree fn, where, task_decl; /* FIXME:TODO: To change the order to (TASK, WHERE): tree cleanup_id = TREE_VALUE (TREE_VALUE (attr)); tree cleanup_decl = lookup_name (cleanup_id); */ fn = *node; where = TREE_VALUE (args); task_decl = TREE_VALUE (TREE_CHAIN (args)); if (implicit_cpu_task_implementation_p (task_decl)) /* TASK_DECL is actually a CPU implementation. Implicit CPU task implementations can lead to this situation, because the task is renamed and modified to become a CPU implementation. */ task_decl = task_implementation_task (task_decl); loc = DECL_SOURCE_LOCATION (fn); /* Get rid of the `task_implementation' attribute by default so that FN isn't further processed when it's erroneous. */ *no_add_attrs = true; /* Mark FN as used to placate `-Wunused-function' when FN is erroneous anyway. */ TREE_USED (fn) = true; if (TREE_CODE (fn) != FUNCTION_DECL) error_at (loc, "% attribute only applies to functions"); else if (TREE_CODE (where) != STRING_CST) error_at (loc, "string constant expected " "as the first % argument"); else if (TREE_CODE (task_decl) != FUNCTION_DECL) error_at (loc, "%qE is not a function", task_decl); else if (lookup_attribute (task_attribute_name, DECL_ATTRIBUTES (task_decl)) == NULL_TREE) error_at (loc, "function %qE lacks the % attribute", DECL_NAME (task_decl)); else if (TYPE_CANONICAL (TREE_TYPE (fn)) != TYPE_CANONICAL (TREE_TYPE (task_decl))) error_at (loc, "type differs from that of task %qE", DECL_NAME (task_decl)); else { /* Add FN to the list of implementations of TASK_DECL. */ add_task_implementation (task_decl, fn, where); /* Keep the attribute. */ *no_add_attrs = false; } return NULL_TREE; } /* Return true when VAR is an automatic variable with complete array type; otherwise, return false, and emit error messages mentioning ATTRIBUTE. */ static bool automatic_array_variable_p (const char *attribute, tree var) { gcc_assert (TREE_CODE (var) == VAR_DECL); location_t loc; loc = DECL_SOURCE_LOCATION (var); if (DECL_EXTERNAL (var)) error_at (loc, "attribute %qs cannot be used on external declarations", attribute); else if (TREE_PUBLIC (var) || TREE_STATIC (var)) { error_at (loc, "attribute %qs cannot be used on global variables", attribute); TREE_TYPE (var) = error_mark_node; } else if (TREE_CODE (TREE_TYPE (var)) != ARRAY_TYPE) { error_at (loc, "variable %qE must have an array type", DECL_NAME (var)); TREE_TYPE (var) = error_mark_node; } else if (TYPE_SIZE (TREE_TYPE (var)) == NULL_TREE) { error_at (loc, "variable %qE has an incomplete array type", DECL_NAME (var)); TREE_TYPE (var) = error_mark_node; } else return true; return false; } /* Handle the `heap_allocated' attribute on variable *NODE. */ static tree handle_heap_allocated_attribute (tree *node, tree name, tree args, int flags, bool *no_add_attrs) { tree var = *node; if (automatic_array_variable_p (heap_allocated_attribute_name, var)) { /* Turn VAR into a pointer that feels like an array. This is what's done for PARM_DECLs that have an array type. */ location_t loc = DECL_SOURCE_LOCATION (var); tree array_type = TREE_TYPE (var); tree element_type = TREE_TYPE (array_type); tree pointer_type = build_pointer_type (element_type); /* Keep a copy of VAR's original type. */ DECL_ATTRIBUTES (var) = tree_cons (get_identifier (heap_allocated_orig_type_attribute_name), array_type, DECL_ATTRIBUTES (var)); TREE_TYPE (var) = pointer_type; DECL_SIZE (var) = TYPE_SIZE (pointer_type); DECL_SIZE_UNIT (var) = TYPE_SIZE_UNIT (pointer_type); DECL_ALIGN (var) = TYPE_ALIGN (pointer_type); DECL_USER_ALIGN (var) = false; DECL_MODE (var) = TYPE_MODE (pointer_type); tree malloc_fn = lookup_name (get_identifier ("starpu_malloc")); gcc_assert (malloc_fn != NULL_TREE); tree alloc = build_call_expr (malloc_fn, 2, build_addr (var, current_function_decl), TYPE_SIZE_UNIT (array_type)); TREE_SIDE_EFFECTS (alloc) = true; /* Add a destructor for VAR. Instead of consing the `cleanup' attribute for VAR, directly use `push_cleanup'. This guarantees that CLEANUP_ID is looked up in the right context, and allows us to pass VAR directly to `starpu_free', instead of `&VAR'. TODO: Provide a way to disable this. */ static tree cleanup_decl; LOOKUP_STARPU_FUNCTION (cleanup_decl, "starpu_free"); if (registered_p (var)) { /* A `registered' attribute has already been processed, and thus a cleanup for it has been pushed. However, we want that cleanup to appear before ours, and our allocation to appear before the registration, so swap them. */ tree_stmt_iterator it; tree parent, try_finally, registration; #ifdef stmt_list_stack # ifdef VEC_index /* 4.7 */ gcc_assert (VEC_length (tree, stmt_list_stack) > 1); parent = VEC_index (tree, stmt_list_stack, VEC_length (tree, stmt_list_stack) - 2); # else # error not ported to 4.8! # endif #else /* 4.6 and before */ parent = TREE_CHAIN (cur_stmt_list); #endif gcc_assert (parent != NULL_TREE && TREE_CODE (parent) == STATEMENT_LIST); it = tsi_last (parent); try_finally = tsi_stmt (it); gcc_assert (TREE_CODE (try_finally) == TRY_FINALLY_EXPR); tsi_prev (&it); registration = build_data_register_call (loc, var, array_type_element_count (loc, array_type)); add_stmt (registration); *tsi_stmt_ptr (it) = alloc; push_cleanup (var, build_data_unregister_call (loc, var), false); TREE_OPERAND (try_finally, 1) = build_call_expr (cleanup_decl, 1, var); } else { /* Push the allocation and cleanup in order. */ add_stmt (alloc); push_cleanup (var, build_call_expr (cleanup_decl, 1, var), false); } /* Keep the attribute. */ *no_add_attrs = false; } return NULL_TREE; } /* Handle the `registered' attribute on variable *NODE. */ static tree handle_registered_attribute (tree *node, tree name, tree args, int flags, bool *no_add_attrs) { location_t loc; tree var = *node; loc = DECL_SOURCE_LOCATION (var); bool heap_p = heap_allocated_p (var); /* When VAR has the `heap_allocated' attribute, we know it has a complete array type. */ if (heap_p || automatic_array_variable_p (registered_attribute_name, var)) { /* FIXME: This warning cannot be emitted here, because the `heap_allocated' attribute may be processed later. */ /* if (!heap_p */ /* && !MAIN_NAME_P (DECL_NAME (current_function_decl))) */ /* warning_at (loc, 0, "using an on-stack array as a task input " */ /* "considered unsafe"); */ tree ptr_type, heap_attr = lookup_attribute (heap_allocated_orig_type_attribute_name, DECL_ATTRIBUTES (var)); if (heap_attr != NULL_TREE) /* PTR is `heap_allocated' so use its original array type to determine its size. */ ptr_type = TREE_VALUE (heap_attr); else ptr_type = TREE_TYPE (var); tree count = array_type_element_count (loc, ptr_type); add_stmt (build_data_register_call (loc, var, count)); push_cleanup (var, build_data_unregister_call (DECL_SOURCE_LOCATION (var), var), false); } return NULL_TREE; } /* Handle the `output' attribute on type *NODE, which should be the type of a PARM_DECL of a task or task implementation. */ static tree handle_output_attribute (tree *node, tree name, tree args, int flags, bool *no_add_attrs) { tree type = *node; gcc_assert (TYPE_P (type)); if (!POINTER_TYPE_P (type) && TREE_CODE (type) != ARRAY_TYPE) error ("% attribute not allowed for non-pointer types"); else /* Keep the attribute. */ *no_add_attrs = false; return NULL_TREE; } /* Return true when FN is an implicit CPU task implementation. */ static bool implicit_cpu_task_implementation_p (const_tree fn) { if (task_implementation_p (fn) && task_implementation_where (fn) == STARPU_CPU) { /* XXX: Hackish heuristic. */ const_tree cpu_id; cpu_id = build_cpu_codelet_identifier (task_implementation_task (fn)); return cpu_id == DECL_NAME (fn); } return false; } /* Return true when VAR_DECL has the `heap_allocated' attribute. */ static bool heap_allocated_p (const_tree var_decl) { gcc_assert (TREE_CODE (var_decl) == VAR_DECL); return lookup_attribute (heap_allocated_attribute_name, DECL_ATTRIBUTES (var_decl)) != NULL_TREE; } /* Return true when VAR_DECL has the `registered' attribute. */ static bool registered_p (const_tree var_decl) { gcc_assert (TREE_CODE (var_decl) == VAR_DECL); return lookup_attribute (registered_attribute_name, DECL_ATTRIBUTES (var_decl)) != NULL_TREE; } static void register_task_attributes (void *gcc_data, void *user_data) { static const struct attribute_spec task_attr = { task_attribute_name, 0, 0, true, false, false, handle_task_attribute #ifdef HAVE_ATTRIBUTE_SPEC_AFFECTS_TYPE_IDENTITY , false #endif }; static const struct attribute_spec task_implementation_attr = { task_implementation_attribute_name, 2, 2, true, false, false, handle_task_implementation_attribute #ifdef HAVE_ATTRIBUTE_SPEC_AFFECTS_TYPE_IDENTITY , false #endif }; static const struct attribute_spec heap_allocated_attr = { heap_allocated_attribute_name, 0, 0, true, false, false, handle_heap_allocated_attribute #ifdef HAVE_ATTRIBUTE_SPEC_AFFECTS_TYPE_IDENTITY , false #endif }; static const struct attribute_spec registered_attr = { registered_attribute_name, 0, 0, true, false, false, handle_registered_attribute #ifdef HAVE_ATTRIBUTE_SPEC_AFFECTS_TYPE_IDENTITY , false #endif }; static const struct attribute_spec output_attr = { output_attribute_name, 0, 0, true, true, false, handle_output_attribute #ifdef HAVE_ATTRIBUTE_SPEC_AFFECTS_TYPE_IDENTITY , true /* affects type identity */ #endif }; register_attribute (&task_attr); register_attribute (&task_implementation_attr); register_attribute (&heap_allocated_attr); register_attribute (®istered_attr); register_attribute (&output_attr); } /* Return the type of a codelet function, i.e., `void (*) (void **, void *)'. */ static tree build_codelet_wrapper_type (void) { tree void_ptr_ptr; void_ptr_ptr = build_pointer_type (ptr_type_node); return build_function_type_list (void_type_node, void_ptr_ptr, ptr_type_node, NULL_TREE); } /* Return an identifier for the wrapper of TASK_IMPL, a task implementation. */ static tree build_codelet_wrapper_identifier (tree task_impl) { static const char suffix[] = ".task_implementation_wrapper"; tree id; char *cl_name; const char *task_name; id = DECL_NAME (task_impl); task_name = IDENTIFIER_POINTER (id); cl_name = (char *) alloca (IDENTIFIER_LENGTH (id) + strlen (suffix) + 1); memcpy (cl_name, task_name, IDENTIFIER_LENGTH (id)); strcpy (&cl_name[IDENTIFIER_LENGTH (id)], suffix); return get_identifier (cl_name); } /* Return a function of type `void (*) (void **, void *)' that calls function TASK_IMPL, the FUNCTION_DECL of a task implementation whose prototype may be arbitrary. */ static tree build_codelet_wrapper_definition (tree task_impl) { location_t loc; tree task_decl, wrapper_name, decl; loc = DECL_SOURCE_LOCATION (task_impl); task_decl = task_implementation_task (task_impl); wrapper_name = build_codelet_wrapper_identifier (task_impl); decl = build_decl (loc, FUNCTION_DECL, wrapper_name, build_codelet_wrapper_type ()); local_define (tree, build_local_var, (const_tree type)) { tree var, t; const char *seed; t = TREE_VALUE (type); seed = POINTER_TYPE_P (t) ? "pointer_arg" : "scalar_arg"; var = build_decl (loc, VAR_DECL, create_tmp_var_name (seed), t); DECL_CONTEXT (var) = decl; DECL_ARTIFICIAL (var) = true; return var; }; /* Return the body of the wrapper, which unpacks `cl_args' and calls the user-defined task implementation. */ local_define (tree, build_body, (tree wrapper_decl, tree vars)) { bool opencl_p; tree stmts = NULL, call, v; VEC(tree, gc) *args; opencl_p = (task_implementation_where (task_impl) == STARPU_OPENCL); /* Build `var0 = STARPU_VECTOR_GET_PTR (buffers[0]); ...' or `var0 = STARPU_VECTOR_GET_DEV_HANDLE (buffers[0])' for OpenCL. */ size_t index = 0; for (v = vars; v != NULL_TREE; v = TREE_CHAIN (v)) { if (POINTER_TYPE_P (TREE_TYPE (v))) { /* Compute `void *VDESC = buffers[0];'. */ tree vdesc = array_ref (DECL_ARGUMENTS (wrapper_decl), index); /* Use the right field, depending on OPENCL_P. */ size_t offset = opencl_p ? offsetof (struct starpu_vector_interface, dev_handle) : offsetof (struct starpu_vector_interface, ptr); gcc_assert (POINTER_TYPE_P (TREE_TYPE (vdesc))); /* Compute `type *PTR = *(type **) VDESC;'. */ tree ptr = build_indirect_ref (UNKNOWN_LOCATION, fold_convert (build_pointer_type (TREE_TYPE (v)), pointer_plus (vdesc, offset)), RO_ARRAY_INDEXING); append_to_statement_list (build2 (MODIFY_EXPR, TREE_TYPE (v), v, ptr), &stmts); index++; } } /* Build `starpu_codelet_unpack_args (cl_args, &var1, &var2, ...)'. */ args = NULL; VEC_safe_push (tree, gc, args, TREE_CHAIN (DECL_ARGUMENTS (wrapper_decl))); for (v = vars; v != NULL_TREE; v = TREE_CHAIN (v)) { if (!POINTER_TYPE_P (TREE_TYPE (v))) VEC_safe_push (tree, gc, args, build_addr (v, wrapper_decl)); } if (VEC_length (tree, args) > 1) { call = build_call_expr_loc_vec (UNKNOWN_LOCATION, unpack_fn, args); TREE_SIDE_EFFECTS (call) = 1; append_to_statement_list (call, &stmts); } /* Build `my_task_impl (var1, var2, ...)'. */ args = NULL; for (v = vars; v != NULL_TREE; v = TREE_CHAIN (v)) VEC_safe_push (tree, gc, args, v); call = build_call_expr_loc_vec (UNKNOWN_LOCATION, task_impl, args); TREE_SIDE_EFFECTS (call) = 1; append_to_statement_list (call, &stmts); tree bind; bind = build3 (BIND_EXPR, void_type_node, vars, stmts, DECL_INITIAL (wrapper_decl)); TREE_TYPE (bind) = TREE_TYPE (TREE_TYPE (wrapper_decl)); return bind; }; /* Return the parameter list of the wrapper: `(void **BUFFERS, void *CL_ARGS)'. */ local_define (tree, build_parameters, (tree wrapper_decl)) { tree param1, param2; param1 = build_decl (loc, PARM_DECL, create_tmp_var_name ("buffers"), build_pointer_type (ptr_type_node)); DECL_ARG_TYPE (param1) = ptr_type_node; DECL_CONTEXT (param1) = wrapper_decl; TREE_USED (param1) = true; param2 = build_decl (loc, PARM_DECL, create_tmp_var_name ("cl_args"), ptr_type_node); DECL_ARG_TYPE (param2) = ptr_type_node; DECL_CONTEXT (param2) = wrapper_decl; TREE_USED (param2) = true; return chainon (param1, param2); }; tree vars, result; vars = map (build_local_var, list_remove (void_type_p, TYPE_ARG_TYPES (TREE_TYPE (task_decl)))); DECL_CONTEXT (decl) = NULL_TREE; DECL_ARGUMENTS (decl) = build_parameters (decl); result = build_decl (loc, RESULT_DECL, NULL_TREE, void_type_node); DECL_CONTEXT (result) = decl; DECL_ARTIFICIAL (result) = true; DECL_IGNORED_P (result) = true; DECL_RESULT (decl) = result; DECL_INITIAL (decl) = build_block (vars, NULL_TREE, decl, NULL_TREE); DECL_SAVED_TREE (decl) = build_body (decl, vars); TREE_PUBLIC (decl) = TREE_PUBLIC (task_impl); TREE_STATIC (decl) = true; TREE_USED (decl) = true; DECL_ARTIFICIAL (decl) = true; DECL_EXTERNAL (decl) = false; DECL_UNINLINABLE (decl) = true; rest_of_decl_compilation (decl, true, 0); struct function *prev_cfun = cfun; set_cfun (NULL); allocate_struct_function (decl, false); cfun->function_end_locus = DECL_SOURCE_LOCATION (task_impl); cgraph_finalize_function (decl, false); /* Mark DECL as needed so that it doesn't get removed by `cgraph_remove_unreachable_nodes' when it's not public. */ cgraph_mark_needed_node (cgraph_get_node (decl)); set_cfun (prev_cfun); return decl; } /* Define one wrapper function for each implementation of TASK. TASK should be the FUNCTION_DECL of a task. */ static void define_codelet_wrappers (tree task) { local_define (void, define, (tree task_impl)) { tree wrapper_def; wrapper_def = build_codelet_wrapper_definition (task_impl); DECL_ATTRIBUTES (task_impl) = tree_cons (get_identifier (task_implementation_wrapper_attribute_name), wrapper_def, DECL_ATTRIBUTES (task_impl)); }; for_each (define, task_implementation_list (task)); } /* Return the identifier for an automatically-generated CPU codelet of TASK. */ static tree build_cpu_codelet_identifier (const_tree task) { static const char suffix[] = ".cpu_implementation"; tree id; char *cl_name; const char *task_name; id = DECL_NAME (task); task_name = IDENTIFIER_POINTER (id); cl_name = (char *) alloca (IDENTIFIER_LENGTH (id) + strlen (suffix) + 1); memcpy (cl_name, task_name, IDENTIFIER_LENGTH (id)); strcpy (&cl_name[IDENTIFIER_LENGTH (id)], suffix); return get_identifier (cl_name); } static void handle_pre_genericize (void *gcc_data, void *user_data) { tree fn = (tree) gcc_data; gcc_assert (TREE_CODE (fn) == FUNCTION_DECL); if (task_p (fn) && TREE_STATIC (fn)) { /* The user defined a body for task FN, which we interpret as being the body of an implicit CPU task implementation for FN. Thus, rename FN and turn it into the "cpu" implementation of a task that we create under FN's original name (this is easier than moving the body to a different function, which would require traversing the body to rewrite all references to FN to point to the new function.) Later, `lower_starpu' rewrites calls to FN as calls to the newly created task. */ tree task_name = DECL_NAME (fn); tree cpu_impl = fn; DECL_NAME (cpu_impl) = build_cpu_codelet_identifier (fn); if (verbose_output_p) inform (DECL_SOURCE_LOCATION (fn), "implicit CPU implementation renamed from %qE to %qE", task_name, DECL_NAME (cpu_impl)); tree task = build_decl (DECL_SOURCE_LOCATION (fn), FUNCTION_DECL, task_name, TREE_TYPE (fn)); TREE_PUBLIC (task) = TREE_PUBLIC (fn); TREE_PUBLIC (cpu_impl) = false; taskify_function (task); /* Inherit the task implementation list from FN. */ tree impls = lookup_attribute (task_implementation_list_attribute_name, DECL_ATTRIBUTES (fn)); gcc_assert (impls != NULL_TREE); impls = TREE_VALUE (impls); DECL_ATTRIBUTES (task) = tree_cons (get_identifier (task_implementation_list_attribute_name), impls, DECL_ATTRIBUTES (task)); /* Make CPU_IMPL an implementation of FN. */ DECL_ATTRIBUTES (cpu_impl) = tree_cons (get_identifier (task_implementation_attribute_name), tree_cons (NULL_TREE, build_string (3, "cpu"), tree_cons (NULL_TREE, task, NULL_TREE)), NULL_TREE); add_task_implementation (task, cpu_impl, build_string (3, "cpu")); /* And now, process CPU_IMPL. */ } if (task_implementation_p (fn)) { tree task = task_implementation_task (fn); if (!TREE_STATIC (task)) { /* TASK lacks a body. Declare its codelet, intantiate its codelet wrappers, and its body in this compilation unit. */ /* Declare TASK's codelet. It cannot be defined yet because the complete list of tasks isn't available at this point. */ declare_codelet (task); /* Build its body. */ current_function_decl = task; define_task (task); current_function_decl = fn; /* Compile TASK's body. */ rest_of_decl_compilation (task, true, 0); allocate_struct_function (task, false); cgraph_finalize_function (task, false); cgraph_mark_needed_node (cgraph_get_node (task)); } } } /* Raise warnings if TASK doesn't meet the basic criteria. */ static void validate_task (tree task) { gcc_assert (task_p (task)); int where = task_where (task); /* If TASK has no implementations, things will barf elsewhere anyway. */ if (task_implementation_list (task) != NULL_TREE) if ((where & supported_targets) == 0) error_at (DECL_SOURCE_LOCATION (task), "none of the implementations of task %qE can be used", DECL_NAME (task)); } /* Raise an error when IMPL doesn't satisfy the constraints of a task implementations, such as not invoking another task. */ static void validate_task_implementation (tree impl) { gcc_assert (task_implementation_p (impl)); const struct cgraph_node *cgraph; const struct cgraph_edge *callee; cgraph = cgraph_get_node (impl); /* When a definition of IMPL is available, check its callees. */ if (cgraph != NULL) for (callee = cgraph->callees; callee != NULL; callee = callee->next_callee) { if (task_p (callee->callee->decl)) { location_t loc; loc = gimple_location (callee->call_stmt); error_at (loc, "task %qE cannot be invoked from task implementation %qE", DECL_NAME (callee->callee->decl), DECL_NAME (impl)); } } } static unsigned int lower_starpu (void) { tree fndecl; const struct cgraph_node *cgraph; const struct cgraph_edge *callee; fndecl = current_function_decl; gcc_assert (TREE_CODE (fndecl) == FUNCTION_DECL); if (task_p (fndecl)) { /* Make sure the task and its implementations are valid. */ validate_task (fndecl); for_each (validate_task_implementation, task_implementation_list (fndecl)); /* Generate a `struct starpu_codelet' structure and a wrapper function for each implementation of TASK_DECL. This cannot be done earlier because we need to have a complete list of task implementations. */ define_codelet_wrappers (fndecl); tree cl_def = task_codelet_declaration (fndecl); DECL_INITIAL (cl_def) = build_codelet_initializer (fndecl); TREE_STATIC (cl_def) = true; DECL_EXTERNAL (cl_def) = false; varpool_finalize_decl (cl_def); } /* This pass should occur after `build_cgraph_edges'. */ cgraph = cgraph_get_node (fndecl); gcc_assert (cgraph != NULL); if (MAIN_NAME_P (DECL_NAME (fndecl))) { /* Check whether FNDECL initializes StarPU and emit a warning if it doesn't. */ bool initialized; for (initialized = false, callee = cgraph->callees; !initialized && callee != NULL; callee = callee->next_callee) { initialized = DECL_NAME (callee->callee->decl) == get_identifier ("starpu_init"); } if (!initialized) warning_at (DECL_SOURCE_LOCATION (fndecl), 0, "%qE does not initialize StarPU", DECL_NAME (fndecl)); } for (callee = cgraph->callees; callee != NULL; callee = callee->next_callee) { gcc_assert (callee->callee != NULL); tree callee_decl, caller_decl; callee_decl = callee->callee->decl; caller_decl = callee->caller->decl; if (implicit_cpu_task_implementation_p (callee_decl) && !DECL_ARTIFICIAL (caller_decl)) { /* Rewrite the call to point to the actual task beneath CALLEE_DECL. */ callee_decl = task_implementation_task (callee_decl); if (verbose_output_p) inform (gimple_location (callee->call_stmt), "call to %qE rewritten as a call to task %qE", DECL_NAME (callee->callee->decl), DECL_NAME (callee_decl)); gimple_call_set_fn (callee->call_stmt, build_addr (callee_decl, callee->caller->decl)); } if (task_p (callee_decl)) { if (verbose_output_p) inform (gimple_location (callee->call_stmt), "%qE calls task %qE", DECL_NAME (fndecl), DECL_NAME (callee_decl)); } } return 0; } static struct opt_pass pass_lower_starpu = { designated_field_init (type, GIMPLE_PASS), designated_field_init (name, "lower_starpu"), designated_field_init (gate, NULL), designated_field_init (execute, lower_starpu), /* The rest is zeroed. */ }; /* Initialization. */ /* Directory where to look up instead of `STARPU_INCLUDE_DIR'. */ static const char *include_dir; static void define_cpp_macros (void *gcc_data, void *user_data) { cpp_define (parse_in, "STARPU_GCC_PLUGIN=0"); if (include_dir) { /* Get the header from the user-specified directory. This is useful when running the test suite, before StarPU is installed. */ char header[strlen (include_dir) + sizeof ("/starpu.h")]; strcpy (header, include_dir); strcat (header, "/starpu.h"); cpp_push_include (parse_in, header); } else cpp_push_include (parse_in, STARPU_INCLUDE_DIR "/starpu.h"); } int plugin_init (struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) { /* `plugin_default_version_check' happens to be stricter than necessary (for instance, it fails when the `buildstamp' field of the plug-in doesn't match that of GCC), so write our own check and make more relax and more verbose. */ #define VERSION_CHECK(field) \ do \ { \ if (strcmp (gcc_version. field, version-> field) != 0) \ { \ error_at (UNKNOWN_LOCATION, "plug-in version check for `" \ STRINGIFY (field) "' failed: expected `%s', " \ "got `%s'", \ gcc_version. field, version-> field); \ return 1; \ } \ } \ while (0) VERSION_CHECK (basever); /* e.g., "4.6.2" */ VERSION_CHECK (devphase); VERSION_CHECK (revision); VERSION_CHECK (configuration_arguments); #undef VERSION_CHECK register_callback (plugin_name, PLUGIN_START_UNIT, define_cpp_macros, NULL); register_callback (plugin_name, PLUGIN_PRAGMAS, register_pragmas, NULL); register_callback (plugin_name, PLUGIN_ATTRIBUTES, register_task_attributes, NULL); register_callback (plugin_name, PLUGIN_PRE_GENERICIZE, handle_pre_genericize, NULL); /* Register our pass so that it happens after `build_cgraph_edges' has been done. */ struct register_pass_info pass_info = { designated_field_init (pass, &pass_lower_starpu), designated_field_init (reference_pass_name, "*build_cgraph_edges"), designated_field_init (ref_pass_instance_number, 1), designated_field_init (pos_op, PASS_POS_INSERT_AFTER) }; register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info); #if HAVE_DECL_PTR_DEREFS_MAY_ALIAS_P /* This warning pass is only available when `ptr_derefs_may_alias_p' is available, with GCC >= 4.6. */ struct register_pass_info pass_info2 = { designated_field_init (pass, &pass_warn_starpu_unregistered), designated_field_init (reference_pass_name, "ssa"), designated_field_init (ref_pass_instance_number, 1), designated_field_init (pos_op, PASS_POS_INSERT_AFTER) }; if (optimize) /* Using `TODO_rebuild_alias' allows us to have more accurate aliasing info. However, `TODO_rebuild_alias' cannot be used when optimizations are turned off. See for details. */ pass_warn_starpu_unregistered.todo_flags_start = TODO_rebuild_alias; register_callback (plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &pass_info2); #endif include_dir = getenv ("STARPU_GCC_INCLUDE_DIR"); opencl_include_dirs = tree_cons (NULL_TREE, build_string (1, "."), NULL_TREE); int arg; for (arg = 0; arg < plugin_info->argc; arg++) { if (strcmp (plugin_info->argv[arg].key, "include-dir") == 0) { if (plugin_info->argv[arg].value == NULL) error_at (UNKNOWN_LOCATION, "missing directory name for option " "%<-fplugin-arg-starpu-include-dir%>"); else /* XXX: We assume that `value' has an infinite lifetime. */ include_dir = plugin_info->argv[arg].value; } else if (strcmp (plugin_info->argv[arg].key, "opencl-include-dir") == 0) { if (plugin_info->argv[arg].value == NULL) error_at (UNKNOWN_LOCATION, "missing directory name for option " "%<-fplugin-arg-starpu-opencl-include-dir%>"); else { tree dir = build_string (strlen (plugin_info->argv[arg].value), plugin_info->argv[arg].value); opencl_include_dirs = tree_cons (NULL_TREE, dir, opencl_include_dirs); } } else if (strcmp (plugin_info->argv[arg].key, "verbose") == 0) verbose_output_p = true; else error_at (UNKNOWN_LOCATION, "invalid StarPU plug-in argument %qs", plugin_info->argv[arg].key); } /* Keep the directories in the order in which they appear. */ opencl_include_dirs = nreverse (opencl_include_dirs); return 0; } #ifdef __cplusplus } #endif