Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/runtime/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ typedef struct {
Type **param_types;
Expr **param_defaults; // Default value expressions (NULL for required params)
int *param_is_ref; // 1 if parameter is pass-by-reference (ref keyword)
int *param_is_const; // 1 if parameter is const (immutable)
uint32_t *param_hashes; // Pre-computed hashes of param names (optimization)
int num_params;
char *rest_param; // Name of rest parameter (...args), NULL if none
Expand Down
2 changes: 2 additions & 0 deletions runtime/include/hemlock_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,8 @@ void hml_defer_push_call(HmlValue fn);
void hml_defer_push_call_with_args(HmlValue fn, HmlValue *args, int num_args);
void hml_defer_pop_and_execute(void);
void hml_defer_execute_all(void);
int hml_defer_save_depth(void);
void hml_defer_execute_to_depth(int saved_depth);

// ========== ASYNC/CONCURRENCY ==========

Expand Down
19 changes: 19 additions & 0 deletions runtime/src/builtins_func.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,25 @@ void hml_defer_execute_all(void) {
}
}

int hml_defer_save_depth(void) {
int depth = 0;
DeferEntry *e = g_defer_stack;
while (e) { depth++; e = e->next; }
return depth;
}

void hml_defer_execute_to_depth(int saved_depth) {
// Count current depth
int current = 0;
DeferEntry *e = g_defer_stack;
while (e) { current++; e = e->next; }
// Execute only the entries added since saved_depth
while (current > saved_depth && g_defer_stack) {
hml_defer_pop_and_execute();
current--;
}
}

// Helper for deferring HmlValue function calls
static void hml_defer_call_wrapper(void *arg) {
HmlValue *fn_ptr = (HmlValue *)arg;
Expand Down
80 changes: 72 additions & 8 deletions src/backends/compiler/codegen_expr_match.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,45 @@ static const char* type_kind_to_hml_type_enum(TypeKind kind) {
}
}

// Helper: collect all binding names from a pattern (for pre-declaration in OR patterns)
static void collect_pattern_bindings(Pattern *p, char ***names, int *count, int *cap) {
if (!p) return;
switch (p->type) {
case PATTERN_BINDING:
if (*count >= *cap) {
*cap = *cap ? *cap * 2 : 4;
*names = realloc(*names, (size_t)*cap * sizeof(char*));
}
(*names)[(*count)++] = p->as.binding.name;
break;
case PATTERN_TYPED:
if (p->as.typed.name) {
if (*count >= *cap) {
*cap = *cap ? *cap * 2 : 4;
*names = realloc(*names, (size_t)*cap * sizeof(char*));
}
(*names)[(*count)++] = p->as.typed.name;
}
break;
case PATTERN_OR:
// Only collect from first alternative (all should bind the same names)
if (p->as.or_pattern.num_alternatives > 0)
collect_pattern_bindings(p->as.or_pattern.alternatives[0], names, count, cap);
break;
case PATTERN_OBJECT:
for (int i = 0; i < p->as.object.num_fields; i++)
if (p->as.object.fields[i].pattern)
collect_pattern_bindings(p->as.object.fields[i].pattern, names, count, cap);
break;
case PATTERN_ARRAY:
for (int i = 0; i < p->as.array.num_elements; i++)
collect_pattern_bindings(p->as.array.elements[i].pattern, names, count, cap);
break;
default:
break;
}
}

// Generate code to match a pattern against a value
void codegen_pattern_match(CodegenContext *ctx, Pattern *pattern, const char *scrutinee, const char *fail_label) {
if (!pattern) {
Expand Down Expand Up @@ -67,11 +106,17 @@ void codegen_pattern_match(CodegenContext *ctx, Pattern *pattern, const char *sc
case PATTERN_BINDING: {
// Bind the value to a local variable
// Use the actual variable name so identifier lookup works
codegen_add_local(ctx, pattern->as.binding.name);
if (ctx->current_scope) {
scope_add_var(ctx->current_scope, pattern->as.binding.name);
if (codegen_is_local(ctx, pattern->as.binding.name)) {
// Already pre-declared (e.g., from OR pattern), use assignment
codegen_writeln(ctx, "hml_release(&%s);", pattern->as.binding.name);
codegen_writeln(ctx, "%s = %s;", pattern->as.binding.name, scrutinee);
} else {
codegen_add_local(ctx, pattern->as.binding.name);
if (ctx->current_scope) {
scope_add_var(ctx->current_scope, pattern->as.binding.name);
}
codegen_writeln(ctx, "HmlValue %s = %s;", pattern->as.binding.name, scrutinee);
}
codegen_writeln(ctx, "HmlValue %s = %s;", pattern->as.binding.name, scrutinee);
codegen_writeln(ctx, "hml_retain(&%s);", pattern->as.binding.name);
break;
}
Expand All @@ -90,11 +135,17 @@ void codegen_pattern_match(CodegenContext *ctx, Pattern *pattern, const char *sc
}
// Bind if name is provided
if (pattern->as.typed.name) {
codegen_add_local(ctx, pattern->as.typed.name);
if (ctx->current_scope) {
scope_add_var(ctx->current_scope, pattern->as.typed.name);
if (codegen_is_local(ctx, pattern->as.typed.name)) {
// Already pre-declared (e.g., from OR pattern)
codegen_writeln(ctx, "hml_release(&%s);", pattern->as.typed.name);
codegen_writeln(ctx, "%s = %s;", pattern->as.typed.name, scrutinee);
} else {
codegen_add_local(ctx, pattern->as.typed.name);
if (ctx->current_scope) {
scope_add_var(ctx->current_scope, pattern->as.typed.name);
}
codegen_writeln(ctx, "HmlValue %s = %s;", pattern->as.typed.name, scrutinee);
}
codegen_writeln(ctx, "HmlValue %s = %s;", pattern->as.typed.name, scrutinee);
codegen_writeln(ctx, "hml_retain(&%s);", pattern->as.typed.name);
}
break;
Expand All @@ -104,6 +155,19 @@ void codegen_pattern_match(CodegenContext *ctx, Pattern *pattern, const char *sc
// Try each alternative - if any matches, continue; if all fail, go to fail_label
char *success_label = codegen_label(ctx);

// Pre-declare binding variables so they survive the inner {} blocks
char **binding_names = NULL;
int binding_count = 0, binding_cap = 0;
collect_pattern_bindings(pattern, &binding_names, &binding_count, &binding_cap);
for (int b = 0; b < binding_count; b++) {
codegen_add_local(ctx, binding_names[b]);
if (ctx->current_scope) {
scope_add_var(ctx->current_scope, binding_names[b]);
}
codegen_writeln(ctx, "HmlValue %s = hml_val_null();", binding_names[b]);
}
free(binding_names);

for (int i = 0; i < pattern->as.or_pattern.num_alternatives; i++) {
char *next_alt_label = (i + 1 < pattern->as.or_pattern.num_alternatives) ?
codegen_label(ctx) : NULL;
Expand Down
14 changes: 10 additions & 4 deletions src/backends/compiler/codegen_program.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ void codegen_function_decl(CodegenContext *ctx, Expr *func, const char *name, An
codegen_writeln(ctx, "HML_CALL_ENTER();");
}

// Save defer stack depth so we only execute this function's defers on return
codegen_writeln(ctx, "int _defer_depth = hml_defer_save_depth();");

// OPTIMIZATION: Tail call elimination
// Check if function is tail recursive and set up for tail call optimization
// Tail call optimization converts: return func(args) -> reassign params; goto start
Expand All @@ -165,10 +168,10 @@ void codegen_function_decl(CodegenContext *ctx, Expr *func, const char *name, An
// Generate body
funcgen_generate_body(ctx, func);

// Execute defers before implicit return
// Execute defers before implicit return (only this function's defers)
codegen_defer_execute_all(ctx);
if (ctx->has_defers) {
codegen_writeln(ctx, "hml_defer_execute_all();");
codegen_writeln(ctx, "hml_defer_execute_to_depth(_defer_depth);");
}

// Release body-local variables and shared env before returning
Expand Down Expand Up @@ -303,16 +306,19 @@ void codegen_closure_impl(CodegenContext *ctx, ClosureInfo *closure) {
codegen_writeln(ctx, "HML_CALL_ENTER();");
}

// Save defer stack depth so we only execute this function's defers on return
codegen_writeln(ctx, "int _defer_depth = hml_defer_save_depth();");

// Set up shared environment for nested closures
funcgen_setup_shared_env(ctx, func, closure);

// Generate body
funcgen_generate_body(ctx, func);

// Execute defers before implicit return
// Execute defers before implicit return (only this function's defers)
codegen_defer_execute_all(ctx);
if (ctx->has_defers) {
codegen_writeln(ctx, "hml_defer_execute_all();");
codegen_writeln(ctx, "hml_defer_execute_to_depth(_defer_depth);");
}

// Release body-local variables before returning
Expand Down
8 changes: 4 additions & 4 deletions src/backends/compiler/codegen_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -969,7 +969,7 @@ void codegen_stmt(CodegenContext *ctx, Stmt *stmt) {
codegen_defer_execute_all(ctx);
// Execute any runtime defers (from loops) - only if this function has defers
if (ctx->has_defers) {
codegen_writeln(ctx, "hml_defer_execute_all();");
codegen_writeln(ctx, "hml_defer_execute_to_depth(_defer_depth);");
}
// Release body-local variables
codegen_emit_local_cleanup(ctx, NULL);
Expand Down Expand Up @@ -1068,7 +1068,7 @@ void codegen_stmt(CodegenContext *ctx, Stmt *stmt) {
char *value = codegen_expr(ctx, stmt->as.return_stmt.value);
// Execute any runtime defers (from loops) - only if this function has defers
if (ctx->has_defers) {
codegen_writeln(ctx, "hml_defer_execute_all();");
codegen_writeln(ctx, "hml_defer_execute_to_depth(_defer_depth);");
}
// Release body-local variables
codegen_emit_local_cleanup(ctx, NULL);
Expand All @@ -1080,7 +1080,7 @@ void codegen_stmt(CodegenContext *ctx, Stmt *stmt) {
} else {
// Execute any runtime defers (from loops) - only if this function has defers
if (ctx->has_defers) {
codegen_writeln(ctx, "hml_defer_execute_all();");
codegen_writeln(ctx, "hml_defer_execute_to_depth(_defer_depth);");
}
// Release body-local variables
codegen_emit_local_cleanup(ctx, NULL);
Expand Down Expand Up @@ -1267,7 +1267,7 @@ void codegen_stmt(CodegenContext *ctx, Stmt *stmt) {
codegen_indent_inc(ctx);
// Execute any runtime defers (from loops) - only if this function has defers
if (ctx->has_defers) {
codegen_writeln(ctx, "hml_defer_execute_all();");
codegen_writeln(ctx, "hml_defer_execute_to_depth(_defer_depth);");
}
// Release body-local variables before returning
codegen_emit_local_cleanup(ctx, NULL);
Expand Down
1 change: 1 addition & 0 deletions src/backends/interpreter/builtins/concurrency.c
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ Value builtin_join(Value *args, int num_args, ExecutionContext *ctx) {
if (task->ctx->exception_state.is_throwing) {
// Re-throw the exception in the current context
ctx->exception_state = task->ctx->exception_state;
VALUE_RETAIN(ctx->exception_state.exception_value);
pthread_mutex_unlock((pthread_mutex_t*)task->task_mutex);
return val_null();
}
Expand Down
4 changes: 2 additions & 2 deletions src/backends/interpreter/environment.c
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ void env_define_borrowed(Environment *env, const char *name, Value value, int is
// This is safe because we know params are unique (enforced by parser) and env is freshly created
// Note: This is typically called on newly created environments that aren't shared yet,
// but we still lock for consistency if the env happens to be shared.
void env_define_param(Environment *env, const char *name, uint32_t hash, Value value) {
void env_define_param(Environment *env, const char *name, uint32_t hash, Value value, int is_const) {
// Lock environment for thread-safe access
pthread_mutex_t *mutex = (pthread_mutex_t*)env->mutex;
if (mutex) pthread_mutex_lock(mutex);
Expand All @@ -613,7 +613,7 @@ void env_define_param(Environment *env, const char *name, uint32_t hash, Value v
}
VALUE_RETAIN(value);
env->values[index] = value;
env->is_const[index] = 0; // Params are never const
env->is_const[index] = is_const;
env->count++;

// Insert with pre-computed hash
Expand Down
2 changes: 1 addition & 1 deletion src/backends/interpreter/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ void env_define(Environment *env, const char *name, Value value, int is_const, E
// Fast variant that borrows the name string without strdup - caller must ensure name outlives env
void env_define_borrowed(Environment *env, const char *name, Value value, int is_const, ExecutionContext *ctx);
// Fastest variant for function params - uses pre-computed hash, skips "already defined" check
void env_define_param(Environment *env, const char *name, uint32_t hash, Value value);
void env_define_param(Environment *env, const char *name, uint32_t hash, Value value, int is_const);
void env_set(Environment *env, const char *name, Value value, ExecutionContext *ctx);
Value env_get(Environment *env, const char *name, ExecutionContext *ctx);

Expand Down
3 changes: 2 additions & 1 deletion src/backends/interpreter/runtime/eval_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1023,7 +1023,8 @@ Value eval_call_expr(Expr *expr, Environment *env, ExecutionContext *ctx) {
}

// Use fast param binding with pre-computed hash (skips redundant checks)
env_define_param(call_env, fn->param_names[i], fn->param_hashes[i], arg_value);
int is_const_param = fn->param_is_const && fn->param_is_const[i];
env_define_param(call_env, fn->param_names[i], fn->param_hashes[i], arg_value, is_const_param);

// Release default param value if we created it (not from args array)
// For default params or ref params, arg_value was created locally and needs release
Expand Down
6 changes: 3 additions & 3 deletions src/backends/interpreter/runtime/eval_pattern_match.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ int match_pattern(Pattern *pattern, Value value, Environment *env, ExecutionCont
Environment *temp_env = env_new(env);
int matched = match_pattern(pattern->as.or_pattern.alternatives[i], value, temp_env, ctx);
if (matched) {
// Copy bindings from temp_env into the real env
// Transfer bindings from temp_env to the real env
for (int j = 0; j < temp_env->count; j++) {
VALUE_RETAIN(temp_env->values[j]);
env_define(env, temp_env->names[j], temp_env->values[j], 0, ctx);
}
env_free(temp_env);
env_release(temp_env);
return 1;
}
env_free(temp_env);
env_release(temp_env);
}
return 0;
}
Expand Down
2 changes: 2 additions & 0 deletions src/backends/interpreter/runtime/expressions.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ Value eval_expr(Expr *expr, Environment *env, ExecutionContext *ctx) {
bound_fn->param_types = orig_fn->param_types;
bound_fn->param_defaults = orig_fn->param_defaults;
bound_fn->param_is_ref = orig_fn->param_is_ref; // Share ref flags
bound_fn->param_is_const = orig_fn->param_is_const; // Share const flags
bound_fn->param_hashes = orig_fn->param_hashes; // Share pre-computed hashes
bound_fn->num_params = orig_fn->num_params;
bound_fn->rest_param = orig_fn->rest_param; // Share rest param name
Expand Down Expand Up @@ -773,6 +774,7 @@ Value eval_expr(Expr *expr, Environment *env, ExecutionContext *ctx) {
fn->param_types = expr->as.function.param_types;
fn->param_defaults = expr->as.function.param_defaults;
fn->param_is_ref = expr->as.function.param_is_ref;
fn->param_is_const = expr->as.function.param_is_const;

// Lazily compute and cache param hashes on the AST node.
// First closure from this AST node pays the cost; subsequent ones reuse.
Expand Down
7 changes: 7 additions & 0 deletions src/backends/interpreter/runtime/statements.c
Original file line number Diff line number Diff line change
Expand Up @@ -749,12 +749,14 @@ void eval_stmt(Stmt *stmt, Environment *env, ExecutionContext *ctx) {
Value saved_exception = ctx->exception_state.exception_value;
int was_breaking = ctx->loop_state.is_breaking;
int was_continuing = ctx->loop_state.is_continuing;
char *saved_target_label = ctx->loop_state.target_label;

// Clear states before finally
ctx->return_state.is_returning = 0;
ctx->exception_state.is_throwing = 0;
ctx->loop_state.is_breaking = 0;
ctx->loop_state.is_continuing = 0;
ctx->loop_state.target_label = NULL;

// Execute finally block
eval_stmt(stmt->as.try_stmt.finally_block, env, ctx);
Expand All @@ -768,6 +770,11 @@ void eval_stmt(Stmt *stmt, Environment *env, ExecutionContext *ctx) {
ctx->exception_state.exception_value = saved_exception;
ctx->loop_state.is_breaking = was_breaking;
ctx->loop_state.is_continuing = was_continuing;
ctx->loop_state.target_label = saved_target_label;
} else {
// Finally introduced its own break/continue/return/throw;
// the saved label is no longer needed
if (saved_target_label) free(saved_target_label);
}
}
break;
Expand Down
5 changes: 5 additions & 0 deletions src/backends/interpreter/values.c
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,11 @@ void function_free(Function *fn) {
free(fn->param_is_ref);
}

// Free param_is_const array
if (fn->param_is_const) {
free(fn->param_is_const);
}

// Free pre-computed param hashes
if (fn->param_hashes) {
free(fn->param_hashes);
Expand Down
12 changes: 7 additions & 5 deletions src/frontend/lexer/lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -1064,10 +1064,10 @@ Token lexer_next(Lexer *lex) {
advance(lex); // consume third dot
return make_token(lex, TOK_DOT_DOT_DOT);
}
// Check for float literal without leading zero (e.g., .5)
// Check for float literal without leading zero (e.g., .5, .1_2_3)
if (isdigit(peek(lex))) {
// Consume the fractional digits
while (isdigit(peek(lex))) {
// Consume the fractional digits (with underscore separators)
while (isdigit(peek(lex)) || peek(lex) == '_') {
advance(lex);
}

Expand All @@ -1089,15 +1089,17 @@ Token lexer_next(Lexer *lex) {
if (peek(lex) == '+' || peek(lex) == '-') {
advance(lex); // consume sign
}
while (isdigit(peek(lex))) {
while (isdigit(peek(lex)) || peek(lex) == '_') {
advance(lex);
}
}
}

Token token = make_token(lex, TOK_NUMBER);
token.is_float = 1;
token.float_value = strtod(lex->start, NULL);
char *clean = strip_underscores(lex->start, (int)(lex->current - lex->start));
token.float_value = strtod(clean, NULL);
free(clean);
return token;
}
return make_token(lex, TOK_DOT);
Expand Down
7 changes: 7 additions & 0 deletions tests/parity/language/defer_nested_functions.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
outer before inner
inner body
inner defer 2
inner defer 1
outer after inner
outer defer 2
outer defer 1
Loading
Loading