← Commits · fd325f6d
fd325f6ddbcf554e3db42ec80091f258296a0b81
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d6beb5d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,7 @@
+CFLAGS ?= -std=c99 -Wall -Wextra -g
+
+all:
+ $(CC) $(CFLAGS) test.c -o test
+ ./test
+
+.PHONY: all
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..ba64202
--- /dev/null
+++ b/test.c
@@ -0,0 +1,45 @@
+#include <assert.h>
+
+#include "utensil.h"
+
+static void test_shape(void) {
+ ut_shape s = ut_shape_new(3, (int[]){2, 3, 4});
+ assert(s.nelem == 24);
+ assert(s.ndim == 3);
+ // element [1][2][3] should be at offset 1*12 + 2*4 + 3 = 23
+ assert(ut_index(s, (int[]){1, 2, 3}) == 23);
+ // element [0][0][0] is always 0
+ assert(ut_index(s, (int[]){0, 0, 0}) == 0);
+}
+
+static void test_lifetime(void) {
+ ut_tensor* a = ut_alloc(2, (int[]){4, 4});
+ a->data[0] = 42.f;
+
+ ut_tensor* v = ut_view(a, 1, (int[]){16});
+
+ assert(v->data[0] == 42.f);
+ assert(v->owner == a);
+
+ v->data[5] = 99.f;
+ assert(a->data[5] == 99.f);
+
+ ut_free(v);
+ assert(a->data[0] == 42.f);
+ ut_free(a);
+}
+
+void test_reshape(void) {
+ ut_tensor* a = ut_randn(2, (int[]){2, 3}, 0.f, 1.f);
+ ut_tensor* v = ut_view(a, 1, (int[]){6});
+ for (int i = 0; i < 6; i++) assert(v->data[i] == a->data[i]);
+ ut_free(v);
+ ut_free(a);
+}
+
+int main() {
+ test_shape();
+ test_lifetime();
+ test_reshape();
+ return 0;
+}
diff --git a/utensil.h b/utensil.h
new file mode 100644
index 0000000..f893754
--- /dev/null
+++ b/utensil.h
@@ -0,0 +1,82 @@
+#ifndef UTENSIL_H
+#define UTENSIL_H
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UT_MAX_DIMS 4
+
+typedef struct ut_shape {
+ int ndim;
+ int nelem;
+ int shape[UT_MAX_DIMS];
+ int stride[UT_MAX_DIMS];
+} ut_shape;
+
+typedef struct ut_tensor {
+ ut_shape shape;
+ float* data;
+ int rc;
+ struct ut_tensor* owner;
+} ut_tensor;
+
+ut_shape ut_shape_new(int ndim, const int* dim) {
+ ut_shape s = {.ndim = ndim, .nelem = 1};
+ for (int i = 0; i < ndim; i++) s.shape[i] = dim[i], s.nelem *= dim[i];
+ return s;
+}
+
+int ut_index(ut_shape s, const int* idx) {
+ int flat = 0, stride = 1;
+ for (int i = s.ndim - 1; i >= 0; i--) flat += idx[i] * stride, stride *= s.shape[i];
+ return flat;
+}
+
+ut_tensor* ut_alloc(int ndim, int* dim) {
+ ut_tensor* t = (ut_tensor*)malloc(sizeof(ut_tensor));
+ *t = (struct ut_tensor){.shape = ut_shape_new(ndim, dim), .owner = NULL, .rc = 1};
+ t->data = (float*)malloc(t->shape.nelem * sizeof(float));
+ return t;
+}
+
+ut_tensor* ut_randn(int ndim, int* dim, float mean, float stddev) {
+ ut_tensor* t = ut_alloc(ndim, dim);
+ for (int i = 0; i < t->shape.nelem; i++) {
+ // Box-Muller transform to generate normally distributed random numbers
+ float u1 = (float)rand() / RAND_MAX;
+ float u2 = (float)rand() / RAND_MAX;
+ float z0 = sqrtf(-2.0f * logf(u1)) * cosf(6.28f * u2);
+ t->data[i] = z0 * stddev + mean;
+ }
+ return t;
+}
+
+ut_tensor* ut_from_data(int ndim, const int* dim, const float* data) {
+ ut_tensor* t = ut_alloc(ndim, dim);
+ memcpy(t->data, data, (size_t)t->shape.nelem * sizeof(float));
+ return t;
+}
+
+ut_tensor* ut_clone(ut_tensor* t) {
+ ut_tensor* c = ut_alloc(t->shape.ndim, t->shape.shape);
+ memcpy(c->data, t->data, (size_t)t->shape.nelem * sizeof(float));
+ return c;
+}
+
+void ut_free(ut_tensor* t) {
+ if (!--t->rc) return;
+ if (!t->owner) free(t->data);
+ free(t);
+}
+
+ut_tensor* ut_view(ut_tensor* t, int ndim, const int* dim) {
+ ut_shape ns = ut_shape_new(ndim, dim);
+ ut_tensor* v = (ut_tensor*)malloc(sizeof(ut_tensor));
+ *v = (struct ut_tensor){.shape = ns, .data = t->data, .owner = t->owner ? t->owner : t};
+ return v;
+}
+
+ut_tensor* ut_retain(ut_tensor* t) { return t->rc++, t; }
+
+#endif // UTENSIL_H