From aec19e55ca32f68536a550f100d3f058b8a93c02 Mon Sep 17 00:00:00 2001
From: Kimplul <kimi.h.kuparinen@gmail.com>
Date: Sat, 4 Jan 2025 01:25:31 +0200
Subject: initial move checking

+ Missing implementations for most things, but it already highlights
  an oversight in my initial plan, namely that currently, a function
  might call multiple of its closures, meaning that a closure wouldn't
  be allowed to move a value. I'm debating whether to check that only
  one closure from a parameter list is called at a time or if I should
  do what Hylo does and add in some kind of 'subscript' that's like a
  function but has slightly different rules?
---
 src/analyze.c  |   2 +-
 src/ast.c      |  44 +++++++++++++++++
 src/compiler.c |   4 ++
 src/debug.c    |   6 +++
 src/move.c     | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 204 insertions(+), 1 deletion(-)
 create mode 100644 src/move.c

(limited to 'src')

diff --git a/src/analyze.c b/src/analyze.c
index 37b2e65..077e7d8 100644
--- a/src/analyze.c
+++ b/src/analyze.c
@@ -418,7 +418,7 @@ static int analyze(struct state *state, struct scope *scope, struct ast *node)
 	case AST_CONST_STR:     ret = analyze_str       (state, scope, node);
 		break;
 	default:
-		internal_error("missing ast analysis");
+		internal_error("missing ast analysis for %s", ast_str(node->k));
 		return -1;
 	}
 
diff --git a/src/ast.c b/src/ast.c
index 119f943..81fa0b3 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -604,3 +604,47 @@ bool type_lists_match(struct type *al, struct type *bl)
 	}
 	return true;
 }
+
+const char *ast_str(enum ast_kind k)
+{
+#define CASE(x) case x: return #x;
+	switch (k) {
+	CASE(AST_CLOSURE);
+	CASE(AST_IF);
+	CASE(AST_LET);
+	CASE(AST_INIT);
+	CASE(AST_CALL);
+	CASE(AST_PROC_DEF);
+	CASE(AST_VAR_DEF);
+	CASE(AST_DOT);
+	CASE(AST_BLOCK);
+	CASE(AST_ID);
+	CASE(AST_EMPTY);
+	CASE(AST_ADD);
+	CASE(AST_SUB);
+	CASE(AST_MUL);
+	CASE(AST_DIV);
+	CASE(AST_REM);
+	CASE(AST_LAND);
+	CASE(AST_LOR);
+	CASE(AST_LSHIFT);
+	CASE(AST_RSHIFT);
+	CASE(AST_LT);
+	CASE(AST_GT);
+	CASE(AST_LE);
+	CASE(AST_GE);
+	CASE(AST_NE);
+	CASE(AST_EQ);
+	CASE(AST_NEG);
+	CASE(AST_LNOT);
+	CASE(AST_NOT);
+	CASE(AST_CONST_INT);
+	CASE(AST_CONST_CHAR);
+	CASE(AST_CONST_BOOL);
+	CASE(AST_CONST_FLOAT);
+	CASE(AST_CONST_STR);
+	}
+#undef CASE
+
+	return "UNKNOWN";
+}
diff --git a/src/compiler.c b/src/compiler.c
index d18e767..9f564d8 100644
--- a/src/compiler.c
+++ b/src/compiler.c
@@ -21,6 +21,7 @@
 #include <fwd/debug.h>
 #include <fwd/scope.h>
 #include <fwd/lower.h>
+#include <fwd/move.h>
 
 /**
  * Read whole file into a buffer and return pointer to buffer.
@@ -112,6 +113,9 @@ static int process(struct scope **parent, const char *file)
 	if (analyze_root(scope, tree))
 		return -1;
 
+	if (mvcheck_root(tree))
+		return -1;
+
 	return 0;
 }
 
diff --git a/src/debug.c b/src/debug.c
index cd0b8eb..f72fe9b 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -144,6 +144,12 @@ void type_mismatch(struct scope *scope, struct ast *node,
 	free((void *)rs);
 }
 
+void move_error(struct ast *new_use, struct ast *prev_use)
+{
+	semantic_error(new_use->scope, new_use, "using moved value");
+	semantic_info(prev_use->scope, prev_use, "previously moved here");
+}
+
 static void _type_str(FILE *f, struct type *t);
 
 static void _type_list_str(FILE *f, struct type *types)
diff --git a/src/move.c b/src/move.c
new file mode 100644
index 0000000..b09f745
--- /dev/null
+++ b/src/move.c
@@ -0,0 +1,149 @@
+#include <fwd/move.h>
+
+struct ast_pair {
+	struct ast *def;
+	struct ast *use;
+};
+
+#define SPTREE_TYPE struct ast_pair
+#define SPTREE_CMP(a, b) ((a).def - (b).def)
+#define SPTREE_NAME moved
+#include <fwd/sptree.h>
+
+struct state {
+	struct moved moved;
+	struct state *parent;
+};
+
+static struct state create_state(struct state *parent)
+{
+	struct state state = {};
+	state.parent = parent;
+	state.moved = moved_create();
+	return state;
+}
+
+static void destroy_state(struct state *state)
+{
+	moved_destroy(&state->moved);
+}
+
+static struct ast_pair *find_move(struct state *state, struct ast *def)
+{
+	if (!state)
+		return NULL;
+
+	struct ast_pair search = {.def = def};
+	struct ast_pair *found = moved_find(&state->moved, search);
+	if (found)
+		return found;
+
+	return find_move(state->parent, def);
+}
+
+static void insert_move(struct state *state, struct ast *def, struct ast *use)
+{
+	struct ast_pair pair =  {.def = def, .use = use};
+	moved_insert(&state->moved, pair);
+}
+
+static void push_up(struct state *state)
+{
+	struct state *parent = state->parent;
+	if (!parent)
+		return;
+
+	if (moved_len(&state->moved) == 0)
+		return;
+
+	foreach(moved, n, &state->moved) {
+		moved_insert(&parent->moved, *n);
+	}
+}
+
+static int mvcheck(struct state *state, struct ast *node);
+static int mvcheck_list(struct state *state, struct ast *nodes);
+
+static int mvcheck_proc(struct state *state, struct ast *node)
+{
+	/* extern, can't really do anything so just say it's fine */
+	if (!proc_body(node))
+		return 0;
+
+	struct state new_state = create_state(state);
+	/* we don't need to merge things into the parent state */
+	int ret = mvcheck(&new_state, proc_body(node));
+	destroy_state(&new_state);
+	return ret;
+}
+
+static int mvcheck_block(struct state *state, struct ast *node)
+{
+	return mvcheck_list(state, block_body(node));
+}
+
+static int mvcheck_list(struct state *state, struct ast *nodes)
+{
+	foreach_node(node, nodes) {
+		if (mvcheck(state, node))
+			return -1;
+	}
+
+	return 0;
+}
+
+static int mvcheck_call(struct state *state, struct ast *node)
+{
+	return mvcheck_list(state, call_args(node));
+}
+
+static int mvcheck_closure(struct state *state, struct ast *node)
+{
+	struct state new_state = create_state(state);
+	int ret = mvcheck(&new_state, closure_body(node));
+	push_up(&new_state);
+
+	destroy_state(&new_state);
+	return ret;
+}
+
+static int mvcheck_id(struct state *state, struct ast *node)
+{
+	struct ast *def = file_scope_find_symbol(node->scope, id_str(node));
+	assert(def);
+
+	struct ast_pair *prev = find_move(state, def);
+	if (prev) {
+		move_error(node, prev->use);
+		return -1;
+	}
+
+	insert_move(state, def, node);
+	return 0;
+}
+
+static int mvcheck(struct state *state, struct ast *node)
+{
+	switch (node->k) {
+	case AST_PROC_DEF:	return mvcheck_proc	(state, node);
+	case AST_BLOCK:		return mvcheck_block	(state, node);
+	case AST_CALL:		return mvcheck_call	(state, node);
+	case AST_CLOSURE:	return mvcheck_closure	(state, node);
+	case AST_ID:		return mvcheck_id	(state, node);
+	default: break;
+	}
+
+	internal_error("missing move check for %s", ast_str(node->k));
+	return -1;
+}
+
+int mvcheck_root(struct ast *root)
+{
+	foreach_node(node, root) {
+		struct state state = create_state(NULL);
+		if (mvcheck(&state, node))
+			return -1;
+	}
+
+	return 0;
+}
-- 
cgit v1.2.3