Add body model for tasks

The new task body model (or just body model) allows a task to have
multiple bodies. Generally a body is mapped to the execution of the same
user code of the task with specific input arguments.

The body model can constraint if a given body can be paused or re-run
again (resurrect).

Additionally, the body model can run multiple nested bodies but with the
restriction that the parent body should be paused first. This condition
can be relaxed with the BODY_FLAG_RELAX_NESTING flag.
This commit is contained in:
Rodrigo Arias 2023-10-23 15:22:30 +02:00 committed by Rodrigo Arias
parent bdd5696641
commit a0e7fad83e
5 changed files with 559 additions and 0 deletions

View File

@ -11,6 +11,7 @@ include_directories(
add_library(emu STATIC add_library(emu STATIC
../common.c ../common.c
bay.c bay.c
body.c
chan.c chan.c
clkoff.c clkoff.c
cpu.c cpu.c

336
src/emu/body.c Normal file
View File

@ -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 <string.h>
#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];
}

58
src/emu/body.h Normal file
View File

@ -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 <stdint.h>
#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 */

View File

@ -7,6 +7,7 @@ endfunction()
#unit_test(bay-hash-speed.c) #unit_test(bay-hash-speed.c)
unit_test(bay.c) unit_test(bay.c)
unit_test(body.c)
unit_test(cfg.c) unit_test(cfg.c)
unit_test(chan.c) unit_test(chan.c)
unit_test(clkoff.c) unit_test(clkoff.c)

163
test/unit/body.c Normal file
View File

@ -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 <string.h>
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;
}