diff options
Diffstat (limited to 'src/compile/functions.c')
| -rw-r--r-- | src/compile/functions.c | 282 |
1 files changed, 203 insertions, 79 deletions
diff --git a/src/compile/functions.c b/src/compile/functions.c index 63d7d23d..ec37c0ad 100644 --- a/src/compile/functions.c +++ b/src/compile/functions.c @@ -3,6 +3,7 @@ #include "../ast.h" #include "../environment.h" #include "../naming.h" +#include "../stdlib/c_strings.h" #include "../stdlib/datatypes.h" #include "../stdlib/integers.h" #include "../stdlib/nums.h" @@ -247,85 +248,6 @@ Text_t compile_function_call(env_t *env, ast_t *ast) { } } -public -Text_t compile_lambda(env_t *env, ast_t *ast) { - DeclareMatch(lambda, ast, Lambda); - Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id)); - - env_t *body_scope = fresh_scope(env); - body_scope->deferred = NULL; - for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { - type_t *arg_type = get_arg_ast_type(env, arg); - set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); - } - - body_scope->fn = ast; - - Table_t closed_vars = get_closed_vars(env, lambda->args, ast); - if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata - Text_t def = Text("typedef struct {"); - for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { - struct { - const char *name; - binding_t *b; - } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; - if (has_stack_memory(entry->b->type)) - code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type), - " stack memory in the variable `", entry->name, - "`, but the function may outlive the stack memory"); - if (entry->b->type->tag == ModuleType) continue; - set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name)); - def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; "); - } - def = Texts(def, "} ", name, "$userdata_t;"); - env->code->local_typedefs = Texts(env->code->local_typedefs, def); - } - - type_t *ret_t = get_function_return_type(env, ast); - Text_t code = Texts("static ", compile_type(ret_t), " ", name, "("); - for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { - type_t *arg_type = get_arg_ast_type(env, arg); - code = Texts(code, compile_type(arg_type), " _$", arg->name, ", "); - } - - Text_t userdata; - if (Table$length(closed_vars) == 0) { - code = Texts(code, "void *_)"); - userdata = Text("NULL"); - } else { - userdata = Texts("new(", name, "$userdata_t"); - for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { - struct { - const char *name; - binding_t *b; - } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; - if (entry->b->type->tag == ModuleType) continue; - binding_t *b = get_binding(env, entry->name); - assert(b); - Text_t binding_code = b->code; - if (entry->b->type->tag == ListType) userdata = Texts(userdata, ", LIST_COPY(", binding_code, ")"); - else if (entry->b->type->tag == TableType) userdata = Texts(userdata, ", TABLE_COPY(", binding_code, ")"); - else userdata = Texts(userdata, ", ", binding_code); - } - userdata = Texts(userdata, ")"); - code = Texts(code, name, "$userdata_t *userdata)"); - } - - Text_t body = EMPTY_TEXT; - for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) { - if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType - || get_type(body_scope, stmt->ast)->tag == ReturnType) - body = Texts(body, compile_statement(body_scope, stmt->ast), "\n"); - else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n"); - bind_statement(body_scope, stmt->ast); - } - if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred) - body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n"); - - env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n"); - return Texts("((Closure_t){", name, ", ", userdata, "})"); -} - static void add_closed_vars(Table_t *closed_vars, env_t *enclosing_scope, env_t *env, ast_t *ast) { if (ast == NULL) return; @@ -595,6 +517,206 @@ Table_t get_closed_vars(env_t *env, arg_ast_t *args, ast_t *block) { return closed_vars; } +static visit_behavior_t find_used_variables(ast_t *ast, void *userdata) { + Table_t *vars = (Table_t *)userdata; + switch (ast->tag) { + case Var: { + const char *name = Match(ast, Var)->name; + Table$str_set(vars, name, ast); + return VISIT_STOP; + } + case Assign: { + for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) { + ast_t *var = target->ast; + for (;;) { + if (var->tag == Index) { + ast_t *index = Match(var, Index)->index; + if (index) ast_visit(index, find_used_variables, userdata); + var = Match(var, Index)->indexed; + } else if (var->tag == FieldAccess) { + var = Match(var, FieldAccess)->fielded; + } else { + break; + } + } + } + for (ast_list_t *val = Match(ast, Assign)->values; val; val = val->next) { + ast_visit(val->ast, find_used_variables, userdata); + } + return VISIT_STOP; + } + case UPDATE_CASES: { + binary_operands_t operands = BINARY_OPERANDS(ast); + ast_t *lhs = operands.lhs; + for (;;) { + if (lhs->tag == Index) { + ast_t *index = Match(lhs, Index)->index; + if (index) ast_visit(index, find_used_variables, userdata); + lhs = Match(lhs, Index)->indexed; + } else if (lhs->tag == FieldAccess) { + lhs = Match(lhs, FieldAccess)->fielded; + } else { + break; + } + } + ast_visit(operands.rhs, find_used_variables, userdata); + return VISIT_STOP; + } + case Declare: { + ast_visit(Match(ast, Declare)->value, find_used_variables, userdata); + return VISIT_STOP; + } + default: return VISIT_PROCEED; + } +} + +static visit_behavior_t find_assigned_variables(ast_t *ast, void *userdata) { + Table_t *vars = (Table_t *)userdata; + switch (ast->tag) { + case Assign: + for (ast_list_t *target = Match(ast, Assign)->targets; target; target = target->next) { + ast_t *var = target->ast; + for (;;) { + if (var->tag == Index) var = Match(var, Index)->indexed; + else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded; + else break; + } + if (var->tag == Var) { + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + } + } + return VISIT_STOP; + case UPDATE_CASES: { + binary_operands_t operands = BINARY_OPERANDS(ast); + ast_t *var = operands.lhs; + for (;;) { + if (var->tag == Index) var = Match(var, Index)->indexed; + else if (var->tag == FieldAccess) var = Match(var, FieldAccess)->fielded; + else break; + } + if (var->tag == Var) { + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + } + return VISIT_STOP; + } + case Declare: { + ast_t *var = Match(ast, Declare)->var; + const char *name = Match(var, Var)->name; + Table$str_set(vars, name, var); + return VISIT_STOP; + } + default: return VISIT_PROCEED; + } +} + +static void check_unused_vars(env_t *env, arg_ast_t *args, ast_t *body) { + Table_t used_vars = EMPTY_TABLE; + ast_visit(body, find_used_variables, &used_vars); + Table_t assigned_vars = EMPTY_TABLE; + ast_visit(body, find_assigned_variables, &assigned_vars); + + for (arg_ast_t *arg = args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + if (arg_type->tag == PointerType) { + Table$str_remove(&assigned_vars, arg->name); + } + } + + Table_t unused = Table$without(assigned_vars, used_vars, Table$info(&CString$info, &Present$$info)); + for (int64_t i = 0; i < (int64_t)unused.entries.length; i++) { + struct { + const char *name; + } *entry = unused.entries.data + i * unused.entries.stride; + if (streq(entry->name, "_")) continue; + ast_t *var = Table$str_get(assigned_vars, entry->name); + code_err(var, "This variable was assigned to, but never read from."); + } +} + +public +Text_t compile_lambda(env_t *env, ast_t *ast) { + DeclareMatch(lambda, ast, Lambda); + Text_t name = namespace_name(env, env->namespace, Texts("lambda$", lambda->id)); + + env_t *body_scope = fresh_scope(env); + body_scope->deferred = NULL; + for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + set_binding(body_scope, arg->name, arg_type, Texts("_$", arg->name)); + } + + body_scope->fn = ast; + + Table_t closed_vars = get_closed_vars(env, lambda->args, ast); + if (Table$length(closed_vars) > 0) { // Create a typedef for the lambda's closure userdata + Text_t def = Text("typedef struct {"); + for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { + struct { + const char *name; + binding_t *b; + } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; + if (has_stack_memory(entry->b->type)) + code_err(ast, "This function is holding onto a reference to ", type_to_text(entry->b->type), + " stack memory in the variable `", entry->name, + "`, but the function may outlive the stack memory"); + if (entry->b->type->tag == ModuleType) continue; + set_binding(body_scope, entry->name, entry->b->type, Texts("userdata->", entry->name)); + def = Texts(def, compile_declaration(entry->b->type, Text$from_str(entry->name)), "; "); + } + def = Texts(def, "} ", name, "$userdata_t;"); + env->code->local_typedefs = Texts(env->code->local_typedefs, def); + } + + type_t *ret_t = get_function_return_type(env, ast); + Text_t code = Texts("static ", compile_type(ret_t), " ", name, "("); + for (arg_ast_t *arg = lambda->args; arg; arg = arg->next) { + type_t *arg_type = get_arg_ast_type(env, arg); + code = Texts(code, compile_type(arg_type), " _$", arg->name, ", "); + } + + Text_t userdata; + if (Table$length(closed_vars) == 0) { + code = Texts(code, "void *_)"); + userdata = Text("NULL"); + } else { + userdata = Texts("new(", name, "$userdata_t"); + for (int64_t i = 0; i < (int64_t)closed_vars.entries.length; i++) { + struct { + const char *name; + binding_t *b; + } *entry = closed_vars.entries.data + closed_vars.entries.stride * i; + if (entry->b->type->tag == ModuleType) continue; + binding_t *b = get_binding(env, entry->name); + assert(b); + Text_t binding_code = b->code; + if (entry->b->type->tag == ListType) userdata = Texts(userdata, ", LIST_COPY(", binding_code, ")"); + else if (entry->b->type->tag == TableType) userdata = Texts(userdata, ", TABLE_COPY(", binding_code, ")"); + else userdata = Texts(userdata, ", ", binding_code); + } + userdata = Texts(userdata, ")"); + code = Texts(code, name, "$userdata_t *userdata)"); + } + + Text_t body = EMPTY_TEXT; + for (ast_list_t *stmt = Match(lambda->body, Block)->statements; stmt; stmt = stmt->next) { + if (stmt->next || ret_t->tag == VoidType || ret_t->tag == AbortType + || get_type(body_scope, stmt->ast)->tag == ReturnType) + body = Texts(body, compile_statement(body_scope, stmt->ast), "\n"); + else body = Texts(body, compile_statement(body_scope, FakeAST(Return, stmt->ast)), "\n"); + bind_statement(body_scope, stmt->ast); + } + if ((ret_t->tag == VoidType || ret_t->tag == AbortType) && body_scope->deferred) + body = Texts(body, compile_statement(body_scope, FakeAST(Return)), "\n"); + + env->code->lambdas = Texts(env->code->lambdas, code, " {\n", body, "\n}\n"); + + check_unused_vars(env, lambda->args, lambda->body); + + return Texts("((Closure_t){", name, ", ", userdata, "})"); +} + public Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *staticdefs) { bool is_private = false; @@ -785,6 +907,8 @@ Text_t compile_function(env_t *env, Text_t name_code, ast_t *ast, Text_t *static } } + check_unused_vars(env, args, body); + return definition; } |
