ox

The Ox programming language, compiler and tools (WIP)
Log | Files | Refs | README | LICENSE

gen.c (43481B)



#include "../gen.h"
#include "../parser.h"
#include "../utils.h"
#include "../typer.h"

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/param.h>

static gcc_jit_type* type_boolean;
static gcc_jit_type* type_int;
static gcc_jit_type* type_i8;
static gcc_jit_type* type_i16;
static gcc_jit_type* type_i32;
static gcc_jit_type* type_i64;
static gcc_jit_type* type_i128;
static gcc_jit_type* type_u8;
static gcc_jit_type* type_u16;
static gcc_jit_type* type_u32;
static gcc_jit_type* type_u64;
static gcc_jit_type* type_u128;
static gcc_jit_type* type_f32;
static gcc_jit_type* type_f64;
static gcc_jit_type* type_f128;
static gcc_jit_type* type_uint;
static gcc_jit_type* type_void;
static gcc_jit_type* type_cstr;
static gcc_jit_type* type_char;
static gcc_jit_type* type_voidp;

static const char* type_func = "function";
static const char* type_var = "variable";

#define MAXARGS 16

static gcc_jit_location*
loc_from_node(Gen* gen, Node* node)
{
	if (node->filename == NULL) return NULL;
	return gcc_jit_context_new_location(gen->ctx, node->filename, node->line, node->col);
}

static void
push_loop(Gen* gen, gcc_jit_block* brk, gcc_jit_block* cont)
{
	LoopContext* lctx = (LoopContext*)malloc(sizeof(LoopContext));
	if (lctx == NULL) { panic("push_loop: could not alloc"); }
	lctx->break_target = brk;
	lctx->continue_target = cont;
	lctx->prev = gen->loop;
	gen->loop = lctx;
}

static void
pop_loop(Gen* gen)
{
	LoopContext* lctx = gen->loop;
	gen->loop = lctx->prev;
	free(lctx);
}

static inline gcc_jit_block*
current_break(Gen* gen)
{
	return gen->loop ? gen->loop->break_target : NULL;
}

static inline gcc_jit_block*
current_continue(Gen* gen)
{
	return gen->loop ? gen->loop->continue_target : NULL;
}

__attribute__((unused)) static const char*
get_english_type(gcc_jit_type* T)
{
	return gcc_jit_object_get_debug_string(gcc_jit_type_as_object(T));
}

Gen
gen_init(Scope* scope, const char* src, Node* node, bool quiet)
{
	if (scope == NULL || src == NULL) { panic("gen_init: no Scope or AST provided"); }

	gcc_jit_context* ctx;

	ctx = gcc_jit_context_acquire();

	if (!ctx) { panic("could not acquire gcc jit context"); }

	// needs loc* to work
	// gcc_jit_context_set_bool_option(ctx, GCC_JIT_BOOL_OPTION_DEBUGINFO,
	// 1); high level

	gcc_jit_context_set_bool_option(ctx, GCC_JIT_BOOL_OPTION_DEBUGINFO, 1);

	if (quiet == false) { gcc_jit_context_set_bool_option(ctx, GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1); }

	// gcc_jit_context_set_bool_option(ctx,
	// GCC_JIT_BOOL_OPTION_DUMP_SUMMARY, 1);

	gcc_jit_context_set_str_option(ctx, GCC_JIT_STR_OPTION_PROGNAME, "ox");
	// keep FP
	gcc_jit_context_add_driver_option(ctx, "-fno-omit-frame-pointer");

	gcc_jit_context_set_int_option(ctx,
		GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL,
		/*0-3 for O3*/ 0);

	type_boolean = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_BOOL);

	type_int = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT64_T);
	type_i8 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT8_T);
	type_i16 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT16_T);
	type_i32 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT32_T);
	type_i64 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT64_T);
	type_i128 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT128_T);

	type_u8 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UINT8_T);
	type_u16 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UINT16_T);
	type_u32 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UINT32_T);
	type_u64 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UINT64_T);
	type_u128 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UINT128_T);

	type_f32 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_FLOAT);
	type_f64 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_DOUBLE);
	type_f128 = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_LONG_DOUBLE);

	type_uint = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UINT64_T);
	type_char = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_CHAR);
	type_void = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_VOID);
	type_cstr = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_CONST_CHAR_PTR);
	type_voidp = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_VOID_PTR);
	/* gcc_jit_type_dyncast_function_ptr_type; */

	gcc_jit_location* loc = gcc_jit_context_new_location(ctx, node->filename, 0, 0);

	gcc_jit_param* pm_puts[] = { gcc_jit_context_new_param(ctx, loc, type_cstr, "s") };
	gcc_jit_function* fn_puts = gcc_jit_context_new_function(ctx, loc, GCC_JIT_FUNCTION_IMPORTED, type_int, "puts", 1, pm_puts, 0);

	gcc_jit_param* pm_printf[] = { gcc_jit_context_new_param(ctx, loc, type_cstr, "fmt") };
	gcc_jit_function* fn_printf = gcc_jit_context_new_function(ctx,
		loc,
		GCC_JIT_FUNCTION_IMPORTED,
		type_int,
		"printf",
		1,
		pm_printf,
		/*is_variadic=*/1);

	return (Gen) {
		.ctx = ctx,
		.scope = scope,
		.prev_func = NULL,
		.curr_func = NULL,
		.prev_block = NULL,
		.curr_block = NULL,
		.puts_fn = fn_puts,
		.printf_fn = fn_printf,
		.src = src,
	};
}

static void
push_scope(Gen* gen)
{
	printf("push_scope\n");
	Scope* scope = calloc(1, sizeof(Scope));
	*scope = (Scope) { 0 };
	scope->symbols = (Symbol**)calloc(16, sizeof(Symbol*));
	if (scope->symbols == NULL) panic("push_scope: symbols: could not alloc");
	scope->cap = 16;
	scope->len = 0;

	scope->parent = gen->scope;
	gen->scope = scope;
}

static void
pop_scope(Gen* gen)
{
	printf("pop_scope\n");
	Scope* s = gen->scope;
	gen->scope = s->parent;
}

static Symbol*
find_symbol(Gen* gen, Scope* scope, Span name)
{
	const char* ident_name = span_str(gen->src, name, (char[IDENTSZ]) { 0 });
	// Look up the symbol in the current scope // TODO look up in parent
	// scope with utility function and recursion
	for (size_t i = 0; i < scope->len; i++) {
		Symbol* sym = scope->symbols[i];
		const char* sym_name = span_str(gen->src, sym->name, (char[IDENTSZ]) { 0 });
		// printf("symbol %s\n", sym_name);
		// printf("ident %s\n", ident_name);
		if (strcmp(sym_name, ident_name) == 0) {
			printf("found symbol %s\n", sym_name);
			return sym;
		}
	}
	printf("find_symbol: not found locally: %s\n", ident_name);

	if (scope->parent != NULL) {
		printf("looking up symbol %s in parent\n", ident_name);
		return find_symbol(gen, scope->parent, name);
	}

	return NULL;
}

static void
add_symbol(Gen* gen, Symbol* sym)
{
	if (gen->scope->len == gen->scope->cap) {
		gen->scope->cap *= 2;
		gen->scope->symbols = (Symbol**)realloc(gen->scope->symbols, sizeof(Symbol*) * gen->scope->cap);
	}

	gen->scope->symbols[gen->scope->len++] = sym;

	printf("add_symbol: we now have %zu symbols: ", gen->scope->len);
	for (size_t i = 0; i < gen->scope->len; i++) {
		Symbol* symy = gen->scope->symbols[i];
		const char* name = span_str(gen->src, symy->name, (char[IDENTSZ]) { 0 });
		printf("%s, ", name);
	}
	printf("\n");
}

static gcc_jit_rvalue*
handle_ident_call(Gen* gen, Node* node)
{
	// Look up the symbol in the current scope // TODO look up in parent
	// scope with utility function and recursion
	Symbol* sym = find_symbol(gen, gen->scope, node->data.ident.name);

	if (sym == NULL) {
		panic_at(node,
			"handle_ident_call: undefined variable: %s\n",
			span_str(gen->src, node->data.ident.name, (char[IDENTSZ]) { 0 }));
	}
	return gcc_jit_lvalue_as_rvalue(sym->d.lvalue);
}

static gcc_jit_rvalue* handle_expr(Gen*, Node*);

static inline int
is_intlike(gcc_jit_context* ctx, gcc_jit_type* t) // TODO support all new types
{
	return t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT) || t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_INT)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_SHORT)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_SHORT)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_LONG)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_LONG)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_LONG_LONG)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_LONG_LONG)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT64_T) || t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_INT32_T)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_CHAR)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_CHAR);
}

static inline int
is_floatlike(gcc_jit_context* ctx, gcc_jit_type* t)
{
	return t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_FLOAT) || t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_DOUBLE)
		|| t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_LONG_DOUBLE);
}

static inline int
is_cstr(gcc_jit_context* ctx, gcc_jit_type* t)
{
	return t == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_CONST_CHAR_PTR);
}

// > < >= <= + - / * %
static gcc_jit_rvalue*
handle_bin_expr(Gen* gen, Node* node, int gcc_jit_op, bool cmp)
{
	Node* lhs = node->data.binary_expr.lhs;
	Node* rhs = node->data.binary_expr.rhs;
	gcc_jit_context* ctx = gen->ctx;
	gcc_jit_location* loc = loc_from_node(gen, node);

	gcc_jit_rvalue* L = handle_expr(gen, lhs);
	gcc_jit_rvalue* R = handle_expr(gen, rhs);
	gcc_jit_type* Lt = gcc_jit_rvalue_get_type(L);
	gcc_jit_type* Rt = gcc_jit_rvalue_get_type(R);
	gcc_jit_rvalue* result = NULL;

	// floats: cast both to double
	if (is_floatlike(ctx, Lt) || is_floatlike(ctx, Rt)) {
		gcc_jit_type* T = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_DOUBLE);
		L = gcc_jit_context_new_cast(ctx, loc, L, T);
		R = gcc_jit_context_new_cast(ctx, loc, R, T);
		result = cmp ? gcc_jit_context_new_comparison(ctx, loc, gcc_jit_op, L, R)
			     : gcc_jit_context_new_binary_op(ctx, loc, gcc_jit_op, T, L, R);
		;
	}

	// integers: cast both to signed/unsigned long long (cheap, predictable)
	if (is_intlike(ctx, Lt) && is_intlike(ctx, Rt)) {
		int any_unsigned = (Lt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_CHAR))
			|| (Lt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_SHORT))
			|| (Lt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_INT))
			|| (Lt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_LONG))
			|| (Lt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_LONG_LONG))
			|| (Rt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_CHAR))
			|| (Rt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_SHORT))
			|| (Rt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_INT))
			|| (Rt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_LONG))
			|| (Rt == gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_LONG_LONG));

		gcc_jit_type* T = gcc_jit_context_get_type(ctx, any_unsigned ? GCC_JIT_TYPE_UINT64_T : GCC_JIT_TYPE_INT64_T);

		L = gcc_jit_context_new_cast(ctx, loc, L, T);
		R = gcc_jit_context_new_cast(ctx, loc, R, T);
		result = cmp ? gcc_jit_context_new_comparison(ctx, loc, gcc_jit_op, L, R)
			     : gcc_jit_context_new_binary_op(ctx, loc, gcc_jit_op, T, L, R);
	}

	// pointers: either reject or compare addresses (UB in C for unrelated objs).
	if (
#ifdef GCC_JIT_HAVE_gcc_jit_type_is_pointer
		gcc_jit_type_is_pointer(Lt) && gcc_jit_type_is_pointer(Rt)
#else
		0
#endif
	) {
		gcc_jit_type* T = gcc_jit_context_get_type(ctx, GCC_JIT_TYPE_UNSIGNED_LONG_LONG);
		L = gcc_jit_context_new_cast(ctx, loc, L, T);
		R = gcc_jit_context_new_cast(ctx, loc, R, T);
		result = cmp ? gcc_jit_context_new_comparison(ctx, loc, gcc_jit_op, L, R)
			     : gcc_jit_context_new_binary_op(ctx, loc, gcc_jit_op, T, L, R);
	}

	return result;
}

static gcc_jit_rvalue*
handle_binary_expr(Gen* gen, Node* node)
{
	int gcc_jit_op = -1;
	OpType op = node->data.binary_expr.op;

	switch (op) {
	case OP_PLUS:
		gcc_jit_op = GCC_JIT_BINARY_OP_PLUS;
		return handle_bin_expr(gen, node, gcc_jit_op, false);
		break;
	case OP_MINUS:
		gcc_jit_op = GCC_JIT_BINARY_OP_MINUS;
		return handle_bin_expr(gen, node, gcc_jit_op, false);
		break;
	case OP_MUL:
		gcc_jit_op = GCC_JIT_BINARY_OP_MULT;
		return handle_bin_expr(gen, node, gcc_jit_op, false);
		break;
	case OP_DIV:
		gcc_jit_op = GCC_JIT_BINARY_OP_DIVIDE;
		return handle_bin_expr(gen, node, gcc_jit_op, false);
		break;
	case OP_MOD:
		gcc_jit_op = GCC_JIT_BINARY_OP_MODULO;
		return handle_bin_expr(gen, node, gcc_jit_op, false);
		break;
	case OP_LT:
		gcc_jit_op = GCC_JIT_COMPARISON_LT;
		break;
	case OP_GT:
		gcc_jit_op = GCC_JIT_COMPARISON_GT;
		break;
	case OP_GT_EQ:
		gcc_jit_op = GCC_JIT_COMPARISON_GE;
		break;
	case OP_LT_EQ:
		gcc_jit_op = GCC_JIT_COMPARISON_LE;
		break;
	default:
		printf("handle_binary_expr unhandled OpType %d (can be ignored in var assignment "
		       "(for now))\n",
			op);
		return NULL;
		break;
	}

	if (gcc_jit_op < 0) {
		// unsupported types (e.g., strings or structs)
		printf("handle_binary_expr OP_LT: unsupported operand types\n");
		return NULL;
	}

	return handle_bin_expr(gen, node, gcc_jit_op, true);
}

static gcc_jit_rvalue*
handle_unary_expr(Gen* gen, Node* node)
{
	gcc_jit_context* ctx = gen->ctx;
	gcc_jit_block* bb = gen->curr_block;

	Node* opnd = node->data.unary_expr.operand;
	Symbol* sym = find_symbol(gen, gen->scope, opnd->data.ident.name);
	if (sym == NULL) {
		panic_at(node,
			"handle_unary_expr: undefined variable: %s\n",
			span_str(gen->src, opnd->data.ident.name, (char[IDENTSZ]) { 0 }));
	}

	gcc_jit_lvalue* lv = sym->d.lvalue;
	if (lv == NULL) {
		printf("handle_unary_expr: no lvalue\n");
		return NULL;
	}
	gcc_jit_rvalue* orig = gcc_jit_lvalue_as_rvalue(lv);
	gcc_jit_type* ty = gcc_jit_rvalue_get_type(orig);
	gcc_jit_rvalue* one = gcc_jit_context_one(ctx, ty);

	gcc_jit_rvalue* inc = gcc_jit_context_new_binary_op(ctx, NULL, GCC_JIT_BINARY_OP_PLUS, ty, orig, one);
	switch (node->data.unary_expr.op) {
	case OPER_POSTINC: { // i++

		if (!sym->is_variadic) {
			panic_at(node,
				"OPER_POSTINC: cannot change non variadic variable %s\n",
				span_str(gen->src, opnd->data.ident_type.name, (char[IDENTSZ]) { 0 }));
		} else if (sym->is_comp_time) {
			panic_at(node,
				"OPER_POSTINC: cannot change comp time constant %s\n",
				span_str(gen->src, opnd->data.ident_type.name, (char[IDENTSZ]) { 0 }));
		}
		gcc_jit_block_add_assignment(bb, NULL, lv, inc);
		return orig; // return value before incr as ++ is postfix

		break;
	}
	default:
		printf("handle_unary_expr, unhandled op %d\n", node->data.unary_expr.op);
		return NULL;
		break;
	}
	return NULL;
}

static gcc_jit_rvalue*
emit_literal_bool(Gen* gen, Node* node)
{
	int v = node->data.boolean.value ? 1 : 0;
	return gcc_jit_context_new_rvalue_from_int(gen->ctx, type_boolean, v);
}

static gcc_jit_rvalue*
emit_literal_string(Gen* gen, Node* node)
{
	size_t len = node->data.string.value.end - node->data.string.value.start;
	char* str = calloc(len + 1, sizeof(char));
	if (str == NULL) panic_at(node, "emit_literal_string: could not alloc");
	memcpy(str, gen->src + node->data.string.value.start, len);
	str[len] = '\0';
	return gcc_jit_context_new_string_literal(gen->ctx, str);
}

static gcc_jit_rvalue*
emit_literal_int(Gen* gen, Node* node)
{
	return gcc_jit_context_new_rvalue_from_int(gen->ctx, type_int, (int)node->data.number.value);
}

static gcc_jit_rvalue*
emit_literal_float(Gen* gen, Node* node)
{
	return gcc_jit_context_new_rvalue_from_double(gen->ctx, type_f32, node->data.number.value);
}

static void
build_program(Gen* gen, Node* node)
{
	size_t cnt = node->data.program.len;
	for (size_t i = 0; i < cnt; i++) {
		gen_next(gen, node->data.program.decl[i]);
	}
}

static gcc_jit_rvalue*
lower_builtin_print(Gen* gen, Node* node)
{
	printf("lower_builtin_print\n");

	size_t argc = node->data.call_expr.len;

	// for (size_t i = 0; i < argc; i++) {
	// Node* arg = node->data.call_expr.args[i];
	// }

	// 1-arg, treat as puts(arg)
	if (argc == 1) {
		gcc_jit_rvalue* arg = handle_expr(gen, node->data.call_expr.args[0]);
		gcc_jit_type* t = gcc_jit_rvalue_get_type(arg);
		gcc_jit_location* loc = loc_from_node(gen, node->data.call_expr.args[0]);
		// print a string
		if (is_cstr(gen->ctx, t)) {
			gcc_jit_rvalue* args[] = { arg };
			return gcc_jit_context_new_call(gen->ctx, loc, gen->puts_fn, 1, args);
		}
		// print an integer or bool
		else if (is_intlike(gen->ctx, t) || t == type_boolean) {
			// cast to int for a clean %d
			// gcc_jit_type* t_int
			// = gcc_jit_context_get_type(gen->ctx, GCC_JIT_TYPE_INT64_T);
			gcc_jit_rvalue* fmt = gcc_jit_context_new_string_literal(gen->ctx, "%d\n");
			gcc_jit_rvalue* ival = arg;
			gcc_jit_rvalue* args[] = { fmt, ival };
			return gcc_jit_context_new_call(gen->ctx, loc, gen->printf_fn, 2, args);
		}
		// cast common cases to const char*
		// if (gcc_jit_rvalue_get_type(arg) != type_cstr) arg =
		// gcc_jit_context_new_cast(gen->ctx, loc_from_node(gen, node),
		// arg, type_cstr);

		// gcc_jit_rvalue* args[] = { arg };
		// return gcc_jit_context_new_call(
		// 	gen->ctx, loc_from_node(gen, node), gen->puts_fn, 1, args);

		// gcc_jit_type* type_of_args = gcc_jit_rvalue_get_type(arg);
		// switch (type_of_args) {
		// case GCC_JIT_TYPE_CONST_CHAR_PTR:

		// 	break;
		// default:
		// 	printf("lower_builtin_print: unhandled type passed %s\n",
		// type_of_args); 	break;
		// }
	}

	return NULL;

	// softpanic("we don't currently handle formatted strings to print");

	// n>=1, treat as printf(fmt, ...) // Part of TODO about args as list
	// and not
	//
	// through each args, form the ("formatted %s string %d etc.", str,
	// intv) for clib's printf

	// TODO we're talking about formatting here, which we plan on doing as a
	// string interpolation, something along the lines of {{variable}}
	// without defining its type would involve lookup split of the string
	// and then formatting

	// we need to discuss and decide what we'd do when the user inevitably
	// would print out a ref to a struct. Do we say [[struct]] or do we have
	// some automatic unwrap and display of struct data... probably, yes.

	gcc_jit_rvalue** args = (gcc_jit_rvalue**)calloc(MAXARGS, sizeof(gcc_jit_rvalue*));

	if (argc > MAXARGS) {
		panic_at(node,
			"we do not currently support more than 16 args to a "
			"print call");
	}

	for (size_t i = 0; i < argc; i++) {
		gcc_jit_rvalue* arg = handle_expr(gen, node->data.call_expr.args[i]);
		if (i == 0) {
			if (gcc_jit_rvalue_get_type(arg) != type_cstr) {
				// note this is probably not going to work as
				// limited cast supported and string isn't one
				// of them
				arg = gcc_jit_context_new_cast(gen->ctx, loc_from_node(gen, node), arg, type_cstr);
			}
		} else {
			//
			// simple widening for common scalar types
			//
			gcc_jit_type* ty = gcc_jit_rvalue_get_type(arg);
			if (ty == type_int) {
				arg = gcc_jit_context_new_cast(gen->ctx, loc_from_node(gen, node), arg, type_cstr);
			} else if (ty == type_f32) { // TODO print() for other types
				                     // variadics already promote float→double;
				                     // double is
			} else if (ty == type_cstr) {
				// leave as const char*
			} else if (ty == type_boolean) {
				arg = gcc_jit_context_new_cast(gen->ctx, loc_from_node(gen, node), arg, type_cstr);
			} else {
				// fallback: pass pointer as void*
				arg = gcc_jit_context_new_cast(gen->ctx,
					loc_from_node(gen, node),
					arg,
					gcc_jit_context_get_type(gen->ctx, GCC_JIT_TYPE_VOID_PTR));
			}
		}
		// TODO auto grow
		args[i] = arg;
	}
	return gcc_jit_context_new_call(gen->ctx, NULL, gen->printf_fn, argc, args);
	return NULL;
}

static gcc_jit_function*
lookup_function(Gen* gen, Scope* scope, const char* func_name)
{
	for (size_t i = 0; i < scope->len; i++) {
		Symbol* sym = scope->symbols[i];

		if (sym->ctype == type_voidp) {
			const char* name = span_str(gen->src, sym->name, (char[IDENTSZ]) { 0 });
			if (strcmp(name, func_name) == 0) {
				return sym->d.funcvalue; //
			}
		}
	}

	printf("lookup_function: not found locally: %s\n", func_name);

	if (scope->parent != NULL) {
		printf("looking up function %s in parent\n", func_name);
		return lookup_function(gen, scope->parent, func_name);
	}

	return NULL;
}

static gcc_jit_rvalue*
handle_func_call(Gen* gen, Node* node)
{
	Node* fcallee = node->data.call_expr.callee;
	const char* func_name = span_str(gen->src, fcallee->data.ident.name, (char[IDENTSZ]) { 0 });

	// short circuit to print
	if (strcmp(func_name, "print") == 0) return lower_builtin_print(gen, node);

	gcc_jit_function* callee = lookup_function(gen, gen->scope, func_name);
	gcc_jit_location* loc = loc_from_node(gen, node);

	// args handling
	size_t argc = node->data.call_expr.len;

	// alloc args
	gcc_jit_rvalue** args = NULL;

	if (argc > 0) {
		args = calloc(argc, sizeof *args); // or alloca, or a fixed upper bound
		for (size_t i = 0; i < argc; ++i) {
			args[i] = handle_expr(gen, node->data.call_expr.args[i]);
		}
	}

	for (size_t i = 0; i < argc; i++) {
		args[i] = handle_expr(gen, node->data.call_expr.args[i]);
	}

	/*
	        When generating the identifier expression message in the AST:
	        Lookup env["message"] -> you get a gcc_jit_param* or gcc_jit_lvalue*.
	        Use gcc_jit_param_as_rvalue / gcc_jit_lvalue_as_rvalue.

	        gcc_jit_rvalue *msg = gcc_jit_param_as_rvalue(callee_param);

	        gcc_jit_rvalue *args[1] = { msg };
	        gcc_jit_block_add_eval(
	            b, loc,
	            gcc_jit_context_new_call(ctx, loc, print_fn, 1, args));
	*/

	// gcc_jit_rvalue *msg = gcc_jit_param_as_rvalue(callee_param);
	// gcc_jit_rvalue *args[1] = { msg };
	assert(callee != NULL && "callee not found");
	return gcc_jit_context_new_call(gen->ctx, loc, callee, argc, args);

	// TODO consider
	/*
	// calling bob() directly
	   void emit_direct_call(gcc_jit_context *ctxt, SymbolId bob, gcc_jit_function *caller) {
	                 GccObj *cal = ensure_func(ctxt, bob);
	                 gcc_jit_block *b = gcc_jit_function_new_block(caller, "call_bob");
	                 gcc_jit_rvalue *args[] = {};
	                 gcc_jit_block_add_eval(b, NULL, gcc_jit_context_new_call(ctxt, cal->func, 0, args));
	                 gcc_jit_block_end_with_void_return(b, NULL);
	   }
	*/

	// return gcc_jit_context_new_call(gen->ctx, NULL, callee, 0, NULL);

	// TODO handle return values
	// almost unrelated could be useful to deal with return values:
	// gcc_jit_rvalue* rv = handle_func_call(gen, node);
	// if (rv) { gcc_jit_block_add_eval(gen->curr_block, loc_from_node(gen, node), rv); }
	// return false;
	return NULL;
}

static gcc_jit_rvalue*
handle_expr(Gen* gen, Node* node)
{
	switch (node->type) {
	case NODE_INT_LITERAL:
		return emit_literal_int(gen, node);
		break;
	case NODE_FLOAT_LITERAL:
		return emit_literal_float(gen, node);
		break;
	case NODE_STRING_LITERAL:
		return emit_literal_string(gen, node);
		break;
	case NODE_BOOL_LITERAL:
		return emit_literal_bool(gen, node);
	case NODE_CALL_EXPR:
		return handle_func_call(gen, node);
		break;
	case NODE_IDENT:
		return handle_ident_call(gen, node);
		break;
	case NODE_UNARY_EXPR: // ++ etc.
		return handle_unary_expr(gen, node);
		break;
	case NODE_BINARY_EXPR:
		return handle_binary_expr(gen, node);
		break;

	// case NODE_IDENT: {
	// 	return NULL; // fixme
	//      break;
	// }
	default:
		printf("handle_expr unhandled, %s\n", node_type_str(node->type));
	}
	return NULL;
}

static gcc_jit_type*
ox_type_to_c_type(Gen* gen, Node* node)
{
	if (node == NULL) return type_void;

	const char* type_name = span_str(gen->src, node->data.ident.name, (char[IDENTSZ]) { 0 });

	if (strcmp(type_name, "int") == 0) {
		return type_int;

	} else if (strcmp(type_name, "i8") == 0) {
		return type_i8;
	} else if (strcmp(type_name, "i16") == 0) {
		return type_i16;
	} else if (strcmp(type_name, "i32") == 0) {
		return type_i32;
	} else if (strcmp(type_name, "i64") == 0) {
		return type_i64;
	} else if (strcmp(type_name, "i128") == 0) {
		return type_i128;

	} else if (strcmp(type_name, "u8") == 0) {
		return type_u8;
	} else if (strcmp(type_name, "u16") == 0) {
		return type_u16;
	} else if (strcmp(type_name, "u32") == 0) {
		return type_u32;
	} else if (strcmp(type_name, "u64") == 0) {
		return type_u64;
	} else if (strcmp(type_name, "u128") == 0) {
		return type_u128;

	} else if (strcmp(type_name, "f32") == 0) {
		return type_f32;
	} else if (strcmp(type_name, "f64") == 0) {
		return type_f64;
	} else if (strcmp(type_name, "f128") == 0) {
		return type_f128;

	} else if (strcmp(type_name, "str") == 0) {
		return type_cstr;
	} else if (strcmp(type_name, "float") == 0) {
		return type_f32;
	} else if (strcmp(type_name, "double") == 0) {
		return type_f64;
	} else if (strcmp(type_name, "longdouble") == 0) {
		return type_f128;
	} else if (strcmp(type_name, "uint") == 0) {
		return type_uint;
	} else if (strcmp(type_name, "void") == 0) {
		return type_void;
	} else {
		panic_at(node, "unhandled type in gen %s", type_name);
	}
	return NULL;
}

static void
print_symbols_here(Gen* gen)
{
	for (size_t i = 0; i < gen->scope->len; i++) {
		Symbol* sym = gen->scope->symbols[i];
		const char* name = span_str(gen->src, sym->name, (char[IDENTSZ]) { 0 });
		printf("[%zu/%zu] symbol: %s (%s)\n", i + 1, gen->scope->len, name, sym->english_type);
	}
}

static bool build_block(Gen*, Node*);
static bool build_statement(Gen*, Node*);

static gcc_jit_rvalue* build_bool_value(Gen*, Node*);

static int block_counter = 0, loop_counter = 0, while_counter = 0;

static bool
build_while_statement(Gen* gen, Node* node)
{
	gcc_jit_location* loc = loc_from_node(gen, node);

	Node* cond = node->data.while_statement.cond;
	Node* body = node->data.while_statement.body;

	while_counter++;
	char label_cond[64], label_body[64], label_end[64];
	snprintf(label_cond, sizeof label_cond, "while.cond%d", while_counter);
	snprintf(label_body, sizeof label_body, "while.body%d", while_counter);
	snprintf(label_end, sizeof label_end, "while.end%d", while_counter);

	gcc_jit_block* cond_block = gcc_jit_function_new_block(gen->curr_func, label_cond);
	gcc_jit_block* body_block = gcc_jit_function_new_block(gen->curr_func, label_body);
	gcc_jit_block* end_block = gcc_jit_function_new_block(gen->curr_func, label_end);

	/* jump from current block into the loop */
	gcc_jit_block_end_with_jump(gen->curr_block, loc, cond_block);

	gen->curr_block = cond_block;
	gcc_jit_rvalue* cnd = NULL;

	if (cond != NULL) {
		cnd = build_bool_value(gen, cond);
		gcc_jit_block_end_with_conditional(cond_block, loc, cnd, body_block, end_block);
	} else {
		/* while (1) */
		gcc_jit_block_end_with_jump(cond_block, loc, body_block);
	}

	gcc_jit_block* continue_target = cond != NULL ? cond_block : body_block;
	push_loop(gen, /*break to*/ end_block, /*continue to*/ continue_target);

	gen->curr_block = body_block;
	bool body_ended = build_block(gen, body);
	if (!body_ended) { gcc_jit_block_end_with_jump(gen->curr_block, loc, continue_target); }

	pop_loop(gen);

	/* resume after loop */
	gen->curr_block = end_block;
	return false;
}

static bool
build_for_statement(Gen* gen, Node* node)
{
	gcc_jit_location* loc = loc_from_node(gen, node);

	Node* init = node->data.for_statement.init;
	Node* cond = node->data.for_statement.cond;
	Node* step = node->data.for_statement.increment;
	Node* body = node->data.for_statement.body;

	loop_counter++;

	char label_cond[64], label_body[64], label_step[64], label_end[64];
	snprintf(label_cond, 64, "for.cond%d", loop_counter);
	snprintf(label_body, 64, "for.body%d", loop_counter);
	snprintf(label_step, 64, "for.step%d", loop_counter);
	snprintf(label_end, 64, "for.end%d", loop_counter);

	gcc_jit_block* cond_block = gcc_jit_function_new_block(gen->curr_func, label_cond);
	gcc_jit_block* body_block = gcc_jit_function_new_block(gen->curr_func, label_body);
	gcc_jit_block* step_block = gcc_jit_function_new_block(gen->curr_func, label_step);
	gcc_jit_block* end_block = gcc_jit_function_new_block(gen->curr_func, label_end);

	// gcc_jit_block* saved_break = gen->loop->break_target;
	// gcc_jit_block* saved_cont = gen->loop->continue_target;
	// gen->loop->break_target = end_block;
	// gen->loop->continue_target = step_block;

	// header, e.g. for(int = 0 <-
	if (init) { build_statement(gen, init); }

	// jump to cond e.g. ; i < 5 <-
	gcc_jit_block_end_with_jump(gen->curr_block, loc, cond_block);

	// cond: evaluate
	gen->curr_block = cond_block;
	gcc_jit_rvalue* cnd = NULL;

	if (cond != NULL) {
		cnd = build_bool_value(gen, cond);
		gcc_jit_block_end_with_conditional(cond_block, loc, cnd, body_block, end_block);
	} else {
		gcc_jit_block_end_with_jump(cond_block, loc, body_block); // presume for(;;)
	}

	push_loop(gen, /*break to*/ end_block, /*continue to*/ step_block);

	// build for body

	gen->curr_block = body_block;
	bool for_body_ended = build_block(gen, body);
	if (!for_body_ended) { gcc_jit_block_end_with_jump(gen->curr_block, loc, step_block); }

	pop_loop(gen);

	// step incr etc.

	gen->curr_block = step_block;
	if (step) { build_statement(gen, step); }
	gcc_jit_block_end_with_jump(step_block, loc, cond != NULL ? cond_block : body_block);

	// resume after loop

	gen->curr_block = end_block;
	return false;
}

static bool
build_if_statement(Gen* gen, Node* node)
{
	gcc_jit_location* loc = loc_from_node(gen, node);
	// build the condition

	OpType op = node->data.if_statement.cond->data.binary_expr.op;
	Node* lhs = node->data.if_statement.cond->data.binary_expr.lhs;
	Node* rhs = node->data.if_statement.cond->data.binary_expr.rhs;

	Node* then_body = node->data.if_statement.then_body;
	Node* else_body = node->data.if_statement.else_body;

	enum gcc_jit_comparison cmp;
	switch (op) {
	case OP_EQUALITY:
		cmp = GCC_JIT_COMPARISON_EQ;
		break;
	case OP_INEQUALITY:
		cmp = GCC_JIT_COMPARISON_NE;
		break;
	case OP_GT:
		cmp = GCC_JIT_COMPARISON_GT;
		break;
	case OP_GT_EQ:
		cmp = GCC_JIT_COMPARISON_GE;
		break;
	case OP_LT_EQ:
		cmp = GCC_JIT_COMPARISON_LE;
		break;
	case OP_LT:
		cmp = GCC_JIT_COMPARISON_LT;
		break;
	default:
		printf("/!\\ build_statement NODE_IF unhandled, %d\n", op);
		cmp = GCC_JIT_COMPARISON_NE;
	}

	gcc_jit_rvalue* lhs_val = handle_expr(gen, lhs);
	gcc_jit_rvalue* rhs_val = handle_expr(gen, rhs);

	gcc_jit_rvalue* cond = gcc_jit_context_new_comparison(gen->ctx, loc, cmp, lhs_val, rhs_val);

	// create the BLOCKS

	// labels
	block_counter++;
	char label_then[64], label_else[64], label_end[64];
	snprintf(label_then, 64, "if.then%d", block_counter);
	snprintf(label_else, 64, "if.else%d", block_counter);
	snprintf(label_end, 64, "if.end%d", block_counter);

	// blocks
	gcc_jit_block* then_bb = gcc_jit_function_new_block(gen->curr_func, label_then);
	gcc_jit_block* else_bb = else_body ? gcc_jit_function_new_block(gen->curr_func, label_else) : NULL;
	gcc_jit_block* merge_bb = NULL;

	if (!else_bb) {
		// no else: need merge now for the false edge
		merge_bb = gcc_jit_function_new_block(gen->curr_func, label_end);
		gcc_jit_block_end_with_conditional(gen->curr_block, loc, cond, then_bb, merge_bb);
	} else {
		// with else: branch to then/else; decide on merge later
		gcc_jit_block_end_with_conditional(gen->curr_block, loc, cond, then_bb, else_bb);
	}

	// THEN
	gen->curr_block = then_bb;
	bool then_ended = build_block(gen, then_body);
	gcc_jit_block* then_open = gen->curr_block; // last open block in THEN
	                                            // (may differ from then_bb)

	// ELSE
	bool else_ended = false;
	gcc_jit_block* else_open = NULL;
	if (else_bb) {
		gen->curr_block = else_bb;
		else_ended = build_block(gen, else_body);
		else_open = gen->curr_block; // last open block in ELSE
	}

	// If both branches ended, no merge needed, we notify we have ended as
	// well
	if (else_bb && then_ended && else_ended) return true;

	// Ensure we have a merge if any branch continues.
	if (!merge_bb) merge_bb = gcc_jit_function_new_block(gen->curr_func, label_end);

	if (!then_ended) gcc_jit_block_end_with_jump(then_open, loc, merge_bb);
	if (else_bb && !else_ended) gcc_jit_block_end_with_jump(else_open, loc, merge_bb);

	gen->curr_block = merge_bb;
	return false;
}

static bool
build_var_decl_statement(Gen* gen, Node* node)
{
	gcc_jit_location* loc = loc_from_node(gen, node);
	const char* var_name = span_str(gen->src, node->data.var_decl.name, (char[IDENTSZ]) { 0 });
	gcc_jit_type* declared_type = ox_type_to_c_type(gen, node->data.var_decl.type);

	gcc_jit_lvalue* var_decl = NULL;

	gcc_jit_rvalue* rvalue = NULL;
	Node* init = node->data.var_decl.init;
	if (init != NULL) { rvalue = handle_expr(gen, init); }

	if (gen->curr_func == NULL && gen->curr_block == NULL) {
		// global var
		var_decl = gcc_jit_context_new_global(gen->ctx, loc, GCC_JIT_GLOBAL_INTERNAL, declared_type, strdup(var_name));
		if (rvalue) gcc_jit_global_set_initializer_rvalue(var_decl, rvalue);
	} else {
		// local var
		var_decl = gcc_jit_function_new_local(gen->curr_func, loc, declared_type, strdup(var_name));
		if (rvalue) gcc_jit_block_add_assignment(gen->curr_block, loc, var_decl, rvalue);
	}

	// this is not an error, there is just no initial value
	// if (node->data.var_decl.init == NULL) { panic("could not instanciate gcc jit new local"); }

	printf("adding 1 symbol: %s\n", var_name);

	Symbol* sym = (Symbol*)calloc(1, sizeof(Symbol));
	sym->name = node->data.var_decl.name;
	sym->decl = node;
	sym->ctype = declared_type;
	sym->d.lvalue = var_decl;
	sym->english_type = type_var;
	sym->is_variadic = node->data.var_decl.type->data.ident_type.is_variadic;
	sym->is_comp_time = node->data.var_decl.type->data.ident_type.is_comp_time;
	add_symbol(gen, sym); // add var_decl symbol
	return false;
}

static void build_func_decl(Gen*, Node*);

static bool
build_statement(Gen* gen, Node* node)
{
	gcc_jit_location* loc = loc_from_node(gen, node);
	switch (node->type) {
	case NODE_RETURN: {
		Node* return_expr = node->data.ret.expr;
		if (return_expr) {
			gcc_jit_rvalue* ret_rval = handle_expr(gen, return_expr);
			gcc_jit_type* expect_type = gcc_jit_function_get_return_type(gen->curr_func);
			gcc_jit_type* ret_type = gcc_jit_rvalue_get_type(ret_rval);
			types_match_or_panic(node, expect_type, ret_type);
			gcc_jit_block_end_with_return(gen->curr_block, loc, ret_rval);
		} else {
			gcc_jit_block_end_with_void_return(gen->curr_block, loc);
		}
		gen->curr_block = NULL; // important
		return true;            // we end the block here
	}
	case NODE_BREAK: {
		gcc_jit_block* t = current_break(gen);
		if (!t) return true; // break outside the loop
		gcc_jit_block_end_with_jump(gen->curr_block, loc, t);
		gen->curr_block = NULL;
		return true;
	}
	case NODE_CONTINUE: {
		gcc_jit_block* t = current_continue(gen);
		if (!t) return true; // continue outside of a loop
		gcc_jit_block_end_with_jump(gen->curr_block, loc, t);
		gen->curr_block = NULL;
		return true;
	}
	case NODE_VAR_DECL: {
		return build_var_decl_statement(gen, node);
	}
	case NODE_VAR_ASSIGN: {
		gcc_jit_rvalue* rvalue = handle_expr(gen, node->data.var_assign.rhs);
		// find the var_decl (d.lvalue) from the symbols table's symbol.
		for (size_t i = 0; i < gen->scope->len; i++) {
			Symbol* sym = gen->scope->symbols[i];
			if (span_ident_same(sym->name, node->data.var_assign.lhs->data.ident.name, gen->src)) {
				// we found the symbol we are assigning to
				// TODO check for variadic and comp_time
				if (!sym->is_variadic) {
					const char* name = span_str(gen->src, sym->name, (char[IDENTSZ]) { 0 });
					panic_at(node, "symbol %s is not variadic, cannot mutate.", name);
				}
				if (sym->is_comp_time) {
					const char* name = span_str(gen->src, sym->name, (char[IDENTSZ]) { 0 });
					panic_at(node, "symbol %s is comp time, cannot mutate.", name);
				}

				gcc_jit_lvalue* lvalue = sym->d.lvalue;

				// check the type of lvalue matches the rvalue
				gcc_jit_type* ltype = sym->ctype;
				gcc_jit_type* rtype = gcc_jit_rvalue_get_type(rvalue);
				if (rtype != ltype) {
					panic_at(node,
						"right hand side of assigment "
						"doesn't match the "
						"type of the left hand side: %s <-- %s",
						get_english_type(ltype),
						get_english_type(rtype));
				}
				if (lvalue) gcc_jit_block_add_assignment(gen->curr_block, loc, lvalue, rvalue);
			}
		}
		break;
	}
	case NODE_BINARY_EXPR: {
		Symbol* sym = find_symbol(gen, gen->scope, node->data.binary_expr.lhs->data.ident.name);
		if (!sym)
			panic_at(node,
				"NODE_BINARY_EXPR: Undefined symbol: %s\n",
				span_str(gen->src, node->data.ident.name, (char[IDENTSZ]) { 0 }));

		gcc_jit_lvalue* lv = sym->d.lvalue;
		gcc_jit_rvalue* rhs = handle_expr(gen, node->data.binary_expr.rhs);
		gcc_jit_rvalue* lhs = handle_expr(gen, node->data.binary_expr.lhs);
		gcc_jit_rvalue* temp = gcc_jit_lvalue_as_rvalue(lv);
		gcc_jit_type* lvtype = gcc_jit_rvalue_get_type(temp);
		gcc_jit_rvalue* sum
			= gcc_jit_context_new_binary_op(gen->ctx, loc_from_node(gen, node), GCC_JIT_BINARY_OP_PLUS, lvtype, lhs, rhs);

		gcc_jit_block_add_assignment(gen->curr_block, loc_from_node(gen, node), lv, sum);
	}
	case NODE_UNARY_EXPR: {
		gcc_jit_rvalue* rv = handle_unary_expr(gen, node);
		if (rv) gcc_jit_block_add_eval(gen->curr_block, loc_from_node(gen, node), rv);
		break;
	}
	case NODE_EXPR_STATEMENT: {
		gcc_jit_rvalue* rv = handle_expr(gen, node->data.expr_statement.expr);
		if (rv) gcc_jit_block_add_eval(gen->curr_block, loc_from_node(gen, node), rv);
		break;
	}
	case NODE_IF:
		return build_if_statement(gen, node);
	case NODE_FOR:
		return build_for_statement(gen, node);
	case NODE_WHILE:
		return build_while_statement(gen, node);
	case NODE_FUNCTION_DECL:
		// "closure"
		build_func_decl(gen, node);
		return false;
	case NODE_BLOCK:
		build_block(gen, node);
	default:
		printf("--- WARNING: build_statement unhandled, %s\n", node_type_str(node->type));
		break;
	}
	return false;
}

static gcc_jit_rvalue*
build_bool_value(Gen* gen, Node* node)
{
	gcc_jit_location* loc = loc_from_node(gen, node);

	switch (node->type) {
	case NODE_BOOL_LITERAL:
		/* true/false literal */
		return gcc_jit_context_new_rvalue_from_int(gen->ctx, type_boolean, node->data.boolean.value ? 1 : 0);
		break;
	case NODE_INT_LITERAL: {
		/* while (1) style – C truthiness */
		gcc_jit_rvalue* iv = emit_literal_int(gen, node);
		return gcc_jit_context_new_cast(gen->ctx, loc, iv, type_boolean);
	}
	case NODE_STRING_LITERAL: {
		/* while("word") should also be truthy if the pointer is non null */
		gcc_jit_rvalue* v = emit_literal_string(gen, node);
		gcc_jit_type* ty = gcc_jit_rvalue_get_type(v);
		gcc_jit_rvalue* nullv = gcc_jit_context_null(gen->ctx, ty);
		return gcc_jit_context_new_comparison(gen->ctx, loc, GCC_JIT_COMPARISON_NE, v, nullv);
	}
	case NODE_BINARY_EXPR: {
		gcc_jit_rvalue* lvalue = handle_expr(gen, node->data.binary_expr.lhs);
		gcc_jit_rvalue* rvalue = handle_expr(gen, node->data.binary_expr.rhs);
		enum gcc_jit_comparison op;
		switch (node->data.binary_expr.op) {
		case OP_LT:
			op = GCC_JIT_COMPARISON_LT;
			break;
		case OP_GT:
			op = GCC_JIT_COMPARISON_GT;
			break;
		case OP_LT_EQ:
			op = GCC_JIT_COMPARISON_LE;
			break;
		case OP_GT_EQ:
			op = GCC_JIT_COMPARISON_GE;
			break;
		default:
			printf("build_bool_rvalue nodebinary op unhandled, "
			       "%s\n",
				node_type_str(node->type));
			return NULL;
		}
		return gcc_jit_context_new_comparison(gen->ctx, loc, op, lvalue, rvalue);
		break;
	}
	default:
		printf("/!\\ build_bool_rvalue unhandled, %s\n", node_type_str(node->type));
		break;
	}

	return NULL;
}

// build_block: returns true if block ended with a terminator
static bool
build_block(Gen* gen, Node* body)
{
	// TODO if there is no block ({ ... }), we presume there is only 1 statement.
	// This might not always be valid, func declaration do require a block {}, but there might be cases...
	// FIXME: a symbol declared in a child block is accessible in the parent or in a sibling block...
	if (body->data.block.len == 0) { return build_statement(gen, body); }

	for (size_t i = 0; i < body->data.block.len; i++) {
		if (gen->curr_block == NULL) return true;
		bool ended = build_statement(gen, body->data.block.stmts[i]);
		if (ended) return true;
	}
	return false;
}

static void
build_func_decl(Gen* gen, Node* node)
{
	const char* func_name = span_str(gen->src, node->data.function_decl.name, (char[IDENTSZ]) { 0 });

	Node* return_type = node->data.function_decl.return_type;
	gcc_jit_type* ret_type = ox_type_to_c_type(gen, return_type);
	gcc_jit_location* loc = loc_from_node(gen, node);

	size_t argc = node->data.function_decl.p_len;

	gcc_jit_param** params = calloc(argc, sizeof(gcc_jit_param*));

	// 1) Create ONE gcc_jit_param per AST param
	for (size_t i = 0; i < argc; i++) {
		Node* pnode = node->data.function_decl.params[i];
		const char* pname = span_str(gen->src, pnode->data.param.name, (char[IDENTSZ]) { 0 });

		gcc_jit_type* declared_type = ox_type_to_c_type(gen, pnode->data.param.type);

		gcc_jit_param* p = gcc_jit_context_new_param(gen->ctx, loc, declared_type, strdup(pname));

		params[i] = p;
	}

	// 2) Create the function with those params
	gcc_jit_function* func = gcc_jit_context_new_function(gen->ctx,
		loc,
		GCC_JIT_FUNCTION_EXPORTED,
		ret_type,
		strdup(func_name),
		argc,
		params, // <-- these are now owned by 'func'
		0);

	gcc_jit_block* block = gcc_jit_function_new_block(func, "entry");

	gcc_jit_function* prev_func = gen->curr_func;
	gcc_jit_block* prev_block = gen->curr_block;
	gen->curr_func = func;
	gen->curr_block = block;

	// 3) Add the function symbol to the current (enclosing) scope
	Symbol* sym = (Symbol*)calloc(1, sizeof(Symbol));
	sym->name = node->data.function_decl.name;
	sym->decl = node;
	sym->ctype = type_voidp; // whatever you use for function type metadata
	sym->d.funcvalue = func;
	sym->english_type = type_func;
	add_symbol(gen, sym); // add function symbol -- to the 'parent scope', before we push_scope

	// ENTER FUNCTION SCOPE
	push_scope(gen);

	// 4) Add parameter symbols to the FUNCTION scope, reusing the same gcc_jit_param*
	for (size_t i = 0; i < argc; i++) {
		Node* pnode = node->data.function_decl.params[i];

		gcc_jit_param* p = params[i]; // <-- reuse, DO NOT new_param again

		gcc_jit_type* declared_type = ox_type_to_c_type(gen, pnode->data.param.type);

		Symbol* ps = (Symbol*)calloc(1, sizeof(Symbol));
		ps->name = pnode->data.param.name; // param identifier span
		ps->decl = pnode;
		ps->ctype = declared_type;
		ps->d.param = p;             // <-- this is associated with 'func'
		ps->english_type = type_var; // or type_param if you distinguish
		add_symbol(gen, ps);         // add param symbol
	}

	print_symbols_here(gen);

	build_block(gen, node->data.function_decl.body);

	if (gen->curr_block == NULL) {
		// no open block: all paths already terminated, don't add an implicit return
	} else if (ret_type == type_int) {
		gcc_jit_rvalue* ret_value = gcc_jit_context_new_rvalue_from_int(gen->ctx, type_int, 0);
		gcc_jit_block_end_with_return(gen->curr_block, loc, ret_value);
	} else if (ret_type == type_void) {
		gcc_jit_block_end_with_void_return(gen->curr_block, loc);
	} else {
		printf("build_func_decl unhandled return type in func: %s - "
		       "defaulting to void\n",
			func_name);
		gcc_jit_block_end_with_void_return(gen->curr_block, loc);
	}

	// LEAVE FUNCTION SCOPE
	pop_scope(gen);

	gen->curr_func = prev_func;
	gen->curr_block = prev_block;
}

void
gen_next(Gen* gen, Node* node)
{
	// printf("gen_next, %s\n", node_type_str(node->type));

	switch (node->type) {
	case NODE_PROGRAM:
		build_program(gen, node);
		break;
	case NODE_FUNCTION_DECL:
		build_func_decl(gen, node);
		break;
	case NODE_STRING_LITERAL:
		emit_literal_string(gen, node);
		break;
	case NODE_INT_LITERAL:
		emit_literal_int(gen, node);
		break;
	case NODE_FLOAT_LITERAL:
		emit_literal_float(gen, node);
		break;
	case NODE_VAR_DECL:
		build_statement(gen, node);
		break;
	default:
		printf("gen: unhandled, %s\n", node_type_str(node->type));
	}
}