1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294 | // This file defines how to compile loops
#include <gmp.h>
#include "../ast.h"
#include "../config.h"
#include "../environment.h"
#include "../stdlib/datatypes.h"
#include "../stdlib/integers.h"
#include "../stdlib/table.h"
#include "../stdlib/text.h"
#include "../typecheck.h"
#include "../util.h"
#include "compilation.h"
// For loops with an optional `i` index variable (`for i, x in ...`), the index
// is an Int64 counting 1, 2, 3, ..., an independent counter declared before
// the loop and stepped once per yielded element:
static Text_t index_counter_decl(Text_t index) {
return index.length > 0 ? Texts("Int64_t ", index, "$counter = 0;\n") : EMPTY_TEXT;
}
static Text_t index_counter_step(Text_t index) {
return index.length > 0 ? Texts("Int64_t ", index, " = ++", index, "$counter;\n") : EMPTY_TEXT;
}
// ---------------------------------------------------------------------------
// Copy-on-write guard hoisting.
//
// Every indexed list write normally pays a per-write CoW guard
// (`if (data_refcount > 0) List$compact(...)` inside List_lvalue). Even when
// the branch is never taken, the *returning* List$compact call in a hot loop
// pins registers and blocks vectorization. When we can prove the guard can't
// fire mid-loop, we hoist it: compact once (if shared) before the loop, then
// compile the loop's list accesses against hoisted data/stride/length locals
// (List_get_hoisted / List_lvalue_hoisted / List_swap_hoisted, with bounds
// checks kept and only the CoW guard and the header re-reads are removed).
//
// Soundness: data_refcount is not a general aliasing property, since writes
// through aliased pointers never bump it. It changes only at specific
// compiler-emitted operations: taking a value snapshot (copying/dereferencing
// a list value, slicing), starting a list iteration (which snapshots), and
// resizing. Snapshots taken *before* the loop are discharged by the one
// up-front compact. Mid-loop, only the loop body itself runs, so it suffices
// that the body provably contains none of those operations. We establish that
// with a strict whitelist scan: the body may contain only scalar-typed
// declarations/assignments/updates, reads and writes of list elements whose
// items are scalar-typed, arithmetic/comparison operators, if/continue/break/pass,
// and nested loops over integer ranges. Any function call, method call,
// lambda, return (deferred blocks could run arbitrary code), list-typed
// value, iteration over a container, comprehension, or anything else not
// explicitly whitelisted makes the loop ineligible and it compiles exactly as
// before. Shadowing can't smuggle in a different list under a hoisted name:
// list-typed declarations are not whitelisted, and loop variables are always
// integer-typed here (only integer-range iterables are allowed).
// ---------------------------------------------------------------------------
typedef struct {
Table_t *written; // variable name -> Var ast of lists written by index/swap
Table_t *accessed; // variable name -> Var ast of ALL lists indexed (reads + writes)
} cow_scan_t;
static bool cow_expr_ok(env_t *env, ast_t *ast, cow_scan_t *scan);
// Scalar-ish types: values whose copies can't share a list buffer. A tagged
// word that may point at its own heap object still counts, since that object
// is never a list: `Int` (GMP bignum) and `Num` (exact real) both qualify.
static bool cow_type_ok(type_t *t) {
if (t == NULL) return false;
switch (t->tag) {
case IntType:
case BigIntType:
case NumType:
case FloatType:
case BoolType:
case ByteType: return true;
case OptionalType: return cow_type_ok(Match(t, OptionalType)->type);
case StructType: {
for (arg_t *field = Match(t, StructType)->fields; field; field = field->next)
if (!cow_type_ok(field->type)) return false;
return true;
}
default: return false;
}
}
// Non-fatal variable type lookup: the scanner must never `code_err` (it runs
// speculatively and merely declines the optimization on anything unmodeled,
// including discard variables like `_` that have no binding).
static type_t *cow_var_type(env_t *env, ast_t *var_ast) {
if (var_ast->tag != Var) return NULL;
binding_t *binding = get_binding(env, Match(var_ast, Var)->name);
return binding ? binding->type : NULL;
}
// An indexed list element access (`xs[i]`) on a plain variable with
// scalar-typed items. Used for both reads and (in Assign targets) writes;
// records the list in `accessed` so its header can be hoisted into locals.
static bool cow_index_ok(env_t *env, ast_t *ast, cow_scan_t *scan) {
DeclareMatch(index, ast, Index);
if (index->index == NULL) return false; // `ptr[]` dereference takes a snapshot
type_t *indexed_t = cow_var_type(env, index->indexed);
if (indexed_t == NULL) return false;
type_t *container_t = value_type(indexed_t);
if (container_t->tag != ListType) return false; // table reads can insert defaults
if (!cow_type_ok(Match(container_t, ListType)->item_type)) return false;
Table$str_set(scan->accessed, Match(index->indexed, Var)->name, index->indexed);
return true;
}
// Clearing each argument through the scan is what keeps a list from crossing
// into a call: a list-typed variable or element fails cow_type_ok there.
// Checking the argument's own type here as well would reject an untyped
// numeric literal, whose type is `Num` until the call coerces it.
static bool cow_call_args_ok(env_t *env, arg_ast_t *args, cow_scan_t *scan) {
for (arg_ast_t *arg = args; arg; arg = arg->next)
if (!cow_expr_ok(env, arg->value, scan)) return false;
return true;
}
// The `self` of a method call, which is either a scalar-typed value
// (`v.len2()`) or a type's namespace (`Float64.sqrt(x)`, which parses as a
// method call on the type name).
static bool cow_method_self_ok(env_t *env, ast_t *self, cow_scan_t *scan) {
type_t *self_t = cow_var_type(env, self);
if (self_t != NULL && self_t->tag == TypeInfoType) return true;
return cow_expr_ok(env, self, scan) && cow_type_ok(get_type(env, self));
}
static bool cow_expr_ok(env_t *env, ast_t *ast, cow_scan_t *scan) {
if (ast == NULL) return false;
switch (ast->tag) {
case Int:
case Num:
case Bool:
case None: return true;
case Var: return cow_type_ok(cow_var_type(env, ast));
case NonOptional: return cow_expr_ok(env, Match(ast, NonOptional)->value, scan);
case Not: return cow_expr_ok(env, Match(ast, Not)->value, scan);
case Negative: return cow_expr_ok(env, Match(ast, Negative)->value, scan);
case Index: return cow_index_ok(env, ast, scan) && cow_expr_ok(env, Match(ast, Index)->index, scan);
case FunctionCall: {
// Constructor calls of scalar types (e.g. `Int64(1)`) are pure
// conversions: with scalar-only arguments they can't touch any list.
// A call through a plain variable stays disallowed: the variable could
// hold a lambda, and a lambda can capture the list even when its
// signature is all scalars.
DeclareMatch(call, ast, FunctionCall);
type_t *fn_t = cow_var_type(env, call->fn);
if (fn_t == NULL || fn_t->tag != TypeInfoType || !cow_type_ok(Match(fn_t, TypeInfoType)->type)) return false;
return cow_call_args_ok(env, call->args, scan);
}
case MethodCall: {
// A method on a scalar-typed value (`v.len2()`) or in a type's
// namespace (`Float64.sqrt(x)`), taking and returning only scalars.
// This is the same class of call the BINOP_CASES arm below already
// admits: an operator on a struct compiles to a call to the type's own
// metamethod, so `a - b` on a Vec3 already emits `Vec3$minus(...)`.
// Nothing reachable this way can name the loop's list.
DeclareMatch(call, ast, MethodCall);
if (!cow_method_self_ok(env, call->self, scan)) return false;
return cow_type_ok(get_type(env, ast)) && cow_call_args_ok(env, call->args, scan);
}
case FieldAccess: {
// Reading a field of an all-scalar struct reaches no list. A module or
// type namespace on the left is not a scalar struct, so it stops here.
DeclareMatch(access, ast, FieldAccess);
type_t *subject_t = get_type(env, access->fielded);
if (subject_t == NULL || subject_t->tag != StructType || !cow_type_ok(subject_t)) return false;
return cow_expr_ok(env, access->fielded, scan);
}
case RecordLiteral: {
// Building an all-scalar struct out of parts the scan has cleared.
if (!cow_type_ok(get_type(env, ast))) return false;
return cow_call_args_ok(env, Match(ast, RecordLiteral)->args, scan);
}
case BINOP_CASES: {
if (is_update_assignment(ast)) return false; // statements, not expressions
binary_operands_t operands = BINARY_OPERANDS(ast);
return cow_expr_ok(env, operands.lhs, scan) && cow_expr_ok(env, operands.rhs, scan);
}
default: return false;
}
}
static bool cow_stmt_ok(env_t *env, ast_t *ast, cow_scan_t *scan);
static bool cow_block_ok(env_t *env, ast_t *block, cow_scan_t *scan) {
env_t *scope = fresh_scope(env);
if (block->tag == Block) {
for (ast_list_t *stmt = Match(block, Block)->statements; stmt; stmt = stmt->next)
if (!cow_stmt_ok(scope, stmt->ast, scan)) return false;
return true;
}
return cow_stmt_ok(scope, block, scan);
}
// Only integer-range iterables (`a.to(b)` on integers, or a plain integer
// count) are allowed for nested loops: iterating a container takes a snapshot
// of it, which is exactly the refcount bump we must rule out.
static bool cow_iterable_ok(env_t *env, ast_t *iter, cow_scan_t *scan) {
if (iter->tag == MethodCall) {
DeclareMatch(call, iter, MethodCall);
if (!streq(call->name, "to")) return false;
if (!cow_expr_ok(env, call->self, scan) || !is_int_type(get_type(env, call->self))) return false;
for (arg_ast_t *arg = call->args; arg; arg = arg->next)
if (!cow_expr_ok(env, arg->value, scan)) return false;
return true;
}
return cow_expr_ok(env, iter, scan) && is_int_type(get_type(env, iter));
}
static bool cow_for_ok(env_t *env, ast_t *ast, cow_scan_t *scan) {
DeclareMatch(for_, ast, For);
if (for_->iters == NULL || for_->iters->next != NULL) return false; // no lockstep
if (for_->empty != NULL) return false;
if (!cow_iterable_ok(env, for_->iters->ast, scan)) return false;
for (ast_list_t *var = for_->vars; var; var = var->next)
if (var->ast->tag != Var) return false; // no `&x` reference vars
if (for_->at != NULL && for_->at->tag != Var) return false;
env_t *scope = for_scope(env, ast);
return cow_block_ok(scope, for_->body, scan);
}
static bool cow_stmt_ok(env_t *env, ast_t *ast, cow_scan_t *scan) {
switch (ast->tag) {
case Pass:
case Continue:
case Break: return true;
case Block: return cow_block_ok(env, ast, scan);
case Declare: {
DeclareMatch(decl, ast, Declare);
if (!cow_expr_ok(env, decl->value, scan)) return false;
// The value expression passed the scalar whitelist, so its type (and
// thus the declared variable's) is scalar; an explicit annotation
// could only promote it to another scalar type. Discard declarations
// (`_ := ...`) have no binding, so don't query the variable itself.
bind_statement(env, ast); // register the variable for later type lookups
return cow_type_ok(get_type(env, decl->value));
}
case Assign: {
DeclareMatch(assign, ast, Assign);
for (ast_list_t *value = assign->values; value; value = value->next)
if (!cow_expr_ok(env, value->ast, scan)) return false;
for (ast_list_t *target = assign->targets; target; target = target->next) {
if (target->ast->tag == Var) {
if (!cow_type_ok(cow_var_type(env, target->ast))) return false;
} else if (target->ast->tag == Index) {
if (!cow_index_ok(env, target->ast, scan)) return false;
if (!cow_expr_ok(env, Match(target->ast, Index)->index, scan)) return false;
ast_t *indexed = Match(target->ast, Index)->indexed;
Table$str_set(scan->written, Match(indexed, Var)->name, indexed);
} else {
return false;
}
}
return true;
}
case UPDATE_CASES: {
binary_operands_t operands = UPDATE_OPERANDS(ast);
if (!cow_type_ok(cow_var_type(env, operands.lhs))) return false;
return cow_expr_ok(env, operands.rhs, scan);
}
case MethodCall: {
// `xs.swap(i, j)` is the one whitelisted method: it compiles inline
// (List_swap) and behaves exactly like a pair of indexed writes: it
// never snapshots or resizes, and its own CoW guard is elided under a
// hoist just like List_lvalue's.
DeclareMatch(call, ast, MethodCall);
if (!streq(call->name, "swap")) return false;
if (call->self->tag != Var) return false;
type_t *self_t = cow_var_type(env, call->self);
if (self_t == NULL) return false;
type_t *container_t = value_type(self_t);
if (container_t->tag != ListType) return false;
if (!cow_type_ok(Match(container_t, ListType)->item_type)) return false;
for (arg_ast_t *arg = call->args; arg; arg = arg->next)
if (!cow_expr_ok(env, arg->value, scan)) return false;
Table$str_set(scan->written, Match(call->self, Var)->name, call->self);
Table$str_set(scan->accessed, Match(call->self, Var)->name, call->self);
return true;
}
case If: {
DeclareMatch(if_, ast, If);
if (if_->condition == NULL || !cow_expr_ok(env, if_->condition, scan)) return false;
if (!cow_block_ok(env, if_->body, scan)) return false;
if (if_->else_body != NULL && !cow_block_ok(env, if_->else_body, scan)) return false;
return true;
}
case While: {
DeclareMatch(while_, ast, While);
if (while_->condition != NULL && !cow_expr_ok(env, while_->condition, scan)) return false;
return cow_block_ok(env, while_->body, scan);
}
case Repeat: return cow_block_ok(env, Match(ast, Repeat)->body, scan);
case For: return cow_for_ok(env, ast, scan);
default: return false; // calls, lambdas, returns, comprehensions, C code, ...
}
}
public
bool is_cow_hoisted(env_t *env, ast_t *var_ast) {
return var_ast->tag == Var && env->cow_hoisted && Table$str_get(*env->cow_hoisted, Match(var_ast, Var)->name);
}
public
Text_t cow_hoisted_local(ast_t *var_ast, const char *field) {
return Texts("cow$", Match(var_ast, Var)->name, "$", field);
}
// If `loop_ast` (a For/While/Repeat) qualifies for CoW-guard hoisting, return
// a copy of `env` with the written list variables recorded in ->cow_hoisted
// and append the up-front compact-if-shared code for each to `prelude`.
// Otherwise return `env` unchanged with an empty prelude.
static env_t *cow_hoist_env(env_t *env, ast_t *loop_ast, Text_t *prelude) {
cow_scan_t scan = {.written = new (Table_t), .accessed = new (Table_t)};
env_t *scratch = fresh_scope(env);
bool safe = false;
switch (loop_ast->tag) {
case While: {
DeclareMatch(while_, loop_ast, While);
safe = (while_->condition == NULL || cow_expr_ok(scratch, while_->condition, &scan))
&& cow_block_ok(scratch, while_->body, &scan);
break;
}
case Repeat: safe = cow_block_ok(scratch, Match(loop_ast, Repeat)->body, &scan); break;
case For: safe = cow_for_ok(scratch, loop_ast, &scan); break;
default: safe = false; break;
}
if (!safe || Table$length(*scan.accessed) == 0) return env;
env_t *hoisted = new (env_t);
*hoisted = *env;
hoisted->cow_hoisted = new (Table_t, .fallback = env->cow_hoisted);
// Two phases, because different variables can alias the same heap list
// (and thus the same header struct): first compact every *written* list
// if shared, then capture every accessed list's header (data/stride/
// length) into locals. Captures must come after ALL compacts, since a
// compact changes the header and an alias captured before it would go stale.
// Accesses inside the loop compile against these locals, so the C
// compiler can keep the header in registers and strength-reduce the
// stride multiply; going through the list pointer would force a reload on
// every iteration, since element stores could alias the list struct.
Text_t compacts = EMPTY_TEXT, captures = EMPTY_TEXT;
for (int64_t i = 0; i < (int64_t)scan.accessed->entries.length; i++) {
struct {
const char *name;
ast_t *var_ast;
} *entry = scan.accessed->entries.data + scan.accessed->entries.stride * i;
// Already hoisted by an enclosing loop: its locals are in scope.
if (env->cow_hoisted && Table$str_get(*env->cow_hoisted, entry->name)) continue;
ast_t *indexed = entry->var_ast;
type_t *container_t = value_type(get_type(env, indexed));
type_t *item_type = Match(container_t, ListType)->item_type;
Text_t ptr = cow_hoisted_local(indexed, "ptr");
compacts = Texts(compacts, "List_t *", ptr, " = ", compile_to_pointer_depth(env, indexed, 1, false), ";\n");
if (Table$str_get(*scan.written, entry->name))
compacts = Texts(compacts, "if unlikely (", ptr, "->data_refcount > 0) List$compact(", ptr, ", sizeof(",
compile_type(item_type), "));\n");
captures = Texts(captures, "void *", cow_hoisted_local(indexed, "data"), " = ", ptr, "->data;\n", //
"int64_t ", cow_hoisted_local(indexed, "stride"), " = ", ptr, "->stride;\n", //
"int64_t ", cow_hoisted_local(indexed, "length"), " = ", ptr, "->length;\n");
Table$str_set(hoisted->cow_hoisted, entry->name, entry->var_ast);
}
if (Table$length(*hoisted->cow_hoisted) == 0) return env;
*prelude = Texts("// Hoisted copy-on-write guards + list headers:\n", compacts, captures);
return hoisted;
}
// The TypeInfo companions a `--debug` build gives a `for` loop's own
// variables. `vars` is the loop's value variables plus its `at` counter;
// their types are looked up through the loop's scope rather than derived
// here, so each kind of iteration (list, table, integer range, iterator
// function, `for &x` by reference) gets whatever type it actually bound.
static Text_t compile_debug_loop_typeinfos(env_t *env, env_t *body_scope, ast_list_t *vars) {
if (!env->do_debugging) return EMPTY_TEXT;
Text_t code = EMPTY_TEXT;
for (ast_list_t *v = vars; v; v = v->next) {
ast_t *var = v->ast->tag == StackReference ? Match(v->ast, StackReference)->value : v->ast;
if (var->tag != Var) continue;
const char *name = Match(var, Var)->name;
binding_t *b = get_binding(body_scope, name);
// Only plain locals: a binding whose code is anything but the bare
// variable is not a variable a debugger can look up by that name.
if (b && Text$equal_values(b->code, Texts("_$", name)))
code = Texts(code, compile_debug_typeinfo(env, name, b->type));
}
return code;
}
// Compile `for [i,] &x in xs`, in-place mutable iteration over a list.
//
// `&x` is a live pointer into the list's buffer, so element updates are plain
// in-place stores with no per-element bounds checks or copy-on-write guards.
// The list itself stays usable inside the body (reads and indexed writes see
// live data through the normal checked paths); the only things it can't do
// while the loop runs are the two that would invalidate the raw pointer or
// break copy-on-write, and both are runtime failures rather than silent bugs:
// 1. If a snapshot shares the buffer at loop entry (data_refcount > 0), we
// compact once up front, the same copy CoW would charge on first write.
// 2. List_ref_iter_guard (stdlib/list.h) re-checks at the top of each
// iteration AND once after the loop (including `stop` exits), so a
// resize or copy in any iteration, even the last, fails before the
// loop's results can be used. A mid-loop snapshot can't be *protected*
// without a per-write CoW check (it would see the remainder of the
// current iteration's writes), so it fails rather than silently
// corrupting.
// `&x` itself is a non-escaping stack reference, so it can't outlive the loop.
static Text_t compile_for_reference_loop(env_t *env, ast_t *ast, Text_t naked_body, Text_t stop, type_t *item_t,
Text_t index, ast_t *value_var) {
DeclareMatch(for_, ast, For);
ast_t *iter = for_->iters->ast;
const char *name = Match(Match(value_var, StackReference)->value, Var)->name;
Text_t value = Texts("_$", name);
Text_t item_type_code = compile_type(item_t);
Text_t guard = Texts("List_ref_iter_guard(ptr, data0, n0, stride0, ", quoted_str(ast->file->filename), ", ",
(int64_t)(ast->start - ast->file->text), ", ", (int64_t)(ast->end - ast->file->text), ");\n");
Text_t loop = Texts("for (int64_t i = 1; i <= n0; ++i) {\n", guard, item_type_code, " *", value, " = (",
item_type_code, " *)(data0 + (i-1)*stride0);\n",
index.length > 0 ? Texts("Int64_t ", index, " = i;\n") : EMPTY_TEXT, naked_body, "}");
if (for_->empty) loop = Texts("if (n0 > 0) {\n", loop, "\n} else ", compile_statement(env, for_->empty));
return Texts("{ // for &", name,
" in ...\n"
"List_t *ptr = ",
compile_to_pointer_depth(env, iter, 1, false),
";\n"
"if unlikely (ptr->data_refcount > 0) List$compact(ptr, sizeof(",
item_type_code,
"));\n"
"void *data0 = ptr->data;\n"
"int64_t n0 = ptr->length;\n"
"int64_t stride0 = ptr->stride;\n",
loop, stop,
// Post-loop check (runs on normal completion and on `stop`,
// which lands on the label above): catches a resize/copy made
// during the final iteration, which the per-iteration guard
// (top-of-body) would otherwise never see.
"\nif (n0 > 0) ", guard, "}\n");
}
// Build the C call for one step of an iterator function or closure, e.g.
// `next(&x, &y)` or `((ret(*)(...))next.fn)(&x, &y, next.userdata)`:
static Text_t compile_iterator_call(type_t *iter_value_t, Text_t next_fn, Text_t args_joined) {
if (iter_value_t->tag == ClosureType) {
type_t *fn_t = Match(iter_value_t, ClosureType)->fn;
arg_t *closure_fn_args = NULL;
for (arg_t *arg = Match(fn_t, FunctionType)->args; arg; arg = arg->next)
closure_fn_args = new (arg_t, .name = arg->name, .type = arg->type, .default_val = arg->default_val,
.next = closure_fn_args);
closure_fn_args = new (arg_t, .name = "userdata", .type = Type(PointerType, .pointed = Type(MemoryType)),
.next = closure_fn_args);
REVERSE_LIST(closure_fn_args);
Text_t fn_type_code =
compile_type(Type(FunctionType, .args = closure_fn_args, .ret = Match(fn_t, FunctionType)->ret));
return Texts("((", fn_type_code, ")", next_fn, ".fn)(", args_joined, args_joined.length > 0 ? ", " : "",
next_fn, ".userdata)");
} else {
return Texts(next_fn, "(", args_joined, ")");
}
}
// `xs.pairs()` and `t.entries()` are multi-value iterators, but when they
// appear directly in for-position we compile them as inline index loops --
// no closure is allocated and no indirect call happens per iteration. The
// result is identical to iterating the closure; this is a pure performance
// special-case. Returns true (having appended to setup/advance/cleanup and
// advanced *var_p) if `iter_ast` is such a call, false otherwise.
static bool is_combinatoric_call(env_t *env, ast_t *iter_ast) {
if (iter_ast->tag != MethodCall) return false;
DeclareMatch(call, iter_ast, MethodCall);
if (call->args) return false; // extra args -> let the normal path report the error
type_t *self_t = value_type(get_type(env, call->self));
if (streq(call->name, "pairs")) return self_t->tag == ListType;
if (streq(call->name, "entries"))
return self_t->tag == TableType && Match(self_t, TableType)->value_type != PRESENT_TYPE;
return false;
}
static void compile_combinatoric_fragment(env_t *env, env_t *body_scope, ast_t *iter_ast, Text_t temp,
ast_list_t **var_p, Text_t *setup, Text_t *advance, Text_t *cleanup) {
DeclareMatch(call, iter_ast, MethodCall);
ast_t *self = call->self;
type_t *raw_self = get_type(env, self);
type_t *self_t = value_type(raw_self);
if (streq(call->name, "pairs")) {
// Each unordered pair of distinct elements once (i < j). An incref'd
// snapshot gives the same "mutations aren't seen" behavior as the
// closure: mutating the list mid-loop copies its buffer first.
type_t *item_t = Match(self_t, ListType)->item_type;
Text_t list = Texts(temp, "$list"), i = Texts(temp, "$i"), j = Texts(temp, "$j");
if (raw_self->tag == PointerType) {
Text_t ptr = Texts(temp, "$ptr");
*setup = Texts(*setup, "List_t *", ptr, " = ", compile_to_pointer_depth(env, self, 1, false),
";\nLIST_INCREF(*", ptr, ");\nList_t ", list, " = *", ptr, ";\n");
*cleanup = Texts(*cleanup, "LIST_DECREF(*", ptr, ");\n");
} else {
*setup = Texts(*setup, "List_t ", list, " = ", compile_to_pointer_depth(env, self, 0, false), ";\n");
}
*setup = Texts(*setup, "int64_t ", i, " = 1, ", j, " = 1;\n");
*advance = Texts(*advance, j, " += 1;\nif (", j, " > (int64_t)", list, ".length) { ", i, " += 1; ", j, " = ", i,
" + 1; }\nif (", j, " > (int64_t)", list, ".length) break;\n");
ast_t *va = (*var_p)->ast;
*var_p = (*var_p)->next;
if (!streq(Match(va, Var)->name, "_"))
*advance = Texts(*advance, compile_declaration(item_t, compile(body_scope, va)), " = *(",
compile_type(item_t), "*)(", list, ".data + (", i, "-1)*", list, ".stride);\n");
ast_t *vb = (*var_p)->ast;
*var_p = (*var_p)->next;
if (!streq(Match(vb, Var)->name, "_"))
*advance = Texts(*advance, compile_declaration(item_t, compile(body_scope, vb)), " = *(",
compile_type(item_t), "*)(", list, ".data + (", j, "-1)*", list, ".stride);\n");
} else { // "entries"
type_t *key_t = Match(self_t, TableType)->key_type;
type_t *value_t = Match(self_t, TableType)->value_type;
Text_t list = Texts(temp, "$entries"), idx = Texts(temp, "$i");
if (raw_self->tag == PointerType) {
Text_t ptr = Texts(temp, "$ptr");
*setup = Texts(*setup, "Table_t *", ptr, " = ", compile_to_pointer_depth(env, self, 1, false),
";\nLIST_INCREF(", ptr, "->entries);\nList_t ", list, " = ", ptr, "->entries;\n");
*cleanup = Texts(*cleanup, "LIST_DECREF(", ptr, "->entries);\n");
} else {
*setup =
Texts(*setup, "List_t ", list, " = (", compile_to_pointer_depth(env, self, 0, false), ").entries;\n");
}
*setup = Texts(*setup, "int64_t ", idx, " = 0;\n");
*advance = Texts(*advance, "if (", idx, " >= ", list, ".length) break;\n", idx, " += 1;\n");
ast_t *key_var = (*var_p)->ast;
*var_p = (*var_p)->next;
if (!streq(Match(key_var, Var)->name, "_"))
*advance = Texts(*advance, compile_declaration(key_t, compile(body_scope, key_var)), " = *(",
compile_type(key_t), "*)(", list, ".data + (", idx, "-1)*", list, ".stride);\n");
ast_t *value_var = (*var_p)->ast;
*var_p = (*var_p)->next;
if (!streq(Match(value_var, Var)->name, "_")) {
Text_t value_offset = Texts("offsetof(struct { ", compile_declaration(key_t, Text("k")), "; ",
compile_declaration(value_t, Text("v")), "; }, v)");
*advance = Texts(*advance, compile_declaration(value_t, compile(body_scope, value_var)), " = *(",
compile_type(value_t), "*)(", list, ".data + (", idx, "-1)*", list, ".stride + ",
value_offset, ");\n");
}
}
}
// Compile `for x, y in xs, ys`, lockstep iteration over several iterables.
//
// Each iterable contributes a setup step (run once, leftmost first, so the
// iterables are evaluated in source order) and an advance-or-break step at the
// top of a single shared loop, so the loop ends as soon as ANY iterable runs
// out. Each iterable's yielded values bind to its slice of the loop variables
// (`_` discards one); arity was already checked in for_scope(). The `else`
// block (if any) runs when the body never ran at all.
//
// A single-iterable `for a, b in xs.pairs()` also routes here (so pairs() and
// entries() get their inline codegen); it's just a lockstep loop of one.
static Text_t compile_lockstep_loop(env_t *env, ast_t *ast, env_t *body_scope, Text_t naked_body, Text_t stop) {
DeclareMatch(for_, ast, For);
Text_t setup = EMPTY_TEXT, advance = EMPTY_TEXT, cleanup = EMPTY_TEXT;
ast_list_t *var = for_->vars;
int64_t k = 0;
for (ast_list_t *it = for_->iters; it; it = it->next, k += 1) {
Text_t temp = Texts("lockstep", k);
if (is_combinatoric_call(env, it->ast)) {
compile_combinatoric_fragment(env, body_scope, it->ast, temp, &var, &setup, &advance, &cleanup);
continue;
}
type_t *raw_t = get_type(env, it->ast);
type_t *iter_value_t = value_type(raw_t);
switch (iter_value_t->tag) {
case ListType: {
type_t *item_t = Match(iter_value_t, ListType)->item_type;
Text_t list = Texts(temp, "$list"), idx = Texts(temp, "$i");
if (raw_t->tag == PointerType) {
Text_t ptr = Texts(temp, "$ptr");
setup = Texts(setup, "List_t *", ptr, " = ", compile_to_pointer_depth(env, it->ast, 1, false),
";\nLIST_INCREF(*", ptr, ");\nList_t ", list, " = *", ptr, ";\n");
cleanup = Texts(cleanup, "LIST_DECREF(*", ptr, ");\n");
} else {
setup = Texts(setup, "List_t ", list, " = ", compile_to_pointer_depth(env, it->ast, 0, false), ";\n");
}
setup = Texts(setup, "int64_t ", idx, " = 0;\n");
advance = Texts(advance, "if (", idx, " >= ", list, ".length) break;\n", idx, " += 1;\n");
ast_t *v = var->ast;
var = var->next;
if (!streq(Match(v, Var)->name, "_"))
advance = Texts(advance, compile_declaration(item_t, compile(body_scope, v)), " = *(",
compile_type(item_t), "*)(", list, ".data + (", idx, "-1)*", list, ".stride);\n");
break;
}
case TableType: {
// Only sets ({T}) reach here (direct table iteration is a compile
// error in iteration_slots()); iterate the entry keys:
type_t *item_t = Match(iter_value_t, TableType)->key_type;
Text_t list = Texts(temp, "$entries"), idx = Texts(temp, "$i");
if (raw_t->tag == PointerType) {
Text_t ptr = Texts(temp, "$ptr");
setup = Texts(setup, "Table_t *", ptr, " = ", compile_to_pointer_depth(env, it->ast, 1, false),
";\nLIST_INCREF(", ptr, "->entries);\nList_t ", list, " = ", ptr, "->entries;\n");
cleanup = Texts(cleanup, "LIST_DECREF(", ptr, "->entries);\n");
} else {
setup = Texts(setup, "List_t ", list, " = (", compile_to_pointer_depth(env, it->ast, 0, false),
").entries;\n");
}
setup = Texts(setup, "int64_t ", idx, " = 0;\n");
advance = Texts(advance, "if (", idx, " >= ", list, ".length) break;\n", idx, " += 1;\n");
ast_t *v = var->ast;
var = var->next;
if (!streq(Match(v, Var)->name, "_"))
advance = Texts(advance, compile_declaration(item_t, compile(body_scope, v)), " = *(",
compile_type(item_t), "*)(", list, ".data + (", idx, "-1)*", list, ".stride);\n");
break;
}
case BigIntType: {
Text_t cur = Texts(temp, "$cur"), max = Texts(temp, "$max");
setup = Texts(setup, "Int_t ", cur, " = I_small(1), ", max, " = ",
compile_to_pointer_depth(env, it->ast, 0, false), ";\n");
advance = Texts(advance, "if (Int$compare_value(", cur, ", ", max, ") > 0) break;\n");
ast_t *v = var->ast;
var = var->next;
if (!streq(Match(v, Var)->name, "_"))
advance = Texts(advance, "Int_t ", compile(body_scope, v), " = ", cur, ";\n");
advance = Texts(advance, cur, " = Int$plus(", cur, ", I_small(1));\n");
break;
}
case IntType: {
Text_t type_code = compile_type(iter_value_t);
Text_t cur = Texts(temp, "$cur"), max = Texts(temp, "$max");
setup = Texts(setup, "Int64_t ", cur, " = 0, ", max, " = (Int64_t)(",
compile_to_pointer_depth(env, it->ast, 0, false), ");\n");
advance = Texts(advance, "if (", cur, " >= ", max, ") break;\n", cur, " += 1;\n");
ast_t *v = var->ast;
var = var->next;
if (!streq(Match(v, Var)->name, "_"))
advance = Texts(advance, type_code, " ", compile(body_scope, v), " = (", type_code, ")", cur, ";\n");
break;
}
case TextType: {
Text_t state = Texts(temp, "$state"), idx = Texts(temp, "$i");
setup = Texts(setup, "TextIter_t ", state, " = NEW_TEXT_ITER_STATE(",
compile_to_pointer_depth(env, it->ast, 0, false), ");\nint64_t ", idx, " = 0;\n");
advance = Texts(advance, "if (", idx, " >= (int64_t)", state, ".stack[0].text.length) break;\n");
ast_t *v = var->ast;
var = var->next;
if (!streq(Match(v, Var)->name, "_"))
advance = Texts(advance, "Text_t ", compile(body_scope, v), " = Text$from_grapheme(",
"Text$get_grapheme_fast(&", state, ", ", idx, "));\n");
advance = Texts(advance, idx, " += 1;\n");
break;
}
case FunctionType:
case ClosureType: {
Text_t next_fn;
if (is_idempotent(it->ast)) {
next_fn = compile_to_pointer_depth(env, it->ast, 0, false);
} else {
next_fn = Texts(temp, "$next");
setup = Texts(setup, compile_declaration(iter_value_t, next_fn), " = ",
compile_to_pointer_depth(env, it->ast, 0, false), ";\n");
}
arg_t *yield_args = iterator_yield_args(iter_value_t);
if (yield_args) {
// Multi-value iterator protocol: the loop variables are the
// `&` out-slots (declared outside the loop, written each call):
Text_t args_joined = EMPTY_TEXT;
int64_t pos = 1;
for (arg_t *arg = yield_args; arg; arg = arg->next, pos += 1) {
type_t *val_t = Match(arg->type, PointerType)->pointed;
ast_t *v = var->ast;
var = var->next;
Text_t name = streq(Match(v, Var)->name, "_") ? Texts(temp, "$yield", pos) : compile(body_scope, v);
setup = Texts(setup, compile_declaration(val_t, name), ";\n");
args_joined = pos == 1 ? Texts("&", name) : Texts(args_joined, ", &", name);
}
advance =
Texts(advance, "if (!", compile_iterator_call(iter_value_t, next_fn, args_joined), ") break;\n");
} else {
__typeof(iter_value_t->__data.FunctionType) *fn =
iter_value_t->tag == ClosureType ? Match(Match(iter_value_t, ClosureType)->fn, FunctionType)
: Match(iter_value_t, FunctionType);
Text_t call = compile_iterator_call(iter_value_t, next_fn, EMPTY_TEXT);
ast_t *v = var->ast;
var = var->next;
bool discard = streq(Match(v, Var)->name, "_");
if (fn->ret->tag == OptionalType) {
Text_t cur = Texts(temp, "$cur");
setup = Texts(setup, compile_declaration(fn->ret, cur), ";\n");
advance = Texts(advance, cur, " = ", call, ";\nif (", check_none(fn->ret, cur), ") break;\n");
if (!discard)
advance = Texts(advance,
compile_declaration(Match(fn->ret, OptionalType)->type, compile(body_scope, v)),
" = ", optional_into_nonnone(fn->ret, cur), ";\n");
} else if (!discard) {
advance = Texts(advance, compile_declaration(fn->ret, compile(body_scope, v)), " = ", call, ";\n");
} else {
advance = Texts(advance, call, ";\n");
}
}
break;
}
default: code_err(it->ast, "Iteration is not implemented for type: ", type_to_text(raw_t));
}
}
Text_t at_decl = EMPTY_TEXT, at_step = EMPTY_TEXT;
if (for_->at) {
Text_t index = compile(body_scope, for_->at);
at_decl = index_counter_decl(index);
at_step = index_counter_step(index);
}
Text_t code = Texts("{ // lockstep iteration\n", setup, at_decl);
if (for_->empty) code = Texts(code, "Bool_t lockstep$ran = no;\n");
code = Texts(code, "for (;;) {\n", advance);
if (for_->empty) code = Texts(code, "lockstep$ran = yes;\n");
code = Texts(code, at_step, naked_body, "}\n", stop, "\n", cleanup);
if (for_->empty) code = Texts(code, "if (!lockstep$ran) ", compile_statement(env, for_->empty), "\n");
return Texts(code, "}\n");
}
static Text_t compile_for_loop_impl(env_t *env, ast_t *ast) {
DeclareMatch(for_, ast, For);
ast_t *iter = for_->iters->ast;
// If we're iterating over a comprehension, that's actually just doing
// one loop, we don't need to compile the comprehension as a list
// comprehension. This is a common case for reducers like `(+: i*2 for i
// in 5)` or `(and) x.is_good() for x in xs`
if (!for_->iters->next && iter->tag == Comprehension) {
DeclareMatch(comp, iter, Comprehension);
ast_t *body = for_->body;
if (for_->at)
code_err(for_->at, "An `at` counter isn't supported when iterating over a comprehension. "
"Put the `at` inside the comprehension instead.");
if (for_->vars) {
if (for_->vars->ast->tag == StackReference)
code_err(for_->vars->ast, "You can't iterate by reference over a comprehension");
if (for_->vars->next) code_err(for_->vars->next->ast, "This is too many variables for iteration");
body = WrapAST(
ast, Block,
.statements =
new (ast_list_t, .ast = WrapAST(ast, Declare, .var = for_->vars->ast, .value = comp->expr),
.next = body->tag == Block ? Match(body, Block)->statements : new (ast_list_t, .ast = body)));
}
if (comp->filter) body = WrapAST(for_->body, If, .condition = comp->filter, .body = body);
ast_t *loop = WrapAST(ast, For, .vars = comp->vars, .at = comp->at, .iters = comp->iters, .body = body);
return compile_statement(env, loop);
}
env_t *body_scope = for_scope(env, ast);
// `continue`/`break` can target any loop variable by name, including the `at` counter:
ast_list_t *ctx_vars = for_->at ? new (ast_list_t, .ast = for_->at, .next = for_->vars) : for_->vars;
// The loop's own variables get their TypeInfo companions at the top of the
// body. Every branch below declares the loop variables immediately before
// it plants `naked_body`, so putting the companions at the front of the
// body puts them in the same block as the variables they describe, which
// is what a debugger stopped inside the loop looks in. This has to happen
// before the body is compiled: compiling it binds the body's own
// declarations into body_scope too, and those get their companions from
// the Declare case instead.
Text_t loop_var_typeinfos = compile_debug_loop_typeinfos(env, body_scope, ctx_vars);
loop_ctx_t loop_ctx = (loop_ctx_t){
.loop_name = "for",
.loop_vars = ctx_vars,
.deferred = body_scope->deferred,
.next = body_scope->loop_ctx,
};
body_scope->loop_ctx = &loop_ctx;
// Naked means no enclosing braces:
Text_t naked_body = Texts(loop_var_typeinfos, compile_inline_block(body_scope, for_->body));
if (loop_ctx.skip_label.length > 0) naked_body = Texts(naked_body, "\n", loop_ctx.skip_label, ": continue;");
Text_t stop = loop_ctx.stop_label.length > 0 ? Texts("\n", loop_ctx.stop_label, ":;") : EMPTY_TEXT;
// Lockstep loops, and single `for a, b in xs.pairs()` / `for k, v in
// t.entries()` loops, compile to inline index loops (no closure, no
// indirect calls) through the same fragment machinery:
if (for_->iters->next || is_combinatoric_call(env, iter))
return compile_lockstep_loop(env, ast, body_scope, naked_body, stop);
// Special case for improving performance for numeric iteration:
if (iter->tag == MethodCall && streq(Match(iter, MethodCall)->name, "to")
&& is_int_type(get_type(env, Match(iter, MethodCall)->self))) {
// TODO: support other integer types
arg_ast_t *args = Match(iter, MethodCall)->args;
if (!args) code_err(iter, "to() needs at least one argument");
type_t *int_type = get_type(env, Match(iter, MethodCall)->self);
type_t *step_type = int_type->tag == ByteType ? Type(IntType, .bits = TYPE_IBITS8) : int_type;
Text_t last = EMPTY_TEXT, step = EMPTY_TEXT, optional_step = EMPTY_TEXT;
if (!args->name || streq(args->name, "last")) {
last = compile_to_type(env, args->value, int_type);
if (args->next) {
if (args->next->name && !streq(args->next->name, "step"))
code_err(args->next->value, "Invalid argument name: ", args->next->name);
if (get_type(env, args->next->value)->tag == OptionalType)
optional_step = compile_to_type(env, args->next->value, Type(OptionalType, step_type));
else step = compile_to_type(env, args->next->value, step_type);
}
} else if (streq(args->name, "step")) {
if (get_type(env, args->value)->tag == OptionalType)
optional_step = compile_to_type(env, args->value, Type(OptionalType, step_type));
else step = compile_to_type(env, args->value, step_type);
if (args->next) {
if (args->next->name && !streq(args->next->name, "last"))
code_err(args->next->value, "Invalid argument name: ", args->next->name);
last = compile_to_type(env, args->next->value, int_type);
}
}
if (last.length == 0) code_err(iter, "No `last` argument was given");
Text_t type_code = compile_type(int_type);
// `for i, x in a.to(b)`: `i` is an Int64 iteration counter (1, 2, 3, ...)
// and `x` is the range value, typed like the range's endpoints.
ast_t *index_var = for_->at;
ast_t *value_var = single_loop_var(for_->vars);
Text_t index = index_var ? compile(body_scope, index_var) : EMPTY_TEXT;
Text_t value = value_var ? compile(body_scope, value_var) : Text("i");
Text_t counter_decl = index_counter_decl(index);
Text_t index_decl = index_counter_step(index);
Text_t open = index.length > 0 ? Text("{\n") : EMPTY_TEXT;
Text_t close = index.length > 0 ? Text("\n}\n") : EMPTY_TEXT;
if (int_type->tag == BigIntType) {
if (optional_step.length > 0)
step = Texts("({ OptionalInt_t maybe_step = ", optional_step,
"; maybe_step->small == 0 ? "
"(Int$compare_value(last, first) >= 0 "
"? I_small(1) : I_small(-1)) : (Int_t)maybe_step; "
"})");
else if (step.length == 0)
step = Text("Int$compare_value(last, first) >= 0 ? "
"I_small(1) : I_small(-1)");
return Texts(open, counter_decl, "for (", type_code,
" first = ", compile(env, Match(iter, MethodCall)->self), ", ", value,
" = first, last = ", last, ", step = ", step,
"; "
"Int$compare_value(",
value, ", last) != Int$compare_value(step, I_small(0)); ", value, " = Int$plus(", value,
", step)) {\n", index_decl, "\t", naked_body, "}", stop, close);
} else {
if (optional_step.length > 0)
step = Texts("({ ", compile_type(Type(OptionalType, step_type)), " maybe_step = ", optional_step,
"; "
"maybe_step.is_none ? (",
type_code, ")(last >= first ? 1 : -1) : maybe_step.value; })");
else if (step.length == 0) step = Texts("(", type_code, ")(last >= first ? 1 : -1)");
return Texts(open, counter_decl, "for (", type_code,
" first = ", compile(env, Match(iter, MethodCall)->self), ", ", value,
" = first, last = ", last, ", step = ", step, "; (", compile_type(step_type), ")step > 0 ? ",
value, " <= last : ", value, " >= last; ", value, " += step) {\n", index_decl, "\t",
naked_body, "}", stop, close);
}
} else if (iter->tag == MethodCall && streq(Match(iter, MethodCall)->name, "onward")
&& get_type(env, Match(iter, MethodCall)->self)->tag == BigIntType) {
// Special case for Int.onward()
arg_ast_t *args = Match(iter, MethodCall)->args;
arg_t *arg_spec =
new (arg_t, .name = "step", .type = INT_TYPE, .default_val = FakeAST(Int, .i = I_small(1)), .next = NULL);
Text_t step = compile_arguments(env, iter, arg_spec, args);
ast_t *index_var = for_->at;
ast_t *value_var = single_loop_var(for_->vars);
Text_t index = index_var ? compile(body_scope, index_var) : EMPTY_TEXT;
Text_t value = value_var ? compile(body_scope, value_var) : Text("i");
Text_t open = index.length > 0 ? Text("{\n") : EMPTY_TEXT;
Text_t close = index.length > 0 ? Text("\n}\n") : EMPTY_TEXT;
return Texts(open, index_counter_decl(index), "for (Int_t ", value, " = ",
compile(env, Match(iter, MethodCall)->self), ", ", "step = ", step, "; ; ", value, " = Int$plus(",
value, ", step)) {\n", index_counter_step(index), "\t", naked_body, "}", stop, close);
}
type_t *iter_t = get_type(env, iter);
type_t *iter_value_t = value_type(iter_t);
switch (iter_value_t->tag) {
case ListType: {
type_t *item_t = Match(iter_value_t, ListType)->item_type;
ast_t *index_var = for_->at;
ast_t *value_var = single_loop_var(for_->vars);
Text_t index = index_var ? compile(body_scope, index_var) : EMPTY_TEXT;
Text_t value = EMPTY_TEXT;
if (value_var && value_var->tag == StackReference)
return compile_for_reference_loop(env, ast, naked_body, stop, item_t, index, value_var);
if (value_var) value = compile(body_scope, value_var);
Text_t loop = EMPTY_TEXT;
loop = Texts(loop, "for (int64_t i = 1; i <= iterating.length; ++i)");
if (index.length > 0) naked_body = Texts("Int64_t ", index, " = i;\n", naked_body);
if (value.length > 0) {
loop = Texts(loop, "{\n", compile_declaration(item_t, value), " = *(", compile_type(item_t),
"*)(iterating.data + (i-1)*iterating.stride);\n", naked_body, "\n}");
} else {
loop = Texts(loop, "{\n", naked_body, "\n}");
}
if (for_->empty)
loop = Texts("if (iterating.length > 0) {\n", loop, "\n} else ", compile_statement(env, for_->empty));
if (iter_t->tag == PointerType) {
loop = Texts("{\n"
"List_t *ptr = ",
compile_to_pointer_depth(env, iter, 1, false),
";\n"
"\nLIST_INCREF(*ptr);\n"
"List_t iterating = *ptr;\n",
loop, stop,
"\nLIST_DECREF(*ptr);\n"
"}\n");
} else {
loop = Texts("{\n"
"List_t iterating = ",
compile_to_pointer_depth(env, iter, 0, false), ";\n", loop, stop, "}\n");
}
return loop;
}
case TableType: {
// Only sets ({T}) reach here (direct table iteration is a compile
// error in for_scope()); iterate the entry keys:
Text_t loop = Text("for (int64_t i = 0; i < (int64_t)iterating.length; ++i) {\n");
if (for_->at) loop = Texts(loop, "Int64_t ", compile(body_scope, for_->at), " = i + 1;\n");
ast_t *value_var = single_loop_var(for_->vars);
if (value_var) {
Text_t item = compile(body_scope, value_var);
type_t *item_t = Match(iter_value_t, TableType)->key_type;
loop = Texts(loop, compile_declaration(item_t, item), " = *(", compile_type(item_t), "*)(",
"iterating.data + i*iterating.stride);\n");
}
loop = Texts(loop, naked_body, "\n}");
if (for_->empty) {
loop = Texts("if (iterating.length > 0) {\n", loop, "\n} else ", compile_statement(env, for_->empty));
}
// NOTE: the `stop` label goes before the DECREF (like the ListType
// case) so that stopping out of the loop still releases the refcount.
if (iter_t->tag == PointerType) {
loop = Texts("{\n", "Table_t *t = ", compile_to_pointer_depth(env, iter, 1, false),
";\n"
"LIST_INCREF(t->entries);\n"
"List_t iterating = t->entries;\n",
loop, stop,
"\nLIST_DECREF(t->entries);\n"
"}\n");
} else {
loop = Texts("{\n", "List_t iterating = (", compile_to_pointer_depth(env, iter, 0, false), ").entries;\n",
loop, stop, "}\n");
}
return loop;
}
case BigIntType: {
// `for x in n` counts with the count's own type (`Int`); the optional
// index form `for i, x in n` adds an Int64 index. The index is just a
// counter starting at 1, and it can't plausibly overflow within any
// physically executable loop, so it needs no relation to the bound.
ast_t *index_var = for_->at;
ast_t *value_var = single_loop_var(for_->vars);
Text_t index = index_var ? compile(body_scope, index_var) : EMPTY_TEXT;
Text_t value = value_var ? compile(body_scope, value_var) : EMPTY_TEXT;
Text_t n;
if (iter->tag == Int) {
mpz_t i;
mpz_init_int(i, Match(iter, Int)->i);
if (mpz_cmpabs_ui(i, BIGGEST_SMALL_INT) <= 0) n = Text$from_str(mpz_get_str(NULL, 10, i));
else goto big_n;
if (for_->empty && mpz_cmp_si(i, 0) <= 0) {
return compile_statement(env, for_->empty);
} else {
return Texts("for (int64_t i = 1; i <= ", n, "; ++i) {\n",
index.length > 0 ? Texts("\tInt64_t ", index, " = i;\n") : EMPTY_TEXT,
value.length > 0 ? Texts("\tInt_t ", value, " = I_small(i);\n") : EMPTY_TEXT, "\t",
naked_body, "}\n", stop, "\n");
}
}
big_n:
n = compile_to_pointer_depth(env, iter, 0, false);
Text_t i = value.length > 0 ? value : Text("i");
Text_t n_var = value.length > 0 ? Texts("max", i) : Text("n");
Text_t counter_decl = index_counter_decl(index);
Text_t index_decl = index_counter_step(index);
if (for_->empty) {
return Texts("{\n", counter_decl, "Int_t ", n_var, " = ", n,
";\n"
"if (Int$compare_value(",
n_var,
", I(0)) > 0) {\n"
"for (Int_t ",
i, " = I(1); Int$compare_value(", i, ", ", n_var, ") <= 0; ", i, " = Int$plus(", i,
", I(1))) {\n", index_decl, "\t", naked_body,
"}\n"
"} else ",
compile_statement(env, for_->empty), stop,
"\n"
"}\n");
} else {
Text_t open = index.length > 0 ? Text("{\n") : EMPTY_TEXT;
Text_t close = index.length > 0 ? Text("\n}\n") : EMPTY_TEXT;
return Texts(open, counter_decl, "for (Int_t ", i, " = I(1), ", n_var, " = ", n, "; Int$compare_value(", i,
", ", n_var, ") <= 0; ", i, " = Int$plus(", i, ", I(1))) {\n", index_decl, "\t", naked_body,
"}\n", stop, close);
}
}
case IntType: {
// Native-int counts (`for [i,] x in n` where n is Int64/Int32/...): a
// native loop whose value variable has the count's own type and whose
// optional index is an Int64. The internal counter is Int64 so that
// counting to a smaller type's maximum can't overflow.
ast_t *index_var = for_->at;
ast_t *value_var = single_loop_var(for_->vars);
Text_t index = index_var ? compile(body_scope, index_var) : EMPTY_TEXT;
Text_t value = value_var ? compile(body_scope, value_var) : EMPTY_TEXT;
Text_t type_code = compile_type(iter_value_t);
Text_t n = compile_to_pointer_depth(env, iter, 0, false);
Text_t decls = Texts(index.length > 0 ? Texts("Int64_t ", index, " = i$;\n") : EMPTY_TEXT,
value.length > 0 ? Texts(type_code, " ", value, " = (", type_code, ")i$;\n") : EMPTY_TEXT);
Text_t n_var = value.length > 0 ? Texts("max", value) : Text("n");
if (for_->empty) {
return Texts("{\n"
"Int64_t ",
n_var, " = (Int64_t)(", n,
");\n"
"if (",
n_var,
" > 0) {\n"
"for (Int64_t i$ = 1; i$ <= ",
n_var, "; ++i$) {\n", decls, "\t", naked_body,
"}\n"
"} else ",
compile_statement(env, for_->empty), stop,
"\n"
"}\n");
} else {
return Texts("for (Int64_t i$ = 1, ", n_var, " = (Int64_t)(", n, "); i$ <= ", n_var, "; ++i$) {\n", decls,
"\t", naked_body, "}\n", stop, "\n");
}
}
case FunctionType:
case ClosureType: {
// Iterator function. `for x at i in iterfn` gives an Int64 iteration
// counter (1, 2, 3, ...) alongside each yielded value.
ast_t *index_var = for_->at;
Text_t code = Text("{\n");
if (index_var) {
Text_t index = compile(body_scope, index_var);
code = Texts(code, index_counter_decl(index));
naked_body = Texts(index_counter_step(index), naked_body);
}
Text_t next_fn;
if (is_idempotent(iter)) {
next_fn = compile_to_pointer_depth(env, iter, 0, false);
} else {
code = Texts(code, compile_declaration(iter_value_t, Text("next")), " = ",
compile_to_pointer_depth(env, iter, 0, false), ";\n");
next_fn = Text("next");
}
__typeof(iter_value_t->__data.FunctionType) *fn =
iter_value_t->tag == ClosureType ? Match(Match(iter_value_t, ClosureType)->fn, FunctionType)
: Match(iter_value_t, FunctionType);
// Multi-value iterator protocol: every argument is a `&` out-parameter
// and the return is a Bool saying whether values were produced. The
// loop variables themselves are the out-slots: each call writes the
// next values directly into them, no tuple or wrapper value involved.
arg_t *yield_args = iterator_yield_args(iter_value_t);
if (yield_args) {
Text_t args_joined = EMPTY_TEXT;
int64_t pos = 1;
ast_list_t *v = for_->vars;
for (arg_t *arg = yield_args; arg; arg = arg->next, pos += 1) {
type_t *val_t = Match(arg->type, PointerType)->pointed;
Text_t name = (v && !streq(Match(v->ast, Var)->name, "_")) ? Texts("_$", Match(v->ast, Var)->name)
: Texts("yield$", pos);
code = Texts(code, compile_declaration(val_t, name), ";\n");
args_joined = pos == 1 ? Texts("&", name) : Texts(args_joined, ", &", name);
if (v) v = v->next;
}
Text_t get_next = compile_iterator_call(iter_value_t, next_fn, args_joined);
if (for_->empty) {
code = Texts(code, "if (", get_next,
") {\n"
"\tdo{\n\t\t",
naked_body, "\t} while(", get_next,
");\n"
"} else {\n\t",
compile_statement(env, for_->empty), "}", stop, "\n}\n");
} else {
code = Texts(code, "while(", get_next, ") {\n\t", naked_body, "}\n", stop, "\n}\n");
}
return code;
}
ast_t *value_var = single_loop_var(for_->vars);
Text_t get_next = compile_iterator_call(iter_value_t, next_fn, EMPTY_TEXT);
if (fn->ret->tag == OptionalType) {
// Use an optional variable `cur` for each iteration step, which
// will be checked for none
code = Texts(code, compile_declaration(fn->ret, Text("cur")), ";\n");
get_next = Texts("(cur=", get_next, ", !", check_none(fn->ret, Text("cur")), ")");
if (value_var) {
naked_body = Texts(
compile_declaration(Match(fn->ret, OptionalType)->type, Texts("_$", Match(value_var, Var)->name)),
" = ", optional_into_nonnone(fn->ret, Text("cur")), ";\n", naked_body);
}
if (for_->empty) {
code = Texts(code, "if (", get_next,
") {\n"
"\tdo{\n\t\t",
naked_body, "\t} while(", get_next,
");\n"
"} else {\n\t",
compile_statement(env, for_->empty), "}", stop, "\n}\n");
} else {
code = Texts(code, "while(", get_next, ") {\n\t", naked_body, "}\n", stop, "\n}\n");
}
} else {
if (value_var) {
naked_body = Texts(compile_declaration(fn->ret, Texts("_$", Match(value_var, Var)->name)), " = ",
get_next, ";\n", naked_body);
} else {
naked_body = Texts(get_next, ";\n", naked_body);
}
if (for_->empty)
code_err(for_->empty, "This iteration loop will always have values, "
"so this block will never run");
code = Texts(code, "for (;;) {\n\t", naked_body, "}\n", stop, "\n}\n");
}
return code;
}
case TextType: {
ast_t *index_var = for_->at;
ast_t *value_var = single_loop_var(for_->vars);
Text_t index = index_var ? compile(body_scope, index_var) : EMPTY_TEXT;
Text_t value = value_var ? compile(body_scope, value_var) : EMPTY_TEXT;
Text_t code =
Texts("{\n"
"TextIter_t ",
value, "$state = NEW_TEXT_ITER_STATE(", compile_to_pointer_depth(env, iter, 0, false), ");\n");
Text_t loop = Texts("for (int64_t ", value, "$i = 0; ", value, "$i < (int64_t)", value,
"$state.stack[0].text.length; ", value, "$i += 1) {\n");
if (index.length > 0) {
// 1-indexed, to match text cluster indexing:
loop = Texts(loop, "Int64_t ", index, " = ", value, "$i + 1;\n");
}
if (value.length > 0) {
loop = Texts(loop, "int32_t g = Text$get_grapheme_fast(&", value, "$state, ", value,
"$i);\n"
"Text_t ",
value, " = Text$from_grapheme(g);\n", naked_body, "}\n");
} else {
loop = Texts(loop, naked_body, "}\n");
}
if (for_->empty)
loop = Texts("if (", value, "$state.stack[0].text.length > 0) {\n", loop, "\n} else ",
compile_statement(env, for_->empty));
code = Texts(code, loop, stop, "}\n");
return code;
}
default: code_err(iter, "Iteration is not implemented for type: ", type_to_text(iter_t));
}
}
public
Text_t compile_for_loop(env_t *env, ast_t *ast) {
Text_t cow_prelude = EMPTY_TEXT;
env = cow_hoist_env(env, ast, &cow_prelude);
Text_t code = compile_for_loop_impl(env, ast);
if (cow_prelude.length > 0) code = Texts("{\n", cow_prelude, code, "\n}");
return code;
}
public
Text_t compile_repeat(env_t *env, ast_t *ast) {
Text_t cow_prelude = EMPTY_TEXT;
env = cow_hoist_env(env, ast, &cow_prelude);
ast_t *body = Match(ast, Repeat)->body;
env_t *scope = fresh_scope(env);
loop_ctx_t loop_ctx = (loop_ctx_t){
.loop_name = "repeat",
.deferred = scope->deferred,
.next = env->loop_ctx,
};
scope->loop_ctx = &loop_ctx;
Text_t body_code = compile_statement(scope, body);
if (loop_ctx.skip_label.length > 0) body_code = Texts(body_code, "\n", loop_ctx.skip_label, ": continue;");
Text_t loop = Texts("for (;;) {\n\t", body_code, "\n}");
if (loop_ctx.stop_label.length > 0) loop = Texts(loop, "\n", loop_ctx.stop_label, ":;");
if (cow_prelude.length > 0) loop = Texts("{\n", cow_prelude, loop, "\n}");
return loop;
}
public
Text_t compile_while(env_t *env, ast_t *ast) {
Text_t cow_prelude = EMPTY_TEXT;
env = cow_hoist_env(env, ast, &cow_prelude);
DeclareMatch(while_, ast, While);
env_t *scope = fresh_scope(env);
loop_ctx_t loop_ctx = (loop_ctx_t){
.loop_name = "while",
.deferred = scope->deferred,
.next = env->loop_ctx,
};
scope->loop_ctx = &loop_ctx;
Text_t body = compile_statement(scope, while_->body);
if (loop_ctx.skip_label.length > 0) body = Texts(body, "\n", loop_ctx.skip_label, ": continue;");
Text_t loop =
Texts("while (", while_->condition ? compile(scope, while_->condition) : Text("yes"), ") {\n\t", body, "\n}");
if (loop_ctx.stop_label.length > 0) loop = Texts(loop, "\n", loop_ctx.stop_label, ":;");
if (cow_prelude.length > 0) loop = Texts("{\n", cow_prelude, loop, "\n}");
return loop;
}
public
Text_t compile_continue(env_t *env, ast_t *ast) {
const char *target = Match(ast, Continue)->target;
for (loop_ctx_t *ctx = env->loop_ctx; ctx; ctx = ctx->next) {
bool matched = !target || strcmp(target, ctx->loop_name) == 0;
for (ast_list_t *var = ctx->loop_vars; var && !matched; var = var ? var->next : NULL) {
ast_t *var_ast = var->ast->tag == StackReference ? Match(var->ast, StackReference)->value : var->ast;
matched = (strcmp(target, Match(var_ast, Var)->name) == 0);
}
if (matched) {
if (ctx->skip_label.length == 0) {
static int64_t skip_label_count = 1;
ctx->skip_label = Texts("skip_", skip_label_count);
++skip_label_count;
}
Text_t code = EMPTY_TEXT;
for (deferral_t *deferred = env->deferred; deferred && deferred != ctx->deferred; deferred = deferred->next)
code = Texts(code, compile_statement(deferred->defer_env, deferred->block));
if (code.length > 0) return Texts("{\n", code, "goto ", ctx->skip_label, ";\n}\n");
else return Texts("goto ", ctx->skip_label, ";");
}
}
if (env->loop_ctx) code_err(ast, "This is not inside any loop");
else if (target) code_err(ast, "No loop target named '", target, "' was found");
else return Text("continue;");
}
public
Text_t compile_break(env_t *env, ast_t *ast) {
const char *target = Match(ast, Break)->target;
for (loop_ctx_t *ctx = env->loop_ctx; ctx; ctx = ctx->next) {
bool matched = !target || strcmp(target, ctx->loop_name) == 0;
for (ast_list_t *var = ctx->loop_vars; var && !matched; var = var ? var->next : var) {
ast_t *var_ast = var->ast->tag == StackReference ? Match(var->ast, StackReference)->value : var->ast;
matched = (strcmp(target, Match(var_ast, Var)->name) == 0);
}
if (matched) {
if (ctx->stop_label.length == 0) {
static int64_t stop_label_count = 1;
ctx->stop_label = Texts("stop_", stop_label_count);
++stop_label_count;
}
Text_t code = EMPTY_TEXT;
for (deferral_t *deferred = env->deferred; deferred && deferred != ctx->deferred; deferred = deferred->next)
code = Texts(code, compile_statement(deferred->defer_env, deferred->block));
if (code.length > 0) return Texts("{\n", code, "goto ", ctx->stop_label, ";\n}\n");
else return Texts("goto ", ctx->stop_label, ";");
}
}
if (env->loop_ctx) code_err(ast, "This is not inside any loop");
else if (target) code_err(ast, "No loop target named '", target, "' was found");
else return Text("break;");
}
|