diff --git a/.gitignore b/.gitignore
index 62ed0f0bc78bb3400e8b0bf1b81101a09f995f4d..f0dcbe2dd0805bd43c3063cff1b65f5c45f223a0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,4 +68,3 @@ Temporary Items
 .apdisk
 
 .idea
-game_of_life/benchmark/*.png
diff --git a/futhark_mpi/.gitignore b/futhark_mpi/.gitignore
index 4a4fe12cf442706310b1ad0fe5cd3e799a9e91df..d839d8ee26d09927d1d6528ed1577fd639d06f92 100644
--- a/futhark_mpi/.gitignore
+++ b/futhark_mpi/.gitignore
@@ -126,5 +126,5 @@ fabric.properties
 .idea/caches/build_file_checksums.ser
 
 .idea
-gol.h
-gol.c
+envelope.h
+envelope.c
diff --git a/futhark_mpi/chunk_info.c b/futhark_mpi/chunk_info.c
index 0ee7d081a2e83ed4ae1161928cf34402135ae97f..7e1d4ef350629b0d0cd3c6b22e1f75a7d8218462 100644
--- a/futhark_mpi/chunk_info.c
+++ b/futhark_mpi/chunk_info.c
@@ -3,15 +3,18 @@
 #include <assert.h>
 #include "chunk_info.h"
 
-extern void chunk_info_init(chunk_info_t *ci, int type, const int dimensions[2], int y, int x, bool allocate_data) {
+extern void
+chunk_info_init(chunk_info_t *ci, int type, const int dimensions[3], int y, int x, int z, bool allocate_data) {
     ci->dimensions[0] = dimensions[0];
     ci->dimensions[1] = dimensions[1];
-    ci->count = (size_t) ci->dimensions[0] * (size_t) ci->dimensions[1];
+    ci->dimensions[2] = dimensions[2];
+    ci->count = (size_t) ci->dimensions[0] * (size_t) ci->dimensions[1] * (size_t) ci->dimensions[2];
     if (allocate_data) {
         chunk_info_allocate_data(ci, type);
     }
     ci->y = y;
     ci->x = x;
+    ci->z = z;
 }
 
 extern void chunk_info_allocate_data(chunk_info_t *ci, int type) {
@@ -20,8 +23,8 @@ extern void chunk_info_allocate_data(chunk_info_t *ci, int type) {
 }
 
 void chunk_info_print(chunk_info_t *ci) {
-    printf("[chunk_info_t] data = %p, dimensions = [%d][%d], count = %zu, YX = (%d, %d)\n",
-           ci->data, ci->dimensions[0], ci->dimensions[1], ci->count, ci->y, ci->x);
+    printf("[chunk_info_t] data = %p, dimensions = [%d][%d][%d], count = %zu, YXZ = (%d, %d, %d)\n",
+           ci->data, ci->dimensions[0], ci->dimensions[1], ci->dimensions[2], ci->count, ci->y, ci->x, ci->z);
 }
 
 extern void chunk_info_free(chunk_info_t *ci) {
diff --git a/futhark_mpi/chunk_info.h b/futhark_mpi/chunk_info.h
index 3dcd7c1ef97fa8e9e97b778128f945b75e4fe1ad..46340f2e9fddb87b2d82c25a82d93641e994adc2 100644
--- a/futhark_mpi/chunk_info.h
+++ b/futhark_mpi/chunk_info.h
@@ -5,13 +5,15 @@
 
 typedef struct chunk_info {
     void *data;
-    int dimensions[2];
+    int dimensions[3];
     size_t count;
     int y;
     int x;
+    int z;
 } chunk_info_t;
 
-extern void chunk_info_init(chunk_info_t *ci, int type, const int dimensions[2], int y, int x, bool allocate_data);
+extern void
+chunk_info_init(chunk_info_t *ci, int type, const int dimensions[3], int y, int x, int z, bool allocate_data);
 
 extern void chunk_info_allocate_data(chunk_info_t *ci, int type);
 
diff --git a/futhark_mpi/dispatch.c b/futhark_mpi/dispatch.c
index 1dc7217b8063f188daf6797fe21de13a6a93351e..7c86efeebaf2d761676b7c2dd495d51a29cb3f75 100644
--- a/futhark_mpi/dispatch.c
+++ b/futhark_mpi/dispatch.c
@@ -38,10 +38,10 @@ struct dispatch_context {
     int world_size;
     int my_cart_rank;
     int coordinates[2];
-    MPI_Comm communicators[3]; /* cart_comm, row_comm, column_comm */
+    MPI_Comm communicators[4]; /* cart_comm, row_comm, column_comm, depth_comm */
+    int network_dimensions[3];
+    int data_dimensions[3];
     MPI_Datatype datatype;
-    int network_dimensions[2];
-    int data_dimensions[2];
     size_t count;
     int n_dimensions;
     chunk_info_t *chunk_info;
@@ -58,57 +58,80 @@ static void get_my_rank(struct dispatch_context *dc) {
     MPI_Comm_rank(MPI_COMM_WORLD, &dc->my_rank);
 }
 
+static void find_best_factors(int n, int factors[2]) {
+    int result[2] = {0};
+    int limit = (int) sqrt(n);
+    bool first_pass = true;
+    for (int i = 1; i <= limit; ++i) {
+        if (n % i == 0) {
+            int factor1 = i;
+            int factor2 = n / i;
+            int current_difference = abs(result[0] - result[1]);
+            int new_difference = abs(factor1 - factor2);
+            if (first_pass || current_difference > new_difference) {
+                result[0] = factor1;
+                result[1] = factor2;
+            }
+            first_pass = false;
+        }
+    }
+    factors[0] = result[0];
+    factors[1] = result[1];
+}
+
 static void find_network_dimensions(struct dispatch_context *dc) {
     /* 1D */
     if (dc->n_dimensions == 1) {
         dc->network_dimensions[0] = 1;
         dc->network_dimensions[1] = dc->world_size;
+        dc->network_dimensions[2] = 1;
     } else {
         /* 2D */
-        int limit = (int) sqrt(dc->world_size);
-        bool first_pass = true;
-        for (int i = 1; i <= limit; ++i) {
-            if (dc->world_size % i == 0) {
-                int new_grid_ny = i;
-                int new_grid_nx = dc->world_size / i;
-                int current_difference = abs(dc->network_dimensions[0] - dc->network_dimensions[1]);
-                int new_difference = abs(new_grid_ny - new_grid_nx);
-                if (first_pass || current_difference > new_difference) {
-                    dc->network_dimensions[0] = new_grid_ny;
-                    dc->network_dimensions[1] = new_grid_nx;
-                }
-                first_pass = false;
-            }
-        }
+        find_best_factors(dc->world_size, dc->network_dimensions);
+        dc->network_dimensions[2] = 1;
+        /* 3D */
         if (dc->n_dimensions == 3) {
-            /* 3D */
-
+            int factors[2] = {0};
+            find_best_factors(dc->network_dimensions[1], factors);
+            if (factors[0] < factors[1]) {
+                dc->network_dimensions[1] = factors[1];
+                dc->network_dimensions[2] = factors[0];
+            } else {
+                dc->network_dimensions[1] = factors[0];
+                dc->network_dimensions[2] = factors[1];
+            }
         }
     }
 }
 
 static void create_network_communicators(struct dispatch_context *dc) {
-    int periods[2] = {true, true}; // Cyclic on row-column-depth
+    int periods[3] = {true, true, true}; // Cyclic on row-column-depth
 
-    MPI_Cart_create(MPI_COMM_WORLD, 2, dc->network_dimensions, periods, 1, &dc->communicators[0]);
+    MPI_Cart_create(MPI_COMM_WORLD, 3, dc->network_dimensions, periods, 1, &dc->communicators[0]);
 
     /* Create row communicator */
-    int remain_dims[2] = {false, true};
+    int remain_dims[3] = {false, true, false};
     MPI_Cart_sub(dc->communicators[0], remain_dims, &dc->communicators[1]);
 
     /* Create column communicator */
     remain_dims[0] = true; // row
     remain_dims[1] = false; // column
+    remain_dims[2] = false; // depth
     MPI_Cart_sub(dc->communicators[0], remain_dims, &dc->communicators[2]);
 
+    /* Create depth communicator */
+    remain_dims[0] = false; // row
+    remain_dims[1] = false; // column
+    remain_dims[2] = true; // depth
+    MPI_Cart_sub(dc->communicators[0], remain_dims, &dc->communicators[3]);
+
     MPI_Comm_rank(dc->communicators[0], &dc->my_cart_rank);
 
-    MPI_Cart_coords(dc->communicators[0], dc->my_cart_rank, 2, dc->coordinates);
+    MPI_Cart_coords(dc->communicators[0], dc->my_cart_rank, 3, dc->coordinates);
 }
 
 static void divide_data(struct dispatch_context *dc) {
     dc->chunks_info = calloc((size_t) dc->world_size, sizeof(chunk_info_t));
-    assert(dc->chunks_info != NULL);
 
     int nb_rows_per_process = dc->data_dimensions[0] / dc->network_dimensions[0];
     int remaining_rows = dc->data_dimensions[0] % dc->network_dimensions[0];
@@ -116,7 +139,10 @@ static void divide_data(struct dispatch_context *dc) {
     int nb_columns_per_process = dc->data_dimensions[1] / dc->network_dimensions[1];
     int remaining_columns = dc->data_dimensions[1] % dc->network_dimensions[1];
 
-    for (int i = 0, y = 0, x = 0; i < dc->world_size; ++i) {
+    int nb_depths_per_process = dc->data_dimensions[2] / dc->network_dimensions[2];
+    int remaining_depths = dc->data_dimensions[2] % dc->network_dimensions[2];
+
+    for (int i = 0, y = 0, x = 0, z = 0; i < dc->world_size; ++i) {
         int nb_rows = nb_rows_per_process;
         if (remaining_rows > 0) {
             ++nb_rows;
@@ -126,9 +152,13 @@ static void divide_data(struct dispatch_context *dc) {
             ++nb_columns;
             --remaining_columns;
         }
+        int nb_depths = nb_depths_per_process;
+        if (remaining_depths > 0) {
+            ++nb_depths;
+        }
 
-        int dimensions[2] = {nb_rows, nb_columns};
-        chunk_info_init(&dc->chunks_info[i], dc->type, dimensions, y, x, i == dc->my_rank);
+        int dimensions[3] = {nb_rows, nb_columns, nb_depths};
+        chunk_info_init(&dc->chunks_info[i], dc->type, dimensions, y, x, z, i == dc->my_rank);
 
         x += nb_columns;
         if (x >= dc->data_dimensions[1]) {
@@ -137,6 +167,12 @@ static void divide_data(struct dispatch_context *dc) {
             remaining_columns = dc->data_dimensions[1] % dc->network_dimensions[1];
             --remaining_rows;
         }
+        if ((i + 1) % (dc->network_dimensions[0] * dc->network_dimensions[1]) == 0) {
+            z += nb_depths;
+            y = 0;
+            remaining_columns = dc->data_dimensions[1] % dc->network_dimensions[1];
+            --remaining_depths;
+        }
     }
     dc->chunk_info = &dc->chunks_info[dc->my_rank];
 }
@@ -154,19 +190,24 @@ extern struct dispatch_context *dispatch_context_new(const int *dimensions, MPI_
         case 1:
             dc->data_dimensions[0] = 1;
             dc->data_dimensions[1] = dimensions[0];
+            dc->data_dimensions[2] = 1;
             break;
         case 2:
             dc->data_dimensions[0] = dimensions[0];
             dc->data_dimensions[1] = dimensions[1];
+            dc->data_dimensions[2] = 1;
             break;
         case 3:
-            return NULL;
+            dc->data_dimensions[0] = dimensions[0];
+            dc->data_dimensions[1] = dimensions[1];
+            dc->data_dimensions[2] = dimensions[2];
+            break;
         default:
             fprintf(stderr, "Invalid dimensions size.");
             MPI_Abort(MPI_COMM_WORLD, 1);
             break;
     }
-    dc->count = (size_t) dc->data_dimensions[0] * (size_t) dc->data_dimensions[1];
+    dc->count = (size_t) dc->data_dimensions[0] * (size_t) dc->data_dimensions[1] * (size_t) dc->data_dimensions[2];
 
     find_network_dimensions(dc);
     create_network_communicators(dc);
@@ -176,43 +217,58 @@ extern struct dispatch_context *dispatch_context_new(const int *dimensions, MPI_
 }
 
 extern void dispatch_context_print(struct dispatch_context *dc) {
-    printf("[dispatch_context] my_rank = %d, world_size = %d, network_dimensions = [%d][%d], n_dimensions = %d, data_dimensions = [%d][%d]\n",
-           dc->my_rank, dc->world_size, dc->network_dimensions[0], dc->network_dimensions[1], dc->n_dimensions,
-           dc->data_dimensions[0], dc->data_dimensions[1]);
+    printf("[dispatch_context] my_rank = %d, world_size = %d, network_dimensions = [%d][%d][%d], n_dimensions = %d, data_dimensions = [%d][%d][%d]\n",
+           dc->my_rank, dc->world_size, dc->network_dimensions[0], dc->network_dimensions[1], dc->network_dimensions[2],
+           dc->n_dimensions,
+           dc->data_dimensions[0], dc->data_dimensions[1], dc->data_dimensions[2]);
 }
 
+struct futhark_opaque_envelope_1d_t {
+    struct futhark_i8_1d *v0;
+    struct futhark_i8_1d *v1;
+};
+
+struct futhark_opaque_envelope_2d_t {
+    struct futhark_i8_2d *v0;
+    struct futhark_i8_2d *v1;
+    struct futhark_i8_2d *v2;
+    struct futhark_i8_2d *v3;
+    struct futhark_i8_2d *v4;
+    struct futhark_i8_2d *v5;
+    struct futhark_i8_2d *v6;
+    struct futhark_i8_2d *v7;
+};
+
 static envelope_t get_inner_envelope_1d(struct dispatch_context *dc, struct futhark_context *fc, int thickness) {
     struct futhark_u8_1d *fut_chunk_data = futhark_new_u8_1d(fc, dc->chunk_info->data,
                                                              dc->chunk_info->dimensions[1] * dc->type);
-    struct futhark_u8_1d *fut_west;
-    struct futhark_u8_1d *fut_east;
     int thickness_x = min(thickness, dc->chunk_info->dimensions[1]);
-    int dimensions[2] = {1, thickness_x};
+    int dimensions[3] = {1, thickness_x, 1};
+    struct futhark_opaque_envelope_1d_t *fut_inner_envelope;
 
     futhark_context_sync(fc);
-    futhark_entry_get_envelope_1d(fc, &fut_west, &fut_east, fut_chunk_data, dimensions[1] * dc->type);
+    futhark_entry_get_envelope_1d(fc, &fut_inner_envelope, fut_chunk_data, dimensions[1] * dc->type);
     futhark_context_sync(fc);
-    futhark_free_u8_1d(fc, fut_chunk_data);
 
     envelope_t inner_envelope = (envelope_t) {0};
 
     // West
     {
         int start_x = dc->chunk_info->x;
-        chunk_info_init(&inner_envelope.west, dc->type, dimensions, 0, start_x, false);
-        futhark_values_u8_1d(fc, fut_west, inner_envelope.west.data);
+        chunk_info_init(&inner_envelope.west, dc->type, dimensions, 0, start_x, 0, true);
+        futhark_values_i8_1d(fc, fut_inner_envelope->v1, inner_envelope.west.data);
     }
 
     // East
     {
         int start_x = dc->chunk_info->x + dc->chunk_info->dimensions[1] - thickness_x;
-        chunk_info_init(&inner_envelope.east, dc->type, dimensions, 0, start_x, false);
-        futhark_values_u8_1d(fc, fut_east, inner_envelope.east.data);
+        chunk_info_init(&inner_envelope.east, dc->type, dimensions, 0, start_x, 0, true);
+        futhark_values_i8_1d(fc, fut_inner_envelope->v0, inner_envelope.east.data);
     }
 
     futhark_context_sync(fc);
-    futhark_free_u8_1d(fc, fut_west);
-    futhark_free_u8_1d(fc, fut_east);
+    futhark_free_opaque_envelope_1d_t(fc, fut_inner_envelope);
+    futhark_free_u8_1d(fc, fut_chunk_data);
     return inner_envelope;
 }
 
@@ -221,163 +277,184 @@ static envelope_t get_inner_envelope_2d(struct dispatch_context *dc, struct futh
                                                              dc->active_domain.dimensions[0],
                                                              dc->active_domain.dimensions[1] * dc->type);
 
-    struct futhark_u8_2d *fut_north_west;
-    struct futhark_u8_2d *fut_north;
-    struct futhark_u8_2d *fut_north_east;
-    struct futhark_u8_2d *fut_west;
-    struct futhark_u8_2d *fut_east;
-    struct futhark_u8_2d *fut_south_west;
-    struct futhark_u8_2d *fut_south;
-    struct futhark_u8_2d *fut_south_east;
-
     int thickness_y = min(thickness, dc->active_domain.dimensions[0]);
     int thickness_x = min(thickness, dc->active_domain.dimensions[1]);
 
+
+    struct futhark_opaque_envelope_2d_t *fut_inner_envelope;
+
     futhark_context_sync(fc);
-    futhark_entry_get_envelope_2d(fc, &fut_north_west, &fut_west, &fut_south_west,
-                                  &fut_south, &fut_south_east,
-                                  &fut_east, &fut_north_east, &fut_north,
-                                  fut_chunk_data, thickness_y,thickness_x * dc->type);
+    futhark_entry_get_envelope_2d(fc, &fut_inner_envelope, fut_chunk_data, thickness_y, thickness_x * dc->type);
     futhark_context_sync(fc);
-    futhark_free_u8_2d(fc, fut_chunk_data);
 
     envelope_t inner_envelope = (envelope_t) {0};
-
     // North-West
     {
-        int dimensions[2] = {thickness_y, thickness_x};
+        int dimensions[3] = {thickness_y, thickness_x, 1};
         int start_y = dc->active_domain.y;
         int start_x = dc->active_domain.x;
-        chunk_info_init(&inner_envelope.north_west, dc->type, dimensions, start_y, start_x, true);
-        futhark_values_u8_2d(fc, fut_north_west, inner_envelope.north_west.data);
+        chunk_info_init(&inner_envelope.north_west, dc->type, dimensions, start_y, start_x, 0, true);
+        futhark_values_i8_2d(fc, fut_inner_envelope->v3, inner_envelope.north_west.data);
     }
 
     // North
     {
-        int dimensions[2] = {thickness_y, dc->active_domain.dimensions[1]};
+        int dimensions[3] = {thickness_y, dc->active_domain.dimensions[1], 1};
         int start_y = dc->active_domain.y;
         int start_x = dc->active_domain.x;
-        chunk_info_init(&inner_envelope.north, dc->type, dimensions, start_y, start_x, true);
-        futhark_values_u8_2d(fc, fut_north, inner_envelope.north.data);
+        chunk_info_init(&inner_envelope.north, dc->type, dimensions, start_y, start_x, 0, true);
+        futhark_values_i8_2d(fc, fut_inner_envelope->v1, inner_envelope.north.data);
     }
 
     // North-East
     {
-        int dimensions[2] = {thickness_y, thickness_x};
+        int dimensions[3] = {thickness_y, thickness_x, 1};
         int start_y = dc->active_domain.y;
         int start_x = dc->active_domain.x + dc->active_domain.dimensions[1] - thickness_x;
-        chunk_info_init(&inner_envelope.north_east, dc->type, dimensions, start_y, start_x, true);
-        futhark_values_u8_2d(fc, fut_north_east, inner_envelope.north_east.data);
+        chunk_info_init(&inner_envelope.north_east, dc->type, dimensions, start_y, start_x, 0, true);
+        futhark_values_i8_2d(fc, fut_inner_envelope->v2, inner_envelope.north_east.data);
     }
 
     // West
     {
-        int dimensions[2] = {dc->active_domain.dimensions[0], thickness_x};
+        int dimensions[3] = {dc->active_domain.dimensions[0], thickness_x, 1};
         int start_y = dc->active_domain.y;
         int start_x = dc->active_domain.x;
-        chunk_info_init(&inner_envelope.west, dc->type, dimensions, start_y, start_x, true);
-        futhark_values_u8_2d(fc, fut_west, inner_envelope.west.data);
+        chunk_info_init(&inner_envelope.west, dc->type, dimensions, start_y, start_x, 0, true);
+        futhark_values_i8_2d(fc, fut_inner_envelope->v7, inner_envelope.west.data);
     }
 
     // East
     {
-        int dimensions[2] = {dc->chunk_info->dimensions[0], thickness_x};
+        int dimensions[3] = {dc->chunk_info->dimensions[0], thickness_x, 1};
         int start_y = dc->active_domain.y;
         int start_x = dc->active_domain.x + dc->active_domain.dimensions[1] - thickness_x;
-        chunk_info_init(&inner_envelope.east, dc->type, dimensions, start_y, start_x, true);
-        futhark_values_u8_2d(fc, fut_east, inner_envelope.east.data);
+        chunk_info_init(&inner_envelope.east, dc->type, dimensions, start_y, start_x, 0, true);
+        futhark_values_i8_2d(fc, fut_inner_envelope->v0, inner_envelope.east.data);
     }
 
     // South-West
     {
-        int dimensions[2] = {thickness_y, thickness_x};
+        int dimensions[3] = {thickness_y, thickness_x, 1};
         int start_y = dc->active_domain.y + dc->active_domain.dimensions[0] - thickness_y;
         int start_x = dc->active_domain.x;
-        chunk_info_init(&inner_envelope.south_west, dc->type, dimensions, start_y, start_x, true);
-        futhark_values_u8_2d(fc, fut_south_west, inner_envelope.south_west.data);
+        chunk_info_init(&inner_envelope.south_west, dc->type, dimensions, start_y, start_x, 0, true);
+        futhark_values_i8_2d(fc, fut_inner_envelope->v6, inner_envelope.south_west.data);
     }
 
     // South
     {
-        int dimensions[2] = {thickness_y, dc->active_domain.dimensions[1]};
+        int dimensions[3] = {thickness_y, dc->active_domain.dimensions[1], 1};
         int start_y = dc->active_domain.y + dc->active_domain.dimensions[0] - thickness_y;
         int start_x = dc->active_domain.x;
-        chunk_info_init(&inner_envelope.south, dc->type, dimensions, start_y, start_x, true);
-        futhark_values_u8_2d(fc, fut_south, inner_envelope.south.data);
+        chunk_info_init(&inner_envelope.south, dc->type, dimensions, start_y, start_x, 0, true);
+        futhark_values_i8_2d(fc, fut_inner_envelope->v4, inner_envelope.south.data);
     }
 
     // South-East
     {
-        int dimensions[2] = {thickness_y, thickness_x};
+        int dimensions[3] = {thickness_y, thickness_x, 1};
         int start_y = dc->active_domain.y + dc->active_domain.dimensions[0] - thickness_y;
         int start_x = dc->active_domain.x + dc->active_domain.dimensions[1] - thickness_x;
-        chunk_info_init(&inner_envelope.south_east, dc->type, dimensions, start_y, start_x, true);
-        futhark_values_u8_2d(fc, fut_south_east, inner_envelope.south_east.data);
+        chunk_info_init(&inner_envelope.south_east, dc->type, dimensions, start_y, start_x, 0, true);
+        futhark_values_i8_2d(fc, fut_inner_envelope->v5, inner_envelope.south_east.data);
     }
 
     futhark_context_sync(fc);
-    futhark_free_u8_2d(fc, fut_north_west);
-    futhark_free_u8_2d(fc, fut_north);
-    futhark_free_u8_2d(fc, fut_north_east);
-    futhark_free_u8_2d(fc, fut_west);
-    futhark_free_u8_2d(fc, fut_east);
-    futhark_free_u8_2d(fc, fut_south_west);
-    futhark_free_u8_2d(fc, fut_south);
-    futhark_free_u8_2d(fc, fut_south_east);
+    futhark_free_u8_2d(fc, fut_chunk_data);
+    futhark_free_opaque_envelope_2d_t(fc, fut_inner_envelope);
     return inner_envelope;
 }
 
-static void futhark_outer_envelope_1d_new(struct futhark_context *fc, envelope_t *outer_envelope,
-                                          void *f(struct futhark_context *, uint8_t *, int64_t),
-                                          void *futhark_envelope[2]) {
-    futhark_envelope[0] = f(fc, outer_envelope->west.data, outer_envelope->west.dimensions[1]);
-    futhark_envelope[1] = f(fc, outer_envelope->east.data, outer_envelope->east.dimensions[1]);
+static uint8_t *chunk_info_to_futhark(struct dispatch_context *dc, uint8_t *out, chunk_info_t *ci, char *fut_type) {
+    *out++ = 'b';
+    *out++ = 2;
+    *out++ = dc->n_dimensions;
+    memcpy(out, fut_type, 4);
+    out += 4;
+
+    int64_t dimensions64[3];
+    dimensions64[0] = (int64_t) ci->dimensions[0];
+    dimensions64[1] = (int64_t) ci->dimensions[1];
+    dimensions64[2] = (int64_t) ci->dimensions[2];
+
+    if (dc->n_dimensions == 1) {
+        memcpy(out, &dimensions64[1], 1 * sizeof(int64_t));
+    } else if (dc->n_dimensions == 2) {
+        memcpy(out, dimensions64, 2 * sizeof(int64_t));
+    }
+    out += dc->n_dimensions * sizeof(int64_t);
+    memcpy(out, ci->data, ci->count * dc->type);
+    out += ci->count * dc->type;
+    return out;
 }
 
-static void futhark_outer_envelope_2d_new(struct futhark_context *fc, envelope_t *outer_envelope,
-                                          void *f(struct futhark_context *, uint8_t *, int64_t, int64_t),
-                                          void *futhark_envelope[2]) {
-    futhark_envelope[0] = f(fc, outer_envelope->north_west.data, outer_envelope->north_west.dimensions[0],
-                            outer_envelope->north_west.dimensions[1]);
+static void *
+futhark_outer_envelope_1d_new(struct dispatch_context *dc, struct futhark_context *fc, envelope_t *outer_envelope,
+                              void *f(struct futhark_context *, const void *), char *fut_type) {
+    int64_t size_0 = 7 + 1 * sizeof(int64_t) + outer_envelope->east.count * dc->type;
+    int64_t size_1 = 7 + 1 * sizeof(int64_t) + outer_envelope->west.count * dc->type;
 
-    futhark_envelope[1] = f(fc, outer_envelope->west.data, outer_envelope->west.dimensions[0],
-                            outer_envelope->west.dimensions[1]);
+    void *opaque_struct = calloc(size_0 * size_1, sizeof(uint8_t));
+    uint8_t *out = (uint8_t *) opaque_struct;
 
-    futhark_envelope[2] = f(fc, outer_envelope->south_west.data, outer_envelope->south_west.dimensions[0],
-                            outer_envelope->south_west.dimensions[1]);
+    // East
+    out = chunk_info_to_futhark(dc, out, &outer_envelope->east, fut_type);
 
-    futhark_envelope[3] = f(fc, outer_envelope->south.data, outer_envelope->south.dimensions[0],
-                            outer_envelope->south.dimensions[1]);
+    // West
+    chunk_info_to_futhark(dc, out, &outer_envelope->west, fut_type);
 
-    futhark_envelope[4] = f(fc, outer_envelope->south_east.data, outer_envelope->south_east.dimensions[0],
-                            outer_envelope->south_east.dimensions[1]);
+    void *fut_opaque_struct = f(fc, opaque_struct);
+    futhark_context_sync(fc);
 
-    futhark_envelope[5] = f(fc, outer_envelope->east.data, outer_envelope->east.dimensions[0],
-                            outer_envelope->east.dimensions[1]);
+    free(opaque_struct);
+    return fut_opaque_struct;
+}
 
-    futhark_envelope[6] = f(fc, outer_envelope->north_east.data, outer_envelope->north_east.dimensions[0],
-                            outer_envelope->north_east.dimensions[1]);
+static void *
+futhark_outer_envelope_2d_new(struct dispatch_context *dc, struct futhark_context *fc, envelope_t *outer_envelope,
+                              void *f(struct futhark_context *, const void *),
+                              char *fut_type) {
+
+    int64_t size_0 = 7 + 2 * sizeof(int64_t) + outer_envelope->east.count * dc->type;
+    int64_t size_1 = 7 + 2 * sizeof(int64_t) + outer_envelope->north.count * dc->type;
+    int64_t size_2 = 7 + 2 * sizeof(int64_t) + outer_envelope->north_east.count * dc->type;
+    int64_t size_3 = 7 + 2 * sizeof(int64_t) + outer_envelope->north_west.count * dc->type;
+    int64_t size_4 = 7 + 2 * sizeof(int64_t) + outer_envelope->south.count * dc->type;
+    int64_t size_5 = 7 + 2 * sizeof(int64_t) + outer_envelope->south_east.count * dc->type;
+    int64_t size_6 = 7 + 2 * sizeof(int64_t) + outer_envelope->south_west.count * dc->type;
+    int64_t size_7 = 7 + 2 * sizeof(int64_t) + outer_envelope->west.count * dc->type;
+
+    void *opaque_struct = calloc(size_0 + size_1 + size_2 + size_3 + size_4 + size_5 + size_6 + size_7,
+                                 sizeof(uint8_t));
+    uint8_t *out = (uint8_t *) opaque_struct;
+
+    out = chunk_info_to_futhark(dc, out, &outer_envelope->east, fut_type);
+    out = chunk_info_to_futhark(dc, out, &outer_envelope->north, fut_type);
+    out = chunk_info_to_futhark(dc, out, &outer_envelope->north_east, fut_type);
+    out = chunk_info_to_futhark(dc, out, &outer_envelope->north_west, fut_type);
+    out = chunk_info_to_futhark(dc, out, &outer_envelope->south, fut_type);
+    out = chunk_info_to_futhark(dc, out, &outer_envelope->south_east, fut_type);
+    out = chunk_info_to_futhark(dc, out, &outer_envelope->south_west, fut_type);
+    chunk_info_to_futhark(dc, out, &outer_envelope->west, fut_type);
+
+    void *fut_opaque_struct = f(fc, opaque_struct);
+    futhark_context_sync(fc);
 
-    futhark_envelope[7] = f(fc, outer_envelope->north.data, outer_envelope->north.dimensions[0],
-                            outer_envelope->north.dimensions[1]);
+    free(opaque_struct);
+    return fut_opaque_struct;
 }
 
-extern void futhark_outer_envelope_new(struct dispatch_context *dc, struct futhark_context *fc,
-                                       envelope_t *outer_envelope, void *f(struct futhark_context *, uint8_t *, ...),
-                                       void *futhark_envelope[]) {
+extern void *futhark_outer_envelope_new(struct dispatch_context *dc, struct futhark_context *fc,
+                                        envelope_t *outer_envelope, void *f(struct futhark_context *, const void *),
+                                        char *fut_type) {
     switch (dc->n_dimensions) {
         case 1:
-            futhark_outer_envelope_1d_new(fc, outer_envelope,
-                                          (void *(*)(struct futhark_context *, uint8_t *, int64_t)) f,
-                                          futhark_envelope);
-            break;
+            return futhark_outer_envelope_1d_new(dc, fc, outer_envelope, f, fut_type);
         case 2:
-            futhark_outer_envelope_2d_new(fc, outer_envelope,
-                                          (void *(*)(struct futhark_context *, uint8_t *, int64_t, int64_t)) f,
-                                          futhark_envelope);
-            break;
+            return futhark_outer_envelope_2d_new(dc, fc, outer_envelope, f, fut_type);
         default:
-            break;
+            return NULL;
     }
 }
 
@@ -397,9 +474,9 @@ static envelope_t get_outer_envelope_1d(struct dispatch_context *dc, int thickne
         MPI_Isend(inner_envelope->west.data, send_count, dc->datatype, dest_source_x, WEST_COLUMN_TAG,
                   dc->communicators[1], &requests[i_request++]);
 
-        int dimensions[2] = {1, min(thickness, dc->chunks_info[dest_source].dimensions[1])};
+        int dimensions[3] = {1, min(thickness, dc->chunks_info[dest_source].dimensions[1]), 1};
         int start_x = dc->chunks_info[dest_source].x + dc->chunks_info[dest_source].dimensions[1] - dimensions[1];
-        chunk_info_init(&outer_envelope.west, dc->type, dimensions, 0, start_x, true);
+        chunk_info_init(&outer_envelope.west, dc->type, dimensions, 0, start_x, 0, true);
 
         MPI_Irecv(outer_envelope.west.data, (int) outer_envelope.west.count, dc->datatype,
                   dest_source_x, EAST_COLUMN_TAG, dc->communicators[1], &requests[i_request++]);
@@ -418,9 +495,9 @@ static envelope_t get_outer_envelope_1d(struct dispatch_context *dc, int thickne
         MPI_Isend(inner_envelope_east, send_count, dc->datatype, dest_source_x, EAST_COLUMN_TAG,
                   dc->communicators[1], &requests[i_request++]);
 
-        int dimensions[2] = {1, min(thickness, dc->chunks_info[dest_source].dimensions[1])};
+        int dimensions[3] = {1, min(thickness, dc->chunks_info[dest_source].dimensions[1]), 1};
         int start_x = dc->chunks_info[dest_source].x + dimensions[1];
-        chunk_info_init(&outer_envelope.east, dc->type, dimensions, 0, start_x, true);
+        chunk_info_init(&outer_envelope.east, dc->type, dimensions, 0, start_x, 0, true);
 
         MPI_Irecv(outer_envelope.east.data, (int) outer_envelope.east.count, dc->datatype,
                   dest_source_x, WEST_COLUMN_TAG, dc->communicators[1], &requests[i_request]);
@@ -449,13 +526,13 @@ static envelope_t get_outer_envelope_2d(struct dispatch_context *dc, int thickne
                   dc->communicators[2], &requests[i_request++]);
 
         /* Neighbour send south row, which correspond to north envelope */
-        int dimensions[2] = {
+        int dimensions[3] = {
                 min(thickness, dc->chunks_info[dest_source].dimensions[0]),
-                dc->chunks_info[dest_source].dimensions[1]
+                dc->chunks_info[dest_source].dimensions[1], 1
         };
         int start_y = dc->chunks_info[dest_source].y + dc->chunks_info[dest_source].dimensions[0] - dimensions[0];
         int start_x = dc->chunks_info[dest_source].x;
-        chunk_info_init(&outer_envelope.north, dc->type, dimensions, start_y, start_x, true);
+        chunk_info_init(&outer_envelope.north, dc->type, dimensions, start_y, start_x, 0, true);
 
         MPI_Irecv(outer_envelope.north.data, (int) outer_envelope.north.count, dc->datatype,
                   dest_source_y, SOUTH_ROW_TAG, dc->communicators[2], &requests[i_request++]);
@@ -472,13 +549,13 @@ static envelope_t get_outer_envelope_2d(struct dispatch_context *dc, int thickne
                   dc->communicators[1], &requests[i_request++]);
 
         /* Neighbour send west column, which correspond to east envelope */
-        int dimensions[2] = {
+        int dimensions[3] = {
                 dc->chunks_info[dest_source].dimensions[0],
-                min(thickness, dc->chunks_info[dest_source].dimensions[1])
+                min(thickness, dc->chunks_info[dest_source].dimensions[1]), 1
         };
         int start_y = dc->chunks_info[dest_source].y;
         int start_x = dc->chunks_info[dest_source].x + dc->chunks_info[dest_source].dimensions[1] - dimensions[1];
-        chunk_info_init(&outer_envelope.east, dc->type, dimensions, start_y, start_x, true);
+        chunk_info_init(&outer_envelope.east, dc->type, dimensions, start_y, start_x, 0, true);
         MPI_Irecv(outer_envelope.east.data, (int) outer_envelope.east.count, dc->datatype, dest_source_x,
                   WEST_COLUMN_TAG, dc->communicators[1], &requests[i_request++]);
     }
@@ -494,13 +571,13 @@ static envelope_t get_outer_envelope_2d(struct dispatch_context *dc, int thickne
                   dc->communicators[2], &requests[i_request++]);
 
         /* Neighbour send north row, which correspond to south envelope */
-        int dimensions[2] = {
+        int dimensions[3] = {
                 min(thickness, dc->chunks_info[dest_source].dimensions[0]),
-                dc->chunks_info[dest_source].dimensions[1]
+                dc->chunks_info[dest_source].dimensions[1], 1
         };
         int start_y = dc->chunks_info[dest_source].y;
         int start_x = dc->chunks_info[dest_source].x;
-        chunk_info_init(&outer_envelope.south, dc->type, dimensions, start_y, start_x, true);
+        chunk_info_init(&outer_envelope.south, dc->type, dimensions, start_y, start_x, 0, true);
         MPI_Irecv(outer_envelope.south.data, (int) outer_envelope.south.count, dc->datatype, dest_source_y,
                   NORTH_ROW_TAG, dc->communicators[2], &requests[i_request++]);
     }
@@ -515,13 +592,13 @@ static envelope_t get_outer_envelope_2d(struct dispatch_context *dc, int thickne
                   dc->communicators[1], &requests[i_request++]);
 
         /* Neighbour send west column, which correspond to east envelope */
-        int dimensions[2] = {
+        int dimensions[3] = {
                 dc->chunks_info[dest_source].dimensions[0],
-                min(thickness, dc->chunks_info[dest_source].dimensions[1])
+                min(thickness, dc->chunks_info[dest_source].dimensions[1]), 1
         };
         int start_y = dc->chunks_info[dest_source].y;
         int start_x = dc->chunks_info[dest_source].x;
-        chunk_info_init(&outer_envelope.west, dc->type, dimensions, start_y, start_x, true);
+        chunk_info_init(&outer_envelope.west, dc->type, dimensions, start_y, start_x, 0, true);
         MPI_Irecv(outer_envelope.west.data, (int) outer_envelope.west.count, dc->datatype, dest_source_x,
                   EAST_COLUMN_TAG, dc->communicators[1], &requests[i_request++]);
     }
@@ -537,10 +614,10 @@ static envelope_t get_outer_envelope_2d(struct dispatch_context *dc, int thickne
                   MPI_COMM_WORLD, &requests[i_request++]);
 
         /* Neighbour send south-west cell, which correspond to north-east cell */
-        int dimensions[2] = {thickness, thickness};
+        int dimensions[3] = {thickness, thickness, 1};
         int start_y = dc->chunks_info[dest_source].y + dc->chunks_info[dest_source].dimensions[0] + dimensions[0];
         int start_x = dc->chunks_info[dest_source].x;
-        chunk_info_init(&outer_envelope.north_east, dc->type, dimensions, start_y, start_x, true);
+        chunk_info_init(&outer_envelope.north_east, dc->type, dimensions, start_y, start_x, 0, true);
         MPI_Irecv(outer_envelope.north_east.data, (int) outer_envelope.north_east.count, dc->datatype, dest_source,
                   SOUTH_WEST_CELLS_TAG, MPI_COMM_WORLD, &requests[i_request++]);
     }
@@ -556,10 +633,10 @@ static envelope_t get_outer_envelope_2d(struct dispatch_context *dc, int thickne
                   MPI_COMM_WORLD, &requests[i_request++]);
 
         /* Neighbour send north-west cell, which correspond to south-east cell */
-        int dimensions[2] = {thickness, thickness};
+        int dimensions[3] = {thickness, thickness, 1};
         int start_y = dc->chunks_info[dest_source].y;
         int start_x = dc->chunks_info[dest_source].x;
-        chunk_info_init(&outer_envelope.south_east, dc->type, dimensions, start_y, start_x, true);
+        chunk_info_init(&outer_envelope.south_east, dc->type, dimensions, start_y, start_x, 0, true);
         MPI_Irecv(outer_envelope.south_east.data, (int) outer_envelope.south_east.count, dc->datatype, dest_source,
                   NORTH_WEST_CELLS_TAG, MPI_COMM_WORLD, &requests[i_request++]);
     }
@@ -575,10 +652,10 @@ static envelope_t get_outer_envelope_2d(struct dispatch_context *dc, int thickne
                   MPI_COMM_WORLD, &requests[i_request++]);
 
         /* Neighbour send north-east cell, which correspond to south-west cell */
-        int dimensions[2] = {thickness, thickness};
+        int dimensions[3] = {thickness, thickness, 1};
         int start_y = dc->chunks_info[dest_source].y;
         int start_x = dc->chunks_info[dest_source].x + dc->chunks_info[dest_source].dimensions[1] - thickness;
-        chunk_info_init(&outer_envelope.south_west, dc->type, dimensions, start_y, start_x, true);
+        chunk_info_init(&outer_envelope.south_west, dc->type, dimensions, start_y, start_x, 0, true);
         MPI_Irecv(outer_envelope.south_west.data, (int) outer_envelope.south_west.count, dc->datatype, dest_source,
                   NORTH_EAST_CELLS_TAG, MPI_COMM_WORLD, &requests[i_request++]);
     }
@@ -594,10 +671,10 @@ static envelope_t get_outer_envelope_2d(struct dispatch_context *dc, int thickne
                   MPI_COMM_WORLD, &requests[i_request++]);
 
         /* Neighbour send south-east cell, which correspond to north-west cell */
-        int dimensions[2] = {thickness, thickness};
+        int dimensions[3] = {thickness, thickness, 1};
         int start_y = dc->chunks_info[dest_source].y + dc->chunks_info[dest_source].dimensions[0] - thickness;
         int start_x = dc->chunks_info[dest_source].x + dc->chunks_info[dest_source].dimensions[1] - thickness;
-        chunk_info_init(&outer_envelope.north_west, dc->type, dimensions, start_y, start_x, true);
+        chunk_info_init(&outer_envelope.north_west, dc->type, dimensions, start_y, start_x, 0, true);
         MPI_Irecv(outer_envelope.north_west.data, (int) outer_envelope.north_west.count, dc->datatype, dest_source,
                   SOUTH_EAST_CELLS_TAG, MPI_COMM_WORLD, &requests[i_request++]);
     }
@@ -694,20 +771,13 @@ extern void dispatch_context_free(struct dispatch_context *dc) {
     MPI_Comm_free(&dc->communicators[0]);
 }
 
-extern void futhark_outer_envelope_free(struct dispatch_context *dc, struct futhark_context *fc,
-                                        int f(struct futhark_context *, void *), void *foe[]) {
-    for (int i = 0; i < (dc->n_dimensions * dc->n_dimensions) * 2; ++i) {
-        f(fc, foe[i]);
-    }
-}
-
-static void set_active_domain_1d( struct dispatch_context *dc, struct futhark_context *fc, int dimensions[1], int x) {
-    chunk_info_init(&dc->active_domain, dc->type, dimensions, 0, x, true);
+static void set_active_domain_1d(struct dispatch_context *dc, struct futhark_context *fc, int dimensions[1], int x) {
+    chunk_info_init(&dc->active_domain, dc->type, dimensions, 0, x, 0, true);
     int64_t dimensions64[1];
     dimensions64[0] = dimensions[0] * dc->type;
 
     struct futhark_u8_1d *fut_chunk_data = futhark_new_u8_1d(fc, dc->chunk_info->data, dimensions64[0] * dc->type);
-    struct futhark_i64_1d* fut_dimension = futhark_new_i64_1d(fc, dimensions64, 1);
+    struct futhark_i64_1d *fut_dimension = futhark_new_i64_1d(fc, dimensions64, 1);
 
     struct futhark_u8_1d *subdomain;
     futhark_context_sync(fc);
@@ -720,9 +790,10 @@ static void set_active_domain_1d( struct dispatch_context *dc, struct futhark_co
     futhark_free_i64_1d(fc, fut_dimension);
     futhark_free_u8_1d(fc, subdomain);
 }
+
 static void
 set_active_domain_2d(struct dispatch_context *dc, struct futhark_context *fc, int dimensions[2], int y, int x) {
-    chunk_info_init(&dc->active_domain, dc->type, dimensions, y, x, true);
+    chunk_info_init(&dc->active_domain, dc->type, dimensions, y, x, 0, true);
 
     int64_t dimensions64[2];
     dimensions64[0] = dimensions[0];
@@ -746,7 +817,35 @@ set_active_domain_2d(struct dispatch_context *dc, struct futhark_context *fc, in
     futhark_free_u8_2d(fc, subdomain);
 }
 
-extern void set_active_domain(struct dispatch_context *dc, struct futhark_context *fc, int *dimensions, int y, int x) {
+static void
+set_active_domain_3d(struct dispatch_context *dc, struct futhark_context *fc, int dimensions[3], int y, int x, int z) {
+    chunk_info_init(&dc->active_domain, dc->type, dimensions, y, x, z, true);
+
+    int64_t dimensions64[3];
+    dimensions64[0] = dimensions[0];
+    dimensions64[1] = dimensions[1] * dc->type;
+    dimensions64[2] = dimensions[2];
+
+    struct futhark_u8_3d *fut_chunk_data = futhark_new_u8_3d(fc, dc->chunk_info->data, dimensions64[0],
+                                                             dimensions64[1], dimensions64[2]);
+
+    struct futhark_i64_1d *fut_dimensions = futhark_new_i64_1d(fc, dimensions64, 3);
+    struct futhark_u8_3d *subdomain;
+
+    futhark_context_sync(fc);
+    futhark_entry_get_subdomain_3d(fc, &subdomain, fut_chunk_data, y, x * dc->type, z, fut_dimensions);
+
+    futhark_context_sync(fc);
+    futhark_values_u8_3d(fc, subdomain, dc->active_domain.data);
+    futhark_context_sync(fc);
+
+    futhark_free_u8_3d(fc, fut_chunk_data);
+    futhark_free_i64_1d(fc, fut_dimensions);
+    futhark_free_u8_3d(fc, subdomain);
+}
+
+extern void
+set_active_domain(struct dispatch_context *dc, struct futhark_context *fc, int *dimensions, int y, int x, int z) {
     switch (dc->n_dimensions) {
         case 1:
             set_active_domain_1d(dc, fc, dimensions, x);
@@ -754,6 +853,9 @@ extern void set_active_domain(struct dispatch_context *dc, struct futhark_contex
         case 2:
             set_active_domain_2d(dc, fc, dimensions, y, x);
             break;
+        case 3:
+            set_active_domain_3d(dc, fc, dimensions, y, x, z);
+            break;
         default:
             break;
     }
diff --git a/futhark_mpi/dispatch.h b/futhark_mpi/dispatch.h
index df9df9c33473795147231b742226231fb1f01f45..f39db1a2fe8e68f605fa9cb236ca0b30a83a734b 100644
--- a/futhark_mpi/dispatch.h
+++ b/futhark_mpi/dispatch.h
@@ -1,9 +1,20 @@
 #ifndef _DISPATCH_H_
 #define _DISPATCH_H_
 
-#include "futhark.h"
+#include "../game_of_life/gol.h"
 #include "chunk_info.h"
 
+#define FUT_I8  "  i8"
+#define FUT_U8  "  i8"
+#define FUT_I16 " i16"
+#define FUT_U16 " i16"
+#define FUT_I32 " i32"
+#define FUT_U32 " i32"
+#define FUT_I64 " i64"
+#define FUT_U64 " i64"
+#define FUT_F32 " f32"
+#define FUT_F64 " f64"
+
 struct dispatch_context;
 
 typedef struct envelope {
@@ -25,9 +36,9 @@ extern envelope_t get_inner_envelope(struct dispatch_context *dc, struct futhark
 
 extern envelope_t get_outer_envelope(struct dispatch_context *dc, struct futhark_context *fc, int thickness);
 
-extern void futhark_outer_envelope_new(struct dispatch_context *dc, struct futhark_context *fc,
-                                       envelope_t *outer_envelope, void *f(struct futhark_context *, uint8_t *, ...),
-                                       void *futhark_envelope[]);
+extern void *
+futhark_outer_envelope_new(struct dispatch_context *dc, struct futhark_context *fc, envelope_t *outer_envelope,
+                           void *f(struct futhark_context *, const void *), char *fut_type);
 
 extern chunk_info_t get_chunk_info(struct dispatch_context *dc);
 
@@ -35,8 +46,8 @@ extern void *get_data(struct dispatch_context *dc);
 
 extern void dispatch_context_free(struct dispatch_context *dc);
 
-extern void futhark_outer_envelope_free(struct dispatch_context *dc, struct futhark_context *fc,
-                                        int f(struct futhark_context *, void *), void *foe[]);
+extern void
+set_active_domain(struct dispatch_context *dc, struct futhark_context *fc, int *dimensions, int y, int x, int z);
 
 extern void envelope_free(envelope_t *envelope);
 
diff --git a/futhark_mpi/envelope.fut b/futhark_mpi/envelope.fut
index 2bebb9345feb818d19afdb5b2d9fdb33e9764d99..1798ecca04da25e89d9c55db8d327f7c84c3a9a0 100644
--- a/futhark_mpi/envelope.fut
+++ b/futhark_mpi/envelope.fut
@@ -1,9 +1,25 @@
-entry get_envelope_1d [n] (xs: [n]u8) (thickness: i64) : ([]u8, []u8) =
+type envelope_1d_t [n] = {
+    west: [n]u8, -- 1
+    east: [n]u8  -- 0
+}
+
+type envelope_2d_t [n][m][ty][tx] = {
+    north_west: [ty][tx]u8,  -- 3
+    west: [n][tx]u8,         -- 7
+    south_west: [ty][tx]u8,  -- 6
+    south: [ty][m]u8,        -- 4
+    south_east: [ty][tx]u8,  -- 5
+    east: [n][tx]u8,         -- 0
+    north_east: [ty][tx]u8,  -- 2
+    north: [ty][m]u8         -- 1
+}
+
+entry get_envelope_1d [n] (xs: [n]u8) (thickness: i64) : envelope_1d_t [thickness] =
     let west = xs[0:thickness] :> [thickness]u8
     let east = xs[n - thickness:] :> [thickness]u8
-    in (west, east)
+    in {west, east}
 
-entry get_envelope_2d [n][m] (matrix: [n][m]u8) (thickness_y: i64) (thickness_x: i64): ([][]u8, [][]u8, [][]u8, [][]u8, [][]u8, [][]u8, [][]u8, [][]u8) =
+entry get_envelope_2d [n][m] (matrix: [n][m]u8) (thickness_y: i64) (thickness_x: i64): envelope_2d_t [n][m][thickness_y][thickness_x] =
     let north = matrix[0:thickness_y] :> [thickness_y][m]u8
 
     let north_west = matrix[0:thickness_y,0:thickness_x] :> [thickness_y][thickness_x]u8
@@ -17,10 +33,51 @@ entry get_envelope_2d [n][m] (matrix: [n][m]u8) (thickness_y: i64) (thickness_x:
     let east = matrix[:,m - (thickness_x):] :> [n][thickness_x]u8
     let west = matrix[:,0:thickness_x] :> [n][thickness_x]u8
 
-    in (north_west, west, south_west, south, south_east, east, north_east, north)
+    in {north_west, west, south_west, south, south_east, east, north_east, north}
+
+ -- entry get_envelope_3d [n][m][l] (cube:[n][m][l]u8) (thickness_y: i64) (thickness_x: i64) (thickness_z: i64):
+ --    let north_front = matrix[0:thickness_y,:,0:thickness_z]
+ --    let south_front = matrix[n - thickness_y:,:,0:thickness_z]
+
+ --    let west_front = matrix[:,0:thickness_x:0:thickness_z]
+ --    let east_front = matrix[:,m - (thickness_x):0:thickness_z]
+
+ --    let north_west_front = matrix[0:thickness_y,0:thickness_x,0:thickness_z]
+ --    let north_east_front = matrix[0:thickness_y,m - thickness_x:m,0:thickness_z]
+ --    let south_east_front = matrix[n - thickness_y:n,m - thickness_x:m,0:thickness_z]
+ --    let south_west_front = matrix[n - thickness_y:n,0:thickness_x,0:thickness_z]
+
+ --    let north_back = matrix[0:thickness_y,:,0:l-thickness_z]
+ --    let south_back = matrix[n - thickness_y:,:,0:l-thickness_z]
+
+ --    let west_back = matrix[:,0:thickness_x:0:l-thickness_z]
+ --    let east_back = matrix[:,m - (thickness_x):l-thickness_z]
+
+ --    let north_west_back = matrix[0:thickness_y,0:thickness_x,l-thickness_z]
+ --    let north_east_back = matrix[0:thickness_y,m - thickness_x:m,l-thickness_z]
+ --    let south_east_back = matrix[n - thickness_y:n,m - thickness_x:m,l-thickness_z]
+ --    let south_west_back = matrix[n - thickness_y:n,0:thickness_x,l-thickness_z]
+
+
+ --   -- let north_right = matrix[0:thickness_y,m-1:m,:]
+ --   -- let south_right = matrix[n - thickness_y:m-1:m,:]
+
+ --   -- let west_back = matrix[:,0:thickness_x:0:l-thickness_z]
+ --   -- let east_back = matrix[:,m - (thickness_x):l-thickness_z]
+
+ --   -- let north_west_back = matrix[0:thickness_y,0:thickness_x,l-thickness_z]
+ --   -- let north_east_back = matrix[0:thickness_y,m - thickness_x:m,l-thickness_z]
+ --   -- let south_east_back = matrix[n - thickness_y:n,m - thickness_x:m,l-thickness_z]
+ --   -- let south_west_back = matrix[n - thickness_y:n,0:thickness_x,l-thickness_z]
+
+
+ --    in (north_west, west, south_west, south, south_east, east, north_east, north)
 
 entry get_subdomain_1d [n] (xs: [n]u8) (x:i64) (dimensions: [1]i64) : []u8 =
     xs[x:x+dimensions[0]]
 
 entry get_subdomain_2d [n][m] (matrix: [n][m]u8) (y:i64) (x:i64) (dimensions: [2]i64) : [][]u8 =
     matrix[y:y+dimensions[0],x:x+dimensions[1]]
+
+entry get_subdomain_3d [n][m][l] (cube:[n][m][l]u8) (y:i64) (x:i64) (z:i64) (dimensions: [3]i64) : [][][]u8 =
+    cube[y:y+dimensions[0],x:x+dimensions[1],z:z+dimensions[2]]
diff --git a/futhark_mpi/futhark.h b/futhark_mpi/futhark.h
deleted file mode 100644
index 111f74f76b5fff0cfb7024a9048ea8f9d6b709ae..0000000000000000000000000000000000000000
--- a/futhark_mpi/futhark.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * File to resolve symbol when programming
- */
-#ifndef _FUTHARK_H_
-#define _FUTHARK_H_
-#include <stdint.h>
-
-struct futhark_context;
-
-struct futhark_u8_1d;
-struct futhark_u8_2d;
-
-struct futhark_u8_1d *futhark_new_u8_1d(struct futhark_context *ctx, const
-uint8_t *data, int64_t dim0);
-
-int futhark_free_u8_1d(struct futhark_context *ctx, struct futhark_u8_1d *arr);
-
-int futhark_values_u8_1d(struct futhark_context *ctx, struct futhark_u8_1d *arr,
-                         uint8_t *data);
-
-int futhark_entry_get_envelope_1d(struct futhark_context *ctx,
-                                  struct futhark_u8_1d **out0,
-                                  struct futhark_u8_1d **out1, const
-                                  struct futhark_u8_1d *in0, int64_t in1);
-
-int futhark_entry_get_envelope_2d(struct futhark_context *ctx,
-                                  struct futhark_u8_2d **out0,
-                                  struct futhark_u8_2d **out1,
-                                  struct futhark_u8_2d **out2,
-                                  struct futhark_u8_2d **out3,
-                                  struct futhark_u8_2d **out4,
-                                  struct futhark_u8_2d **out5,
-                                  struct futhark_u8_2d **out6,
-                                  struct futhark_u8_2d **out7, const
-                                  struct futhark_u8_2d *in0, int64_t in1,
-                                  int64_t in2);
-
-int futhark_entry_get_subdomain_1d(struct futhark_context *ctx,
-                                   struct futhark_u8_1d **out0, const
-                                   struct futhark_u8_1d *in0, const int64_t in1,
-                                   const struct futhark_i64_1d *in2);
-int futhark_entry_get_subdomain_2d(struct futhark_context *ctx,
-                                   struct futhark_u8_2d **out0, const
-                                   struct futhark_u8_2d *in0, const int64_t in1,
-                                   const int64_t in2, const
-                                   struct futhark_i64_1d *in3);
-
-struct futhark_u8_1d *futhark_new_u8_1d(struct futhark_context *ctx, const uint8_t *data, int64_t dim0);
-
-struct futhark_u8_2d *futhark_new_u8_2d(struct futhark_context *ctx, const
-uint8_t *data, int64_t dim0, int64_t dim1);
-
-int futhark_free_u8_2d(struct futhark_context *ctx, struct futhark_u8_2d *arr);
-
-int futhark_values_u8_2d(struct futhark_context *ctx, struct futhark_u8_2d *arr,
-                         uint8_t *data);
-
-int futhark_context_sync(struct futhark_context *ctx);
-
-struct futhark_i64_1d ;
-struct futhark_i64_1d *futhark_new_i64_1d(struct futhark_context *ctx, const
-int64_t *data, int64_t dim0);
-int futhark_free_i64_1d(struct futhark_context *ctx,
-                        struct futhark_i64_1d *arr);
-int futhark_values_i64_1d(struct futhark_context *ctx,
-                          struct futhark_i64_1d *arr, int64_t *data);
-
-#endif //_FUTHARK_H_
diff --git a/game_of_life/.gitignore b/game_of_life/.gitignore
index 4a4fe12cf442706310b1ad0fe5cd3e799a9e91df..c8ce81d6bddf761b53cfda7b6ac5f495c07f7893 100644
--- a/game_of_life/.gitignore
+++ b/game_of_life/.gitignore
@@ -128,3 +128,4 @@ fabric.properties
 .idea
 gol.h
 gol.c
+benchmark/*.png
diff --git a/game_of_life/benchmark/main.c b/game_of_life/benchmark/main.c
index 67f58deafe9a941c599b551c4908d1e6a7afd88f..8e9e802286f240b15a22bba0ccbd2a33fe277b16 100644
--- a/game_of_life/benchmark/main.c
+++ b/game_of_life/benchmark/main.c
@@ -31,19 +31,15 @@ void init_chunk_board(chunk_info_t *ci) {
 void compute_next_chunk_board(struct dispatch_context *dc, struct futhark_context *fc, chunk_info_t *ci) {
     envelope_t outer_envelope = get_outer_envelope(dc, fc, 1);
 
-    struct futhark_i8_2d *fut_outer_envelope[8] = {0};
-    futhark_outer_envelope_new(dc, fc, &outer_envelope,
-                               (void *(*)(struct futhark_context *, uint8_t *, ...)) futhark_new_i8_2d,
-                               (void **) fut_outer_envelope);
+    struct futhark_opaque_envelope_2d_i8 *fut_outer_envelope = futhark_outer_envelope_new(dc, fc, &outer_envelope,
+                                                                                          futhark_restore_opaque_envelope_2d_i8,
+                                                                                          FUT_I8);
 
     struct futhark_i8_2d *fut_chunk_board = futhark_new_i8_2d(fc, ci->data, ci->dimensions[0], ci->dimensions[1]);
     struct futhark_i8_2d *fut_next_chunk_board;
 
     futhark_context_sync(fc);
-    futhark_entry_next_chunk_board(fc, &fut_next_chunk_board, fut_chunk_board,
-                                   fut_outer_envelope[0], fut_outer_envelope[7], fut_outer_envelope[6],
-                                   fut_outer_envelope[1], fut_outer_envelope[5],
-                                   fut_outer_envelope[2], fut_outer_envelope[3], fut_outer_envelope[4]);
+    futhark_entry_next_chunk_board(fc, &fut_next_chunk_board, fut_chunk_board, fut_outer_envelope);
     futhark_context_sync(fc);
 
     futhark_values_i8_2d(fc, fut_next_chunk_board, ci->data);
@@ -51,8 +47,7 @@ void compute_next_chunk_board(struct dispatch_context *dc, struct futhark_contex
 
     futhark_free_i8_2d(fc, fut_next_chunk_board);
     futhark_free_i8_2d(fc, fut_chunk_board);
-    futhark_outer_envelope_free(dc, fc, (int (*)(struct futhark_context *, void *)) futhark_free_i8_2d,
-                                (void **) fut_outer_envelope);
+    futhark_free_opaque_envelope_2d_i8(fc, fut_outer_envelope);
     envelope_free(&outer_envelope);
 }
 
@@ -70,6 +65,7 @@ int main(int argc, char *argv[]) {
     struct futhark_context_config *fut_config = futhark_context_config_new();
     int nb_devices = atoi(argv[1]);
 #if defined(FUTHARK_BACKEND_cuda) || defined(FUTHARK_BACKEND_opencl)
+    futhark_context_config_list_devices(fut_config);
     char device[4] = {0};
     snprintf(device, sizeof(device), "#%d", my_rank % nb_devices);
     futhark_context_config_set_device(fut_config, device);
diff --git a/game_of_life/benchmark/plot_result.py b/game_of_life/benchmark/plot_result.py
old mode 100644
new mode 100755
index 692d9cf153159da1e1665f899dba4ad57d933607..fb43ade73e3571161eb7c94ec90ac3a03fbbe806
--- a/game_of_life/benchmark/plot_result.py
+++ b/game_of_life/benchmark/plot_result.py
@@ -12,9 +12,9 @@ RESULT_APPEND_FILENAME = "result_and_speedup.png"
 
 def plot(xs, ys, stds):
     fig, ax = plt.subplots()
-    ax.plot(xs, ys, label="Multicore")
+    ax.plot(xs, ys, label="OpenCL")
     ax.set(xlabel="Nombre de tâches", ylabel='Temps (secondes)',
-           title=f"Jeu de la vie\nCalcul de la prochaine génération avec\n une matrice de 60000x60000")
+           title=f"Jeu de la vie\nCalcul de 100 générations avec \nune matrice de 30'000x30'000")
     ax.grid()
     plt.tight_layout()
     plt.legend()
@@ -27,7 +27,7 @@ def plot_speedup(xs, ys):
     ax.plot(xs, ys, label="Speedup obtenu")
     ax.plot(xs, xs, label="Speedup idéal")
     ax.set(xlabel="Nombre de tâches", ylabel='Speedup',
-           title=f"Jeu de la vie\nSpeedup du calcul de la prochaine génération avec\n une matrice de 60000x60000")
+           title=f"Jeu de la vie\nSpeedup du calcul de 100 générations avec \nune matrice de 30'000x30'000")
     ax.grid()
     plt.tight_layout()
     plt.legend()
@@ -36,7 +36,7 @@ def plot_speedup(xs, ys):
 
 
 def main():
-    results = np.genfromtxt('results/gol-multicore-2021-06-05.csv', delimiter=';')
+    results = np.genfromtxt('results/gol-opencl-2021-06-13.csv', delimiter=';')
     results_ordered = {}
     for result in results:
         key = int(result[0])
diff --git a/game_of_life/gol.fut b/game_of_life/gol.fut
index 98a053873a87255b39df13dcf2540c865bf9b33e..55cd8f3c76b0663087ac2fceabcc44d9a1d6fff8 100644
--- a/game_of_life/gol.fut
+++ b/game_of_life/gol.fut
@@ -1,9 +1,33 @@
 module env = import "../futhark_mpi/envelope"
 
-entry get_envelope_1d = env.get_envelope_1d
-entry get_envelope_2d = env.get_envelope_2d
+type envelope_1d_t [n] = env.envelope_1d_t [n]
+type envelope_2d_t [n][m][ty][tx] = env.envelope_2d_t [n][m][ty][tx]
+
+type~ envelope_1d_i8  = {
+    west: []i8,   -- 1
+    east: []i8    -- 0
+}
+
+type~ envelope_2d_i8  = {
+    north_west: [][]i8,  -- 3
+    west: [][]i8,        -- 7
+    south_west: [][]i8,  -- 6
+    south: [][]i8,       -- 4
+    south_east: [][]i8,  -- 5
+    east: [][]i8,        -- 0
+    north_east: [][]i8,  -- 2
+    north: [][]i8        -- 1
+}
+
+entry get_envelope_1d [n] (xs: [n]u8) (thickness: i64) : envelope_1d_t [thickness] =
+    env.get_envelope_1d xs thickness
+
+entry get_envelope_2d [n][m] (matrix: [n][m]u8) (thickness_y: i64) (thickness_x: i64): envelope_2d_t [n][m][thickness_y][thickness_x] =
+    env.get_envelope_2d matrix thickness_y thickness_x
+
 entry get_subdomain_1d = env.get_subdomain_1d
 entry get_subdomain_2d = env.get_subdomain_2d
+entry get_subdomain_3d = env.get_subdomain_3d
 
 let count_neighbours [n][m] (board: [n][m]i8) : [n][m]i8 =
     let north = rotate (-1) board
@@ -21,35 +45,33 @@ let count_neighbours [n][m] (board: [n][m]i8) : [n][m]i8 =
         (zip3 nwr nr ner) (zip3 wr br er) (zip3 swr sr ser))
     (zip3 north_west north north_east) (zip3 west board east) (zip3 south_west south south_east)
 
-let augment_board [n][m] (chunk_board:[n][m]i8)
-                         (nw:[1][1]i8) (no:[1][m]i8) (ne:[1][1]i8)
-                                 (we:[n][1]i8) (ea:[n][1]i8)
-                         (sw:[1][1]i8) (so:[1][m]i8) (se:[1][1]i8) :[][]i8 =
+let augment_board [n][m] (chunk_board:[n][m]i8) (envelope: envelope_2d_i8) :[][]i8 =
     tabulate_2d (n+2) (m+2) (\i j ->
             -- North-West
-            if (i == 0 && j == 0) then nw[0,0]
+            if (i == 0 && j == 0) then envelope.north_west[0,0]
             -- North-East
-            else if (i == 0 && j == m+1) then ne[0,0]
+            else if (i == 0 && j == m+1) then envelope.north_east[0,0]
             -- South-West
-            else if (i == n+1 && j == 0) then sw[0,0]
+            else if (i == n+1 && j == 0) then envelope.south_west[0,0]
             -- South-East
-            else if (i == n+1 && j == m+1) then se[0,0]
+            else if (i == n+1 && j == m+1) then envelope.south_east[0,0]
             -- North
-            else if (i == 0) then no[0, j-1]
+            else if (i == 0) then envelope.north[0, j-1]
             -- West
-            else if (j == 0) then we[i-1, 0]
+            else if (j == 0) then envelope.west[i-1, 0]
             -- East
-            else if (j == m + 1) then ea[i-1, 0]
+            else if (j == m + 1) then envelope.east[i-1, 0]
             -- South
-            else if (i == n+1) then so[0,j-1]
+            else if (i == n+1) then envelope.south[0,j-1]
             else chunk_board[i-1, j-1]
         )
 
-entry next_chunk_board [n][m] (chunk_board :[n][m]i8)
-                              (nw:[1][1]i8) (no:[1][m]i8) (ne:[1][1]i8)
-                                   (we:[n][1]i8) (ea:[n][1]i8)
-                              (sw:[1][1]i8) (so:[1][m]i8) (se:[1][1]i8) :[n][m]i8 =
-    let augmented_board = augment_board chunk_board nw no ne we ea sw so se
+-- (nw:[1][1]i8) (no:[1][m]i8) (ne:[1][1]i8)
+--                                    (we:[n][1]i8) (ea:[n][1]i8)
+--                               (sw:[1][1]i8) (so:[1][m]i8) (se:[1][1]i8)
+
+entry next_chunk_board [n][m] (chunk_board :[n][m]i8) (envelope: envelope_2d_i8) :[n][m]i8 =
+    let augmented_board = augment_board chunk_board envelope
     let neighbours = count_neighbours augmented_board
     let next_board = map2 (\augmented_board_r neighbours_r ->
         map2(\cell nb_alive_cells ->
diff --git a/game_of_life/main.c b/game_of_life/main.c
index f393772549a45083237820ebddba8c9ca9849b5d..ec5cef6e59cd6a3ac01ada8270df0854ba5c0054 100644
--- a/game_of_life/main.c
+++ b/game_of_life/main.c
@@ -31,19 +31,15 @@ void init_chunk_board(chunk_info_t *ci) {
 void compute_next_chunk_board(struct dispatch_context *dc, struct futhark_context *fc, chunk_info_t *ci) {
     envelope_t outer_envelope = get_outer_envelope(dc, fc, 1);
 
-    struct futhark_i8_2d *fut_outer_envelope[8] = {0};
-    futhark_outer_envelope_new(dc, fc, &outer_envelope,
-                               (void *(*)(struct futhark_context *, uint8_t *, ...)) futhark_new_i8_2d,
-                               (void **) fut_outer_envelope);
+    struct futhark_opaque_envelope_2d_i8 *fut_outer_envelope = futhark_outer_envelope_new(dc, fc, &outer_envelope,
+                                                                                          futhark_restore_opaque_envelope_2d_i8,
+                                                                                          FUT_I8);
 
     struct futhark_i8_2d *fut_chunk_board = futhark_new_i8_2d(fc, ci->data, ci->dimensions[0], ci->dimensions[1]);
     struct futhark_i8_2d *fut_next_chunk_board;
 
     futhark_context_sync(fc);
-    futhark_entry_next_chunk_board(fc, &fut_next_chunk_board, fut_chunk_board,
-                                   fut_outer_envelope[0], fut_outer_envelope[1], fut_outer_envelope[2],
-                                   fut_outer_envelope[3], fut_outer_envelope[4],
-                                   fut_outer_envelope[5], fut_outer_envelope[6], fut_outer_envelope[7]);
+    futhark_entry_next_chunk_board(fc, &fut_next_chunk_board, fut_chunk_board, fut_outer_envelope);
     futhark_context_sync(fc);
 
     futhark_values_i8_2d(fc, fut_next_chunk_board, ci->data);
@@ -51,8 +47,7 @@ void compute_next_chunk_board(struct dispatch_context *dc, struct futhark_contex
 
     futhark_free_i8_2d(fc, fut_next_chunk_board);
     futhark_free_i8_2d(fc, fut_chunk_board);
-    futhark_outer_envelope_free(dc, fc, (int (*)(struct futhark_context *, void *)) futhark_free_i8_2d,
-                                (void **) fut_outer_envelope);
+    futhark_free_opaque_envelope_2d_i8(fc, fut_outer_envelope);
     envelope_free(&outer_envelope);
 }