diff --git a/include/api/otter-task-graph/otter-task-graph-user.h b/include/api/otter-task-graph/otter-task-graph-user.h index 08ae8ad..0778a6b 100644 --- a/include/api/otter-task-graph/otter-task-graph-user.h +++ b/include/api/otter-task-graph/otter-task-graph-user.h @@ -45,8 +45,7 @@ credit: https://stackoverflow.com/a/48045656 */ #define OTTER_IMPL_THIRD_ARG(a, b, c, ...) c -#define OTTER_IMPL_VA_OPT_AVAIL_I(...) \ - OTTER_IMPL_THIRD_ARG(__VA_OPT__(, ), 1, 0, ) +#define OTTER_IMPL_VA_OPT_AVAIL_I(...) OTTER_IMPL_THIRD_ARG(__VA_OPT__(, ), 1, 0, ) #define OTTER_IMPL_VA_OPT_AVAIL OTTER_IMPL_VA_OPT_AVAIL_I(?) /* my addition to make variadic macros agnostic of __VA_OPT__ support */ @@ -112,10 +111,9 @@ * @param ...: Variadic arguments for use with \p label. * */ -#define OTTER_INIT_TASK(task, parent, add_to_pool, label, ...) \ - task = otterTaskInitialise(parent, -1, add_to_pool, true, \ - OTTER_SOURCE_LOCATION(), \ - label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) +#define OTTER_INIT_TASK(task, parent, add_to_pool, label, ...) \ + task = otterTaskInitialise(parent, -1, add_to_pool, true, OTTER_SOURCE_LOCATION(), \ + label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) /** * @brief Declare and initialise a new task handle in the current scope. @@ -141,10 +139,9 @@ * @param ...: Variadic arguments for use with \p label. * */ -#define OTTER_DEFINE_TASK(task, parent, add_to_pool, label, ...) \ - OTTER_DECLARE_HANDLE(task); \ - OTTER_INIT_TASK(task, parent, add_to_pool, \ - label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) +#define OTTER_DEFINE_TASK(task, parent, add_to_pool, label, ...) \ + OTTER_DECLARE_HANDLE(task); \ + OTTER_INIT_TASK(task, parent, add_to_pool, label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) /** * @brief Add a task handle to the task pool with the given label. @@ -157,8 +154,7 @@ * @param ...: Variadic arguments for use with \p label. * */ -#define OTTER_POOL_ADD(task, label, ...) \ - otterTaskPushLabel(task, label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) +#define OTTER_POOL_ADD(task, label, ...) otterTaskPushLabel(task, label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) /** * @brief Remove a task from the task pool with the given label. \p task is @@ -172,8 +168,7 @@ * @param ...: Variadic arguments for use with \p label. * */ -#define OTTER_POOL_POP(task, label, ...) \ - task = otterTaskPopLabel(label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) +#define OTTER_POOL_POP(task, label, ...) task = otterTaskPopLabel(label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) /** * @brief Borrow a task from the task pool with the given label. \p task is @@ -192,8 +187,7 @@ * @param ...: Variadic arguments for use with \p label. * */ -#define OTTER_POOL_BORROW(task, label, ...) \ - task = otterTaskBorrowLabel(label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) +#define OTTER_POOL_BORROW(task, label, ...) task = otterTaskBorrowLabel(label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) /** * @brief Declare a handle in the current scope, assigning a task removed from @@ -208,9 +202,9 @@ * @param ...: Variadic arguments for use with \p label. * */ -#define OTTER_POOL_DECL_POP(task, label, ...) \ - OTTER_DECLARE_HANDLE(task); \ - OTTER_POOL_POP(task, label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) +#define OTTER_POOL_DECL_POP(task, label, ...) \ + OTTER_DECLARE_HANDLE(task); \ + OTTER_POOL_POP(task, label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) /** * @brief Declare a handle in the current scope, assigning a task borrowed from @@ -230,9 +224,9 @@ * @param ...: Variadic arguments for use with \p label. * */ -#define OTTER_POOL_DECL_BORROW(task, label, ...) \ - OTTER_DECLARE_HANDLE(task); \ - OTTER_POOL_BORROW(task, label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) +#define OTTER_POOL_DECL_BORROW(task, label, ...) \ + OTTER_DECLARE_HANDLE(task); \ + OTTER_POOL_BORROW(task, label OTTER_IMPL_PASS_ARGS(__VA_ARGS__)) /** * @brief Record the start of the code represented by the given task handle. @@ -271,8 +265,7 @@ * * @see #OTTER_TASK_END */ -#define OTTER_TASK_START(task) \ - task = otterTaskStart(task, OTTER_SOURCE_LOCATION()) +#define OTTER_TASK_START(task) task = otterTaskStart(task, OTTER_SOURCE_LOCATION()) /** * @brief Counterpart to `OTTER_TASK_START()`, indicating the end of the code @@ -299,9 +292,8 @@ * (`descendants`). * */ -#define OTTER_TASK_WAIT_FOR(task, mode) \ - otterSynchroniseTasks(task, otter_sync_##mode, otter_endpoint_discrete, \ - OTTER_SOURCE_LOCATION()) +#define OTTER_TASK_WAIT_FOR(task, mode) \ + otterSynchroniseTasks(task, otter_sync_##mode, otter_endpoint_discrete, OTTER_SOURCE_LOCATION()) /** * @brief Record the start of a region where the task waits for children or @@ -325,9 +317,8 @@ * scheduling point at the barrier. * */ -#define OTTER_TASK_WAIT_START(task, mode) \ - otterSynchroniseTasks(task, otter_sync_##mode, otter_endpoint_enter, \ - OTTER_SOURCE_LOCATION()) +#define OTTER_TASK_WAIT_START(task, mode) \ + otterSynchroniseTasks(task, otter_sync_##mode, otter_endpoint_enter, OTTER_SOURCE_LOCATION()) /** * @brief Record the end of a region where the task waits for children or @@ -336,35 +327,44 @@ * @note Counterpart to #OTTER_TASK_WAIT_START * */ -#define OTTER_TASK_WAIT_END(task, mode) \ - otterSynchroniseTasks(task, otter_sync_##mode, otter_endpoint_leave, \ - OTTER_SOURCE_LOCATION()) +#define OTTER_TASK_WAIT_END(task, mode) \ + otterSynchroniseTasks(task, otter_sync_##mode, otter_endpoint_leave, OTTER_SOURCE_LOCATION()) -/** - * @brief Start a new algorithmic phase. - * - * By default, all trace events fall into the same global phase. However, some - * codes run through particular phases and will want to study these phases - * independently. With the present routine you mark the begin of such a phase. - * Each phase has to be given a unique name. - * - * - * ## Usage - * - * - Must be matched by a corresponding `OTTER_PHASE_END()` or - * `OTTER_PHASE_SWITCH()`. - * - * - * ## Semantics - * - * Creates a meta-region to nest all other regions encountered within it. - * - * - * @param name A unique string identifying this phase. - * - * @see `OTTER_PHASE_END()` - * @see `OTTER_PHASE_SWITCH()` - * +#define OTTER_TASK_WAIT_START_SCOPED(mode) \ + { \ + otter_task_sync_t _otter_suspend_mode = otter_sync_##mode; \ + otter_task_context *_otter_suspended_task = otterSynchroniseTasks( \ + otterGetActiveTask(), _otter_suspend_mode, otter_endpoint_enter, OTTER_SOURCE_LOCATION()); + +#define OTTER_TASK_WAIT_END_SCOPED() \ + otterSynchroniseTasks(_otter_suspended_task, _otter_suspend_mode, otter_endpoint_leave, OTTER_SOURCE_LOCATION()); \ + } + +/** \ + * @brief Start a new algorithmic phase. \ + * \ + * By default, all trace events fall into the same global phase. However, some \ + * codes run through particular phases and will want to study these phases \ + * independently. With the present routine you mark the begin of such a phase. \ + * Each phase has to be given a unique name. \ + * \ + * \ + * ## Usage \ + * \ + * - Must be matched by a corresponding `OTTER_PHASE_END()` or \ + * `OTTER_PHASE_SWITCH()`. \ + * \ + * \ + * ## Semantics \ + * \ + * Creates a meta-region to nest all other regions encountered within it. \ + * \ + * \ + * @param name A unique string identifying this phase. \ + * \ + * @see `OTTER_PHASE_END()` \ + * @see `OTTER_PHASE_SWITCH()` \ + * \ */ #define OTTER_PHASE_BEGIN(name) otterPhaseBegin((name), OTTER_SOURCE_LOCATION()) @@ -397,7 +397,6 @@ * @see `OTTER_PHASE_END()` * */ -#define OTTER_PHASE_SWITCH(name) \ - otterPhaseSwitch((name), OTTER_SOURCE_LOCATION()) +#define OTTER_PHASE_SWITCH(name) otterPhaseSwitch((name), OTTER_SOURCE_LOCATION()) #endif diff --git a/include/api/otter-task-graph/otter-task-graph.h b/include/api/otter-task-graph/otter-task-graph.h index 5a78d63..66e56ea 100644 --- a/include/api/otter-task-graph/otter-task-graph.h +++ b/include/api/otter-task-graph/otter-task-graph.h @@ -15,7 +15,7 @@ #include #if !defined(OTTER_USE_PRIVATE_HEADER) -#warning \ +#warning \ "It is not recommended to include this file directly. Please use otter/otter-task-graph-user.h, or pass -DOTTER_USE_PRIVATE_HEADER to ignore this warning" #endif @@ -32,21 +32,14 @@ typedef struct otter_task_context otter_task_context; * @see otterSynchroniseTasks * */ -typedef enum otter_task_sync_t { - otter_sync_children, - otter_sync_descendants -} otter_task_sync_t; +typedef enum otter_task_sync_t { otter_sync_children, otter_sync_descendants } otter_task_sync_t; /** * @brief Indicates the endpoint of an event i.e. whether it represents entry to * or exit from some region of code, or a discrete event. * */ -typedef enum { - otter_endpoint_enter = 0, - otter_endpoint_leave = 1, - otter_endpoint_discrete = 2 -} otter_endpoint_t; +typedef enum { otter_endpoint_enter = 0, otter_endpoint_leave = 1, otter_endpoint_discrete = 2 } otter_endpoint_t; /** * @brief Used to indicate whether a task should be added to a given task pool. @@ -54,10 +47,7 @@ typedef enum { * @see otterTaskInitialise * */ -typedef enum otter_add_to_pool_t { - otter_no_add_to_pool = 0, - otter_add_to_pool = 1 -} otter_add_to_pool_t; +typedef enum otter_add_to_pool_t { otter_no_add_to_pool = 0, otter_add_to_pool = 1 } otter_add_to_pool_t; #ifdef __cplusplus extern "C" { @@ -150,12 +140,9 @@ void otterTraceStop(void); * @param format: the format of the label, using subsequent arguments. * */ -otter_task_context *otterTaskInitialise(otter_task_context *parent_task, - int flavour, - otter_add_to_pool_t add_to_pool, - bool record_task_create_event, - const char *file, const char *func, - int line, const char *format, ...); +otter_task_context *otterTaskInitialise(otter_task_context *parent_task, int flavour, otter_add_to_pool_t add_to_pool, + bool record_task_create_event, const char *file, const char *func, int line, + const char *format, ...); /****** * Annotating Task Create, Start & End @@ -179,8 +166,8 @@ otter_task_context *otterTaskInitialise(otter_task_context *parent_task, * * @see `otterTaskInitialise()` */ -void otterTaskCreate(otter_task_context *task, otter_task_context *parent_task, - const char *file, const char *func, int line); +void otterTaskCreate(otter_task_context *task, otter_task_context *parent_task, const char *file, const char *func, + int line); /** * @brief Record the start of a region which represents previously initialised @@ -211,8 +198,7 @@ void otterTaskCreate(otter_task_context *task, otter_task_context *parent_task, * * @returns A pointer to a otter_task_context which represents the started task */ -otter_task_context *otterTaskStart(otter_task_context *task, const char *file, - const char *func, int line); +otter_task_context *otterTaskStart(otter_task_context *task, const char *file, const char *func, int line); /** * @brief Counterpart to `otterTaskStart()`, indicating the end of the code @@ -225,8 +211,7 @@ otter_task_context *otterTaskStart(otter_task_context *task, const char *file, * * @see `otterTaskStart()` */ -void otterTaskEnd(otter_task_context *task, const char *file, const char *func, - int line); +void otterTaskEnd(otter_task_context *task, const char *file, const char *func, int line); /****** * Registering & Retrieving Tasks @@ -301,10 +286,11 @@ otter_task_context *otterTaskBorrowLabel(const char *format, ...); * point. * @param line: The line where the task encountered the synchronisation point. * + * @returns The handle of the suspended or resumed task, according to `endpoint`. + * */ -void otterSynchroniseTasks(otter_task_context *task, otter_task_sync_t mode, - otter_endpoint_t endpoint, const char *file, - const char *func, int line); +otter_task_context *otterSynchroniseTasks(otter_task_context *task, otter_task_sync_t mode, otter_endpoint_t endpoint, + const char *file, const char *func, int line); /****** * Managing Phases @@ -339,8 +325,7 @@ void otterSynchroniseTasks(otter_task_context *task, otter_task_sync_t mode, * @see `otterPhaseSwitch()` * */ -void otterPhaseBegin(const char *name, const char *file, const char *func, - int line); +void otterPhaseBegin(const char *name, const char *file, const char *func, int line); /** * @brief End the present algorithmic phase. @@ -378,8 +363,25 @@ void otterPhaseEnd(const char *file, const char *func, int line); * @see `otterPhaseEnd()` * */ -void otterPhaseSwitch(const char *name, const char *file, const char *func, - int line); +void otterPhaseSwitch(const char *name, const char *file, const char *func, int line); + +/****** + * Managing The Active Task + ******/ + +/** + * @brief Get the active task for the encountering thread. + * + * @return otter_task_context* + */ +otter_task_context *otterGetActiveTask(void); + +/** + * @brief Set the active task for the encountering thread. + * + * @return otter_task_context* + */ +void otterSetActiveTask(otter_task_context *task); #ifdef __cplusplus } diff --git a/src/otter-task-graph/otter-task-graph.c b/src/otter-task-graph/otter-task-graph.c index de9b7d5..8233301 100644 --- a/src/otter-task-graph/otter-task-graph.c +++ b/src/otter-task-graph/otter-task-graph.c @@ -148,6 +148,16 @@ void otterTraceInitialise(const char *file, const char *func, int line) { // an event where it is its own parent root_task = task; + // The thread which initialised Otter is considered the main thread + get_thread_data()->is_master_thread = true; + +#if USE_THREAD_LOCAL_TASK_HANDLE + LOG_INFO_SRC(file, line, "main thread (%" PRIu64 ") started root task: %" PRIxPTR, get_thread_data()->id, + (uintptr_t)root_task); + // don't actually track the root task as the active task + get_thread_data()->active_task = NULL; +#endif + return; } @@ -202,6 +212,13 @@ otter_task_context *otterTaskInitialise(otter_task_context *parent, int flavour, otter_src_ref_t init_ref = get_source_location_ref((otter_src_location_t){.file = file, .func = func, .line = line}); +#if USE_THREAD_LOCAL_TASK_HANDLE + // ignore whatever was passed in, unless it was explicitly NULL + if (parent != NULL) { + parent = get_thread_data()->active_task; // can be NULL if the thread not currently executing a task + } +#endif + // If no parent given, set the current phase (or root) task as the parent. // Only the implicit root task may have a NULL parent. if (parent == NULL) { @@ -214,14 +231,17 @@ otter_task_context *otterTaskInitialise(otter_task_context *parent, int flavour, } } + LOG_INFO_SRC(file, line, "task-create: parent=%" PRIxPTR " child=%" PRIxPTR, (uintptr_t)parent, (uintptr_t)task); + otterTaskContext_init(task, parent, flavour, init_ref); va_list args; va_start(args, format); otter_register_task_label_va_list(task, add_to_pool == otter_add_to_pool ? true : false, format, args); va_end(args); - if (record_task_create_event) + if (record_task_create_event) { otterTaskCreate(task, parent, file, func, line); + } return task; } @@ -263,8 +283,25 @@ otter_task_context *otterTaskStart(otter_task_context *task, const char *file, c LOG_ERROR("IGNORED (tried to start null task at %s:%d in %s)", file, line, func); return NULL; } - // TODO: not great to pass this struct by value since I only need a few of the - // fields here + + /** + * @brief On task-start (OTTER_TASK_START(task)) we either: + * - started a new task from no task + * - were told to suspend a current task and start a new one + * + * So: + * - `task` is now the active task for this thread. + * - if we were previously executing a differen task which was suspended, it is up to the annotations at + * that task-suspend point to store the handle of the suspended task. + * - this means we can assert here that the active task is NULL, since we either came here from + * no existing task, or suspended the active task and switched back to the implicit task. + */ + +#if USE_THREAD_LOCAL_TASK_HANDLE + assert(get_thread_data()->active_task == NULL); +#endif + + // TODO: not great to pass this struct by value since I only need a few of the fields here trace_task_region_attr_t task_attr; task_attr.type = otter_task_explicit; task_attr.id = otterTaskContext_get_task_context_id(task); @@ -276,14 +313,41 @@ otter_task_context *otterTaskStart(otter_task_context *task, const char *file, c otter_src_ref_t start_ref = get_source_location_ref((otter_src_location_t){.file = file, .func = func, .line = line}); trace_graph_event_task_begin(get_thread_data()->location, otterTaskContext_get_task_context_id(task), start_ref); + + LOG_INFO_SRC(file, line, "task-start: task=%" PRIxPTR, (uintptr_t)task); + +#if USE_THREAD_LOCAL_TASK_HANDLE + get_thread_data()->active_task = task; +#endif + return task; } void otterTaskEnd(otter_task_context *task, const char *file, const char *func, int line) { LOG_DEBUG("[%lu] end task", otterTaskContext_get_task_context_id(task)); + +#if USE_THREAD_LOCAL_TASK_HANDLE || 1 + if (task == root_task) { + assert(get_thread_data()->is_master_thread); + assert(get_thread_data()->active_task == NULL); + } else if (task == phase_task) { + assert(get_thread_data()->is_master_thread); + assert(get_thread_data()->active_task == NULL); + } else { + assert(task != NULL); + assert(get_thread_data()->active_task == task); + } +#endif + + LOG_INFO_SRC(file, line, "task-end: task=%" PRIxPTR, (uintptr_t)task); + otter_src_ref_t end_ref = get_source_location_ref((otter_src_location_t){.file = file, .func = func, .line = line}); trace_graph_event_task_end(get_thread_data()->location, otterTaskContext_get_task_context_id(task), end_ref); otterTaskContext_delete(task); + +#if USE_THREAD_LOCAL_TASK_HANDLE + get_thread_data()->active_task = NULL; +#endif } void otterTaskPushLabel(otter_task_context *task, const char *format, ...) { @@ -326,12 +390,38 @@ otter_task_context *otterTaskBorrowLabel(const char *format, ...) { return task; } -void otterSynchroniseTasks(otter_task_context *task, otter_task_sync_t mode, otter_endpoint_t endpoint, - const char *file, const char *func, int line) { +otter_task_context *otterSynchroniseTasks(otter_task_context *task, otter_task_sync_t mode, otter_endpoint_t endpoint, + const char *file, const char *func, int line) { LOG_DEBUG("synchronise tasks: %d", mode); otter_src_ref_t src_ref = get_source_location_ref((otter_src_location_t){.file = file, .func = func, .line = line}); + LOG_ERROR_IF(endpoint == otter_endpoint_discrete, + "(%s:%d) otter_endpoint_discrete is deprecated, please switch " + "to otter_endpoint_enter/otter_endpoint_leave", + file, line); + +#if USE_THREAD_LOCAL_TASK_HANDLE + switch (endpoint) { + case otter_endpoint_discrete: + case otter_endpoint_enter: + // switch to implicit task i.e. set active task to NULL + task = get_thread_data()->active_task; + get_thread_data()->active_task = NULL; + break; + + case otter_endpoint_leave: + // thread's active task must be NULL when switching back to a task + assert(get_thread_data()->active_task == NULL); + // resume the given task + get_thread_data()->active_task = task; + break; + } +#endif + + // don't return the phase or root task + otter_task_context *result = task; + if (task == NULL) { if (phase_task != NULL) { task = phase_task; @@ -346,7 +436,7 @@ void otterSynchroniseTasks(otter_task_context *task, otter_task_sync_t mode, ott sync_attr.encountering_task_id = otterTaskContext_get_task_context_id(task); trace_graph_synchronise_tasks(get_thread_data()->location, otterTaskContext_get_task_context_id(task), sync_attr, endpoint, src_ref); - return; + return result; } void otterTraceStart(void) { LOG_DEBUG("not currently implemented - ignored"); } @@ -358,11 +448,24 @@ void otterPhaseBegin(const char *name, const char *file, const char *func, int l assert(name != NULL); assert(phase_task == NULL); assert(root_task != NULL); + assert(get_thread_data()->is_master_thread); + +#if USE_THREAD_LOCAL_TASK_HANDLE + assert(get_thread_data()->active_task == NULL); +#endif + phase_task = otterTaskInitialise(root_task, 0, otter_no_add_to_pool, true, file, func, line, "OTTER PHASE: \"%s\" (%s:%d)", name, func, line); unique_id_t phase_id = otterTaskContext_get_task_context_id(phase_task); LOG_DEBUG(" OTTER PHASE: \"%s\" (%s:%d in %s)", phase_id, name, file, line, func); otterTaskStart(phase_task, file, func, line); + +#if USE_THREAD_LOCAL_TASK_HANDLE + // don't actually track the phase as the active task + get_thread_data()->active_task = NULL; + assert(get_thread_data()->active_task == NULL); +#endif + #else LOG_WARN("phases are disabled - ignoring (name=%s)", name); #endif @@ -372,6 +475,12 @@ void otterPhaseBegin(const char *name, const char *file, const char *func, int l void otterPhaseEnd(const char *file, const char *func, int line) { #if OTTER_USE_PHASES assert(phase_task != NULL); + assert(get_thread_data()->is_master_thread); + +#if USE_THREAD_LOCAL_TASK_HANDLE + assert(get_thread_data()->active_task == NULL); +#endif + unique_id_t phase_id = otterTaskContext_get_task_context_id(phase_task); LOG_DEBUG(" (%s:%d in %s)", phase_id, file, line, func); otterTaskEnd(phase_task, file, func, line); @@ -381,6 +490,11 @@ void otterPhaseEnd(const char *file, const char *func, int line) { otterSynchroniseTasks(root_task, otter_sync_children, otter_endpoint_discrete, file, func, line); phase_task = NULL; + +#if USE_THREAD_LOCAL_TASK_HANDLE + assert(get_thread_data()->active_task == NULL); +#endif + #else LOG_WARN("phases are disabled - ignoring."); #endif @@ -399,6 +513,10 @@ void otterPhaseSwitch(const char *name, const char *file, const char *func, int return; } +otter_task_context *otterGetActiveTask(void) { return get_thread_data()->active_task; } + +void otterSetActiveTask(otter_task_context *task) { get_thread_data()->active_task = task; } + static void debug_print_count(const char *str, int count, void *data) { LOG_DEBUG("%s %d", str, count); return;