diff --git a/src/emu/CMakeLists.txt b/src/emu/CMakeLists.txt index c9112ab..f10c7ae 100644 --- a/src/emu/CMakeLists.txt +++ b/src/emu/CMakeLists.txt @@ -11,6 +11,7 @@ include_directories( add_library(emu STATIC ../common.c bay.c + body.c chan.c clkoff.c cpu.c diff --git a/src/emu/body.c b/src/emu/body.c new file mode 100644 index 0000000..9f95443 --- /dev/null +++ b/src/emu/body.c @@ -0,0 +1,336 @@ +/* Copyright (c) 2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "body.h" +#include "task.h" +#include +#include "uthash.h" +#include "utlist.h" + +/* Make body private struct */ +struct body { + /* Body id consecutive and unique per task */ + uint32_t id; + uint32_t taskid; + int flags; + enum body_state state; + UT_hash_handle hh; + struct body_stack *stack; + long iteration; + struct task *task; + + /* List handle for nested task support */ + struct body *next; + struct body *prev; + + /* Create a identification name */ + char name[256]; +}; + +struct body * +body_find(struct body_info *info, uint32_t body_id) +{ + struct body *body = NULL; + HASH_FIND_INT(info->bodies, &body_id, body); + + return body; +} + +struct body * +body_create(struct body_info *info, struct task *task, uint32_t body_id, int flags) +{ + if (body_id == 0) { + err("body id %u must be non-zero", body_id); + return NULL; + } + + if (body_find(info, body_id) != NULL) { + err("body with id %u already exists", body_id); + return NULL; + } + + if (task == NULL) { + err("task of body %u is NULL", body_id); + return NULL; + } + + struct body *body = calloc(1, sizeof(struct body)); + + if (body == NULL) { + err("calloc failed:"); + return NULL; + } + + body->id = body_id; + body->state = BODY_ST_CREATED; + body->iteration = 0; + body->flags = flags; + body->task = task; + body->taskid = task_get_id(task); /* For debug purposes only */ + + if (snprintf(body->name, sizeof(body->name), "body(id=%u,taskid=%u)", + body->id, body->taskid) >= (int) sizeof(body->name)) { + err("body name too long"); + return NULL; + } + + /* Add the new body to the hash table */ + HASH_ADD_INT(info->bodies, id, body); + + return body; +} + +/* Transition from Created to Running */ +int +body_execute(struct body_stack *stack, struct body *body) +{ + if (body == NULL) { + err("body is NULL"); + return -1; + } + + /* Allow bodies to be executed multiple times */ + if (body->state == BODY_ST_DEAD) { + if (!body_can_resurrect(body)) { + err("%s is not allowed to run again", body->name); + return -1; + } + body->state = BODY_ST_CREATED; + body->iteration++; + dbg("%s runs again", body->name); + } + + /* Better error for executing a paused task body */ + if (body->state == BODY_ST_PAUSED) { + err("refusing to run %s in Paused state, needs to resume intead", + body->name); + return -1; + } + + if (body->state != BODY_ST_CREATED) { + err("%s state must be Created but is %s", + body->name, body_get_state_name(body)); + return -1; + } + + if (body->stack != NULL) { + err("%s stack already set", body->name); + return -1; + } + + struct body *top = body_get_running(stack); + if (top != NULL) { + if (top->flags & BODY_FLAG_RELAX_NESTING) { + warn("deprecated nesting %s over already running %s", + body->name, top->name); + } else { + err("cannot nest %s over %s which is already running", + body->name, top->name); + return -1; + } + } + + body->stack = stack; + body->state = BODY_ST_RUNNING; + + DL_PREPEND(stack->top, body); + + dbg("%s state is now Running, iteration %ld", + body->name, body->iteration); + + return 0; +} + +/* Transition from Running to Paused */ +int +body_pause(struct body_stack *stack, struct body *body) +{ + if (body == NULL) { + err("body is NULL"); + return -1; + } + + if (!body_can_pause(body)) { + err("%s is not allowed to pause", body->name); + return -1; + } + + if (body->state != BODY_ST_RUNNING) { + err("%s state must be Running but is %s", + body->name, body_get_state_name(body)); + return -1; + } + + if (body->stack == NULL) { + err("%s stack not set", body->name); + return -1; + } + + if (body->stack != stack) { + err("%s has another stack", body->name); + return -1; + } + + if (stack->top != body) { + err("%s is not on top of the stack", body->name); + return -1; + } + + body->state = BODY_ST_PAUSED; + + dbg("%s state is now Paused", body->name); + + return 0; +} + +/* Transition from Paused to Running */ +int +body_resume(struct body_stack *stack, struct body *body) +{ + if (body == NULL) { + err("body is NULL"); + return -1; + } + + if (body->state != BODY_ST_PAUSED) { + err("%s state must be Paused but is %s", + body->name, body_get_state_name(body)); + return -1; + } + + if (body->stack == NULL) { + err("%s stack not set", body->name); + return -1; + } + + if (body->stack != stack) { + err("%s has another stack", body->name); + return -1; + } + + if (stack->top != body) { + err("%s is not on top of the stack", body->name); + return -1; + } + + body->state = BODY_ST_RUNNING; + + dbg("%s state is now Running", body->name); + + return 0; +} + +/* Transition from Running to Dead */ +int +body_end(struct body_stack *stack, struct body *body) +{ + if (body == NULL) { + err("body is NULL"); + return -1; + } + + if (body->state != BODY_ST_RUNNING) { + err("%s state must be Running but is %s", + body->name, body_get_state_name(body)); + return -1; + } + + if (body->stack == NULL) { + err("%s stack not set", body->name); + return -1; + } + + if (body->stack != stack) { + err("%s has another stack", body->name); + return -1; + } + + if (stack->top != body) { + err("%s is not on top of the stack", body->name); + return -1; + } + + body->state = BODY_ST_DEAD; + + /* FIXME: This should me changed: + * "Don't unset the thread from the task, as it will be used + * later to ensure we switch to tasks of the same thread." */ + DL_DELETE(stack->top, body); + body->stack = NULL; + + dbg("%s state is now Dead, completed iteration %ld", + body->name, body->iteration); + + return 0; +} + +int +body_can_resurrect(struct body *body) +{ + return (body->flags & BODY_FLAG_RESURRECT); +} + +int +body_can_pause(struct body *body) +{ + return (body->flags & BODY_FLAG_PAUSE); +} + +/** Return the iteration number of the body. + * + * Starts at 0 for the first execution and increases every time it is executed + * again */ +long +body_get_iteration(struct body *body) +{ + return body->iteration; +} + +/** Returns the top body in the stack if is running, otherwise NULL. */ +struct body * +body_get_running(struct body_stack *stack) +{ + struct body *body = stack->top; + if (body && body->state == BODY_ST_RUNNING) + return body; + + return NULL; +} + +/** Returns the top body in the stack. */ +struct body * +body_get_top(struct body_stack *stack) +{ + return stack->top; +} + +uint32_t +body_get_id(struct body *body) +{ + return body->id; +} + +struct task * +body_get_task(struct body *body) +{ + return body->task; +} + +enum body_state +body_get_state(struct body *body) +{ + return body->state; +} + +const char * +body_get_state_name(struct body *body) +{ + const char *name[BODY_ST_MAX] = { + [BODY_ST_CREATED] = "Created", + [BODY_ST_RUNNING] = "Running", + [BODY_ST_PAUSED] = "Paused", + [BODY_ST_DEAD] = "Dead", + }; + + return name[body->state]; +} diff --git a/src/emu/body.h b/src/emu/body.h new file mode 100644 index 0000000..eb366cd --- /dev/null +++ b/src/emu/body.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef BODY_H +#define BODY_H + +#include +#include "common.h" + +enum body_flags { + /* Allow bodies to pause */ + BODY_FLAG_PAUSE = (1 << 0), + /* Allow bodies to run again after death */ + BODY_FLAG_RESURRECT = (1 << 1), + /* Allow nested tasks to begin running without previously pausing the + * current task. This is only added for compatibility with Nanos6. */ + BODY_FLAG_RELAX_NESTING = (1 << 2), +}; + +enum body_state { + BODY_ST_CREATED = 1, + BODY_ST_RUNNING, + BODY_ST_PAUSED, + BODY_ST_DEAD, + BODY_ST_MAX, +}; + +struct body; +struct task; + +struct body_info { + /* Hash map of task bodies for a task */ + struct body *bodies; +}; + +struct body_stack { + struct body *top; +}; + +USE_RET struct body *body_find(struct body_info *info, uint32_t body_id); +USE_RET struct body *body_create(struct body_info *info, struct task *task, uint32_t body_id, int flags); + +USE_RET int body_execute(struct body_stack *stack, struct body *body); +USE_RET int body_pause(struct body_stack *stack, struct body *body); +USE_RET int body_resume(struct body_stack *stack, struct body *body); +USE_RET int body_end(struct body_stack *stack, struct body *body); + +USE_RET int body_can_resurrect(struct body *body); +USE_RET int body_can_pause(struct body *body); +USE_RET long body_get_iteration(struct body *body); +USE_RET struct body *body_get_running(struct body_stack *stack); +USE_RET struct body *body_get_top(struct body_stack *stack); +USE_RET struct task *body_get_task(struct body *body); +USE_RET enum body_state body_get_state(struct body *body); +USE_RET uint32_t body_get_id(struct body *body); +USE_RET const char *body_get_state_name(struct body *body); + +#endif /* BODY_H */ diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 9fb056f..4d8120c 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -7,6 +7,7 @@ endfunction() #unit_test(bay-hash-speed.c) unit_test(bay.c) +unit_test(body.c) unit_test(cfg.c) unit_test(chan.c) unit_test(clkoff.c) diff --git a/test/unit/body.c b/test/unit/body.c new file mode 100644 index 0000000..342798d --- /dev/null +++ b/test/unit/body.c @@ -0,0 +1,163 @@ +/* Copyright (c) 2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "common.h" +#include "emu/task.h" +#include "emu/body.h" +#include "unittest.h" +#include + +static void +test_body(void) +{ + struct task task = { .id = 123 }; + struct body_info info; + struct body_stack stack1; + + memset(&info, 0, sizeof(info)); + memset(&stack1, 0, sizeof(stack1)); + + /* Create 10 bodies: no pause and no resurrect */ + for (uint32_t id = 1; id <= 10; id++) { + if (body_create(&info, &task, id, 0) == NULL) + die("body_create failed"); + } + + /* Try to create another body with the same id */ + if (body_create(&info, &task, 3, 0) != NULL) + die("body_create didn't fail"); + + /* Try to create a body with id 0 */ + if (body_create(&info, &task, 0, 0) != NULL) + die("body_create didn't fail"); + + /* Run a body */ + struct body *body1 = body_find(&info, 1); + if (body1 == NULL) + die("body_find failed"); + OK(body_execute(&stack1, body1)); + + /* Finish a non existant body */ + ERR(body_end(&stack1, NULL)); + + /* Attempt to run a body on top of one that is already running */ + struct body_stack stack2; + memset(&stack2, 0, sizeof(stack2)); + struct body *body2 = body_find(&info, 2); + if (body2 == NULL) + die("body_find failed"); + OK(body_execute(&stack2, body2)); + ERR(body_execute(&stack1, body2)); + + /* Attempt to stop a body that is not the top */ + ERR(body_end(&stack1, body2)); + + /* Attempt to pause the top body without the pause flag */ + ERR(body_pause(&stack1, body1)); + + /* Attempt ending a task from another stack */ + ERR(body_end(&stack2, body1)); + + /* Stop the bodies */ + OK(body_end(&stack1, body1)); + OK(body_end(&stack2, body2)); + + /* Try to run an already created body again without the resurrect flag */ + ERR(body_execute(&stack1, body1)); + + err("ok"); +} + +static void +test_pause(void) +{ + struct task task = { .id = 123 }; + struct body *body; + struct body_info info; + struct body_stack stack; + struct body_stack stack2; + + memset(&info, 0, sizeof(info)); + memset(&stack, 0, sizeof(stack)); + memset(&stack2, 0, sizeof(stack2)); + + /* Create 10 bodies: only pause */ + for (uint32_t id = 1; id <= 10; id++) { + if (body_create(&info, &task, id, BODY_FLAG_PAUSE) == NULL) + die("body_create failed"); + } + + /* Run the 10 bodies one on top of the other */ + for (uint32_t id = 1; id <= 10; id++) { + body = body_find(&info, id); + if (body == NULL) + die("body_find failed"); + OK(body_execute(&stack, body)); + + /* Don't pause the last one */ + if (id != 10) + OK(body_pause(&stack, body)); + } + + /* Attempt to run a body which is already running */ + body = body_find(&info, 3); + if (body == NULL) + die("body_find failed"); + ERR(body_execute(&stack, body)); + + /* Attempt to end a body that is not the top */ + ERR(body_end(&stack, body)); + + /* Pause the top */ + body = body_get_top(&stack); + OK(body_pause(&stack, body)); + + /* Attempt to resume in another stack */ + ERR(body_resume(&stack2, body)); + + /* Stop the bodies */ + for (uint32_t id = 10; id >= 1; id--) { + body = body_find(&info, id); + if (body == NULL) + die("body_find failed"); + OK(body_resume(&stack, body)); + OK(body_end(&stack, body)); + } + + err("ok"); +} + +static void +test_resurrect(void) +{ + struct task task = { .id = 123 }; + struct body *body; + struct body_info info; + struct body_stack stack; + + memset(&info, 0, sizeof(info)); + memset(&stack, 0, sizeof(stack)); + + /* Create a body with id 1 that can resurrect */ + if ((body = body_create(&info, &task, 1, BODY_FLAG_RESURRECT)) == NULL) + die("body_create failed"); + + /* Run it and end it */ + OK(body_execute(&stack, body)); + OK(body_end(&stack, body)); + + /* Then try to run it again */ + OK(body_execute(&stack, body)); + OK(body_end(&stack, body)); + + err("ok"); +} + +int main(void) +{ + test_body(); + test_pause(); + test_resurrect(); + + return 0; +}