mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-18 10:37:01 +00:00
#unroll(N) for
This commit is contained in:
@@ -894,15 +894,49 @@ gb_internal void error_var_decl_identifier(Ast *name) {
|
||||
}
|
||||
}
|
||||
|
||||
gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
|
||||
gb_internal void check_unroll_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
|
||||
ast_node(irs, UnrollRangeStmt, node);
|
||||
check_open_scope(ctx, node);
|
||||
defer (check_close_scope(ctx));
|
||||
|
||||
Type *val0 = nullptr;
|
||||
Type *val1 = nullptr;
|
||||
Entity *entities[2] = {};
|
||||
isize entity_count = 0;
|
||||
|
||||
i64 unroll_count = -1;
|
||||
|
||||
if (irs->args.count > 0) {
|
||||
if (irs->args.count > 1) {
|
||||
error(irs->args[1], "#unroll only supports a single argument for the unroll per loop amount");
|
||||
}
|
||||
Ast *arg = irs->args[0];
|
||||
if (arg->kind == Ast_FieldValue) {
|
||||
error(arg, "#unroll does not yet support named arguments");
|
||||
arg = arg->FieldValue.value;
|
||||
}
|
||||
|
||||
Operand x = {};
|
||||
check_expr(ctx, &x, arg);
|
||||
if (x.mode != Addressing_Constant || !is_type_integer(x.type)) {
|
||||
gbString s = expr_to_string(x.expr);
|
||||
error(x.expr, "Expected a constant integer for #unroll, got '%s'", s);
|
||||
gb_string_free(s);
|
||||
} else {
|
||||
ExactValue value = exact_value_to_integer(x.value);
|
||||
i64 v = exact_value_to_i64(value);
|
||||
if (v < 1) {
|
||||
error(x.expr, "Expected a constant integer >= 1 for #unroll, got %lld", cast(long long)v);
|
||||
} else {
|
||||
unroll_count = v;
|
||||
if (v > 1024) {
|
||||
error(x.expr, "Too large of a value for #unroll, got %lld, expected <= 1024", cast(long long)v);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Ast *expr = unparen_expr(irs->expr);
|
||||
|
||||
ExactValue inline_for_depth = exact_value_i64(0);
|
||||
@@ -946,18 +980,39 @@ gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod
|
||||
val0 = t_rune;
|
||||
val1 = t_int;
|
||||
inline_for_depth = exact_value_i64(operand.value.value_string.len);
|
||||
if (unroll_count > 0) {
|
||||
error(node, "#unroll(%lld) does not support strings", cast(long long)unroll_count);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Type_Array:
|
||||
val0 = t->Array.elem;
|
||||
val1 = t_int;
|
||||
inline_for_depth = exact_value_i64(t->Array.count);
|
||||
inline_for_depth = unroll_count > 0 ? exact_value_i64(unroll_count) : exact_value_i64(t->Array.count);
|
||||
break;
|
||||
case Type_EnumeratedArray:
|
||||
val0 = t->EnumeratedArray.elem;
|
||||
val1 = t->EnumeratedArray.index;
|
||||
if (unroll_count > 0) {
|
||||
error(node, "#unroll(%lld) does not support enumerated arrays", cast(long long)unroll_count);
|
||||
}
|
||||
inline_for_depth = exact_value_i64(t->EnumeratedArray.count);
|
||||
break;
|
||||
|
||||
case Type_Slice:
|
||||
if (unroll_count > 0) {
|
||||
val0 = t->Slice.elem;
|
||||
val1 = t_int;
|
||||
inline_for_depth = exact_value_i64(unroll_count);
|
||||
}
|
||||
break;
|
||||
case Type_DynamicArray:
|
||||
if (unroll_count > 0) {
|
||||
val0 = t->DynamicArray.elem;
|
||||
val1 = t_int;
|
||||
inline_for_depth = exact_value_i64(unroll_count);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -967,7 +1022,7 @@ gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod
|
||||
error(operand.expr, "Cannot iterate over '%s' of type '%s' in an '#unroll for' statement", s, t);
|
||||
gb_string_free(t);
|
||||
gb_string_free(s);
|
||||
} else if (operand.mode != Addressing_Constant) {
|
||||
} else if (operand.mode != Addressing_Constant && unroll_count <= 0) {
|
||||
error(operand.expr, "An '#unroll for' expression must be known at compile time");
|
||||
}
|
||||
}
|
||||
@@ -1050,8 +1105,6 @@ gb_internal void check_inline_range_stmt(CheckerContext *ctx, Ast *node, u32 mod
|
||||
|
||||
check_stmt(ctx, irs->body, mod_flags);
|
||||
|
||||
|
||||
check_close_scope(ctx);
|
||||
}
|
||||
|
||||
gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) {
|
||||
@@ -2679,7 +2732,7 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags)
|
||||
case_end;
|
||||
|
||||
case_ast_node(irs, UnrollRangeStmt, node);
|
||||
check_inline_range_stmt(ctx, node, mod_flags);
|
||||
check_unroll_range_stmt(ctx, node, mod_flags);
|
||||
case_end;
|
||||
|
||||
case_ast_node(ss, SwitchStmt, node);
|
||||
|
||||
@@ -256,7 +256,7 @@ gb_internal void lb_build_when_stmt(lbProcedure *p, AstWhenStmt *ws) {
|
||||
|
||||
gb_internal void lb_build_range_indexed(lbProcedure *p, lbValue expr, Type *val_type, lbValue count_ptr,
|
||||
lbValue *val_, lbValue *idx_, lbBlock **loop_, lbBlock **done_,
|
||||
bool is_reverse) {
|
||||
bool is_reverse, i64 unroll_count=0) {
|
||||
lbModule *m = p->module;
|
||||
|
||||
lbValue count = {};
|
||||
@@ -1230,7 +1230,6 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt *
|
||||
TypeAndValue tav = type_and_value_of_expr(expr);
|
||||
|
||||
if (is_ast_range(expr)) {
|
||||
|
||||
lbAddr val0_addr = {};
|
||||
lbAddr val1_addr = {};
|
||||
if (val0_type) val0_addr = lb_build_addr(p, val0);
|
||||
@@ -1268,7 +1267,6 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt *
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (tav.mode == Addressing_Type) {
|
||||
GB_ASSERT(is_type_enum(type_deref(tav.type)));
|
||||
Type *et = type_deref(tav.type);
|
||||
@@ -1293,72 +1291,203 @@ gb_internal void lb_build_unroll_range_stmt(lbProcedure *p, AstUnrollRangeStmt *
|
||||
if (val0_type) val0_addr = lb_build_addr(p, val0);
|
||||
if (val1_type) val1_addr = lb_build_addr(p, val1);
|
||||
|
||||
GB_ASSERT(expr->tav.mode == Addressing_Constant);
|
||||
|
||||
Type *t = base_type(expr->tav.type);
|
||||
ExactValue unroll_count_ev = {};
|
||||
if (rs->args.count != 0) {
|
||||
unroll_count_ev = rs->args[0]->tav.value;
|
||||
}
|
||||
|
||||
|
||||
switch (t->kind) {
|
||||
case Type_Basic:
|
||||
GB_ASSERT(is_type_string(t));
|
||||
{
|
||||
ExactValue value = expr->tav.value;
|
||||
GB_ASSERT(value.kind == ExactValue_String);
|
||||
String str = value.value_string;
|
||||
Rune codepoint = 0;
|
||||
isize offset = 0;
|
||||
do {
|
||||
isize width = utf8_decode(str.text+offset, str.len-offset, &codepoint);
|
||||
if (val0_type) lb_addr_store(p, val0_addr, lb_const_value(m, val0_type, exact_value_i64(codepoint)));
|
||||
if (val1_type) lb_addr_store(p, val1_addr, lb_const_value(m, val1_type, exact_value_i64(offset)));
|
||||
lb_build_stmt(p, rs->body);
|
||||
if (unroll_count_ev.kind == ExactValue_Invalid) {
|
||||
GB_ASSERT(expr->tav.mode == Addressing_Constant);
|
||||
|
||||
offset += width;
|
||||
} while (offset < str.len);
|
||||
}
|
||||
break;
|
||||
case Type_Array:
|
||||
if (t->Array.count > 0) {
|
||||
lbValue val = lb_build_expr(p, expr);
|
||||
lbValue val_addr = lb_address_from_load_or_generate_local(p, val);
|
||||
Type *t = base_type(expr->tav.type);
|
||||
|
||||
for (i64 i = 0; i < t->Array.count; i++) {
|
||||
if (val0_type) {
|
||||
// NOTE(bill): Due to weird legacy issues in LLVM, this needs to be an i32
|
||||
lbValue elem = lb_emit_array_epi(p, val_addr, cast(i32)i);
|
||||
lb_addr_store(p, val0_addr, lb_emit_load(p, elem));
|
||||
switch (t->kind) {
|
||||
case Type_Basic:
|
||||
GB_ASSERT(is_type_string(t));
|
||||
{
|
||||
ExactValue value = expr->tav.value;
|
||||
GB_ASSERT(value.kind == ExactValue_String);
|
||||
String str = value.value_string;
|
||||
Rune codepoint = 0;
|
||||
isize offset = 0;
|
||||
do {
|
||||
isize width = utf8_decode(str.text+offset, str.len-offset, &codepoint);
|
||||
if (val0_type) lb_addr_store(p, val0_addr, lb_const_value(m, val0_type, exact_value_i64(codepoint)));
|
||||
if (val1_type) lb_addr_store(p, val1_addr, lb_const_value(m, val1_type, exact_value_i64(offset)));
|
||||
lb_build_stmt(p, rs->body);
|
||||
|
||||
offset += width;
|
||||
} while (offset < str.len);
|
||||
}
|
||||
break;
|
||||
case Type_Array:
|
||||
if (t->Array.count > 0) {
|
||||
lbValue val = lb_build_expr(p, expr);
|
||||
lbValue val_addr = lb_address_from_load_or_generate_local(p, val);
|
||||
|
||||
for (i64 i = 0; i < t->Array.count; i++) {
|
||||
if (val0_type) {
|
||||
// NOTE(bill): Due to weird legacy issues in LLVM, this needs to be an i32
|
||||
lbValue elem = lb_emit_array_epi(p, val_addr, cast(i32)i);
|
||||
lb_addr_store(p, val0_addr, lb_emit_load(p, elem));
|
||||
}
|
||||
if (val1_type) lb_addr_store(p, val1_addr, lb_const_value(m, val1_type, exact_value_i64(i)));
|
||||
|
||||
lb_build_stmt(p, rs->body);
|
||||
}
|
||||
if (val1_type) lb_addr_store(p, val1_addr, lb_const_value(m, val1_type, exact_value_i64(i)));
|
||||
|
||||
lb_build_stmt(p, rs->body);
|
||||
}
|
||||
break;
|
||||
case Type_EnumeratedArray:
|
||||
if (t->EnumeratedArray.count > 0) {
|
||||
lbValue val = lb_build_expr(p, expr);
|
||||
lbValue val_addr = lb_address_from_load_or_generate_local(p, val);
|
||||
|
||||
for (i64 i = 0; i < t->EnumeratedArray.count; i++) {
|
||||
if (val0_type) {
|
||||
// NOTE(bill): Due to weird legacy issues in LLVM, this needs to be an i32
|
||||
lbValue elem = lb_emit_array_epi(p, val_addr, cast(i32)i);
|
||||
lb_addr_store(p, val0_addr, lb_emit_load(p, elem));
|
||||
}
|
||||
if (val1_type) {
|
||||
ExactValue idx = exact_value_add(exact_value_i64(i), *t->EnumeratedArray.min_value);
|
||||
lb_addr_store(p, val1_addr, lb_const_value(m, val1_type, idx));
|
||||
}
|
||||
|
||||
lb_build_stmt(p, rs->body);
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
GB_PANIC("Invalid '#unroll for' type");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
||||
////////////////////////////////
|
||||
// //
|
||||
// #unroll(N) logic //
|
||||
// //
|
||||
////////////////////////////////
|
||||
|
||||
|
||||
i64 unroll_count = exact_value_to_i64(unroll_count_ev);
|
||||
gb_unused(unroll_count);
|
||||
|
||||
Type *t = base_type(expr->tav.type);
|
||||
|
||||
lbValue data_ptr = {};
|
||||
lbValue count_ptr = {};
|
||||
|
||||
switch (t->kind) {
|
||||
case Type_Slice:
|
||||
case Type_DynamicArray: {
|
||||
lbValue slice = lb_build_expr(p, expr);
|
||||
if (is_type_pointer(slice.type)) {
|
||||
count_ptr = lb_emit_struct_ep(p, slice, 1);
|
||||
slice = lb_emit_load(p, slice);
|
||||
} else {
|
||||
count_ptr = lb_add_local_generated(p, t_int, false).addr;
|
||||
lb_emit_store(p, count_ptr, lb_slice_len(p, slice));
|
||||
}
|
||||
data_ptr = lb_emit_struct_ev(p, slice, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
case Type_Array: {
|
||||
lbValue array = lb_build_expr(p, expr);
|
||||
count_ptr = lb_add_local_generated(p, t_int, false).addr;
|
||||
lb_emit_store(p, count_ptr, lb_const_int(p->module, t_int, t->Array.count));
|
||||
|
||||
if (!is_type_pointer(array.type)) {
|
||||
array = lb_address_from_load_or_generate_local(p, array);
|
||||
}
|
||||
|
||||
GB_ASSERT(is_type_pointer(array.type));
|
||||
data_ptr = lb_emit_conv(p, array, alloc_type_pointer(t->Array.elem));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Type_EnumeratedArray:
|
||||
if (t->EnumeratedArray.count > 0) {
|
||||
lbValue val = lb_build_expr(p, expr);
|
||||
lbValue val_addr = lb_address_from_load_or_generate_local(p, val);
|
||||
|
||||
for (i64 i = 0; i < t->EnumeratedArray.count; i++) {
|
||||
if (val0_type) {
|
||||
// NOTE(bill): Due to weird legacy issues in LLVM, this needs to be an i32
|
||||
lbValue elem = lb_emit_array_epi(p, val_addr, cast(i32)i);
|
||||
lb_addr_store(p, val0_addr, lb_emit_load(p, elem));
|
||||
}
|
||||
if (val1_type) {
|
||||
ExactValue idx = exact_value_add(exact_value_i64(i), *t->EnumeratedArray.min_value);
|
||||
lb_addr_store(p, val1_addr, lb_const_value(m, val1_type, idx));
|
||||
}
|
||||
default:
|
||||
GB_PANIC("Invalid '#unroll for' type");
|
||||
break;
|
||||
}
|
||||
|
||||
data_ptr.type = alloc_type_multi_pointer_to_pointer(data_ptr.type);
|
||||
|
||||
lbBlock *loop_top = lb_create_block(p, "for.unroll.loop.top");
|
||||
|
||||
lbBlock *body_top = lb_create_block(p, "for.unroll.body.top");
|
||||
lbBlock *body_bot = lb_create_block(p, "for.unroll.body.bot");
|
||||
|
||||
lbBlock *done = lb_create_block(p, "for.unroll.done");
|
||||
|
||||
lbBlock *loop_bot = unroll_count > 1 ? lb_create_block(p, "for.unroll.loop.bot") : done;
|
||||
|
||||
/*
|
||||
i := 0
|
||||
for ; i+N <= len(array); i += N {
|
||||
body
|
||||
}
|
||||
for ; i < len(array); i += 1 {
|
||||
body
|
||||
}
|
||||
*/
|
||||
|
||||
Entity *val_entity = val0 ? entity_of_node(val0) : nullptr;
|
||||
Entity *idx_entity = val1 ? entity_of_node(val1) : nullptr;
|
||||
|
||||
lbAddr val_addr = lb_add_local(p, type_deref(data_ptr.type, true), val_entity);
|
||||
lbAddr idx_addr = lb_add_local(p, t_int, idx_entity);
|
||||
lb_addr_store(p, idx_addr, lb_const_nil(p->module, t_int));
|
||||
|
||||
lb_emit_jump(p, loop_top);
|
||||
lb_start_block(p, loop_top);
|
||||
|
||||
lbValue idx_add_n = lb_addr_load(p, idx_addr);
|
||||
idx_add_n = lb_emit_arith(p, Token_Add, idx_add_n, lb_const_int(p->module, t_int, unroll_count), t_int);
|
||||
|
||||
lbValue cond_top = lb_emit_comp(p, Token_LtEq, idx_add_n, lb_emit_load(p, count_ptr));
|
||||
lb_emit_if(p, cond_top, body_top, loop_bot);
|
||||
|
||||
lb_start_block(p, body_top);
|
||||
for (i64 top = 0; top < unroll_count; top++) {
|
||||
lbValue idx = lb_addr_load(p, idx_addr);
|
||||
lbValue val = lb_emit_load(p, lb_emit_ptr_offset(p, data_ptr, idx));
|
||||
lb_addr_store(p, val_addr, val);
|
||||
|
||||
lb_build_stmt(p, rs->body);
|
||||
|
||||
lb_emit_increment(p, lb_addr_get_ptr(p, idx_addr));
|
||||
}
|
||||
lb_emit_jump(p, loop_top);
|
||||
|
||||
if (unroll_count > 1) {
|
||||
lb_start_block(p, loop_bot);
|
||||
|
||||
lbValue cond_bot = lb_emit_comp(p, Token_Lt, lb_addr_load(p, idx_addr), lb_emit_load(p, count_ptr));
|
||||
lb_emit_if(p, cond_bot, body_bot, done);
|
||||
|
||||
lb_start_block(p, body_bot);
|
||||
{
|
||||
lbValue idx = lb_addr_load(p, idx_addr);
|
||||
lbValue val = lb_emit_load(p, lb_emit_ptr_offset(p, data_ptr, idx));
|
||||
lb_addr_store(p, val_addr, val);
|
||||
|
||||
lb_build_stmt(p, rs->body);
|
||||
}
|
||||
|
||||
lb_emit_increment(p, lb_addr_get_ptr(p, idx_addr));
|
||||
}
|
||||
lb_emit_jump(p, loop_bot);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
GB_PANIC("Invalid '#unroll for' type");
|
||||
break;
|
||||
|
||||
lb_close_scope(p, lbDeferExit_Default, nullptr, rs->body);
|
||||
lb_emit_jump(p, done);
|
||||
lb_start_block(p, done);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -348,10 +348,11 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) {
|
||||
n->RangeStmt.body = clone_ast(n->RangeStmt.body, f);
|
||||
break;
|
||||
case Ast_UnrollRangeStmt:
|
||||
n->UnrollRangeStmt.val0 = clone_ast(n->UnrollRangeStmt.val0, f);
|
||||
n->UnrollRangeStmt.val1 = clone_ast(n->UnrollRangeStmt.val1, f);
|
||||
n->UnrollRangeStmt.expr = clone_ast(n->UnrollRangeStmt.expr, f);
|
||||
n->UnrollRangeStmt.body = clone_ast(n->UnrollRangeStmt.body, f);
|
||||
n->UnrollRangeStmt.args = clone_ast_array(n->UnrollRangeStmt.args, f);
|
||||
n->UnrollRangeStmt.val0 = clone_ast(n->UnrollRangeStmt.val0, f);
|
||||
n->UnrollRangeStmt.val1 = clone_ast(n->UnrollRangeStmt.val1, f);
|
||||
n->UnrollRangeStmt.expr = clone_ast(n->UnrollRangeStmt.expr, f);
|
||||
n->UnrollRangeStmt.body = clone_ast(n->UnrollRangeStmt.body, f);
|
||||
break;
|
||||
case Ast_CaseClause:
|
||||
n->CaseClause.list = clone_ast_array(n->CaseClause.list, f);
|
||||
@@ -1037,15 +1038,16 @@ gb_internal Ast *ast_range_stmt(AstFile *f, Token token, Slice<Ast *> vals, Toke
|
||||
return result;
|
||||
}
|
||||
|
||||
gb_internal Ast *ast_unroll_range_stmt(AstFile *f, Token unroll_token, Token for_token, Ast *val0, Ast *val1, Token in_token, Ast *expr, Ast *body) {
|
||||
gb_internal Ast *ast_unroll_range_stmt(AstFile *f, Token unroll_token, Slice<Ast *> args, Token for_token, Ast *val0, Ast *val1, Token in_token, Ast *expr, Ast *body) {
|
||||
Ast *result = alloc_ast_node(f, Ast_UnrollRangeStmt);
|
||||
result->UnrollRangeStmt.unroll_token = unroll_token;
|
||||
result->UnrollRangeStmt.args = args;
|
||||
result->UnrollRangeStmt.for_token = for_token;
|
||||
result->UnrollRangeStmt.val0 = val0;
|
||||
result->UnrollRangeStmt.val1 = val1;
|
||||
result->UnrollRangeStmt.in_token = in_token;
|
||||
result->UnrollRangeStmt.expr = expr;
|
||||
result->UnrollRangeStmt.body = body;
|
||||
result->UnrollRangeStmt.val0 = val0;
|
||||
result->UnrollRangeStmt.val1 = val1;
|
||||
result->UnrollRangeStmt.in_token = in_token;
|
||||
result->UnrollRangeStmt.expr = expr;
|
||||
result->UnrollRangeStmt.body = body;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -5137,6 +5139,40 @@ gb_internal Ast *parse_attribute(AstFile *f, Token token, TokenKind open_kind, T
|
||||
|
||||
|
||||
gb_internal Ast *parse_unrolled_for_loop(AstFile *f, Token unroll_token) {
|
||||
Array<Ast *> args = {};
|
||||
|
||||
if (allow_token(f, Token_OpenParen)) {
|
||||
f->expr_level++;
|
||||
if (f->curr_token.kind == Token_CloseParen) {
|
||||
syntax_error(f->curr_token, "#unroll expected at least 1 argument, got 0");
|
||||
} else {
|
||||
args = array_make<Ast *>(ast_allocator(f));
|
||||
while (f->curr_token.kind != Token_CloseParen &&
|
||||
f->curr_token.kind != Token_EOF) {
|
||||
Ast *arg = nullptr;
|
||||
arg = parse_value(f);
|
||||
|
||||
if (f->curr_token.kind == Token_Eq) {
|
||||
Token eq = expect_token(f, Token_Eq);
|
||||
if (arg != nullptr && arg->kind != Ast_Ident) {
|
||||
syntax_error(arg, "Expected an identifier for 'key=value'");
|
||||
}
|
||||
Ast *value = parse_value(f);
|
||||
arg = ast_field_value(f, arg, value, eq);
|
||||
}
|
||||
|
||||
array_add(&args, arg);
|
||||
|
||||
if (!allow_field_separator(f)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
f->expr_level--;
|
||||
Token close = expect_closing(f, Token_CloseParen, str_lit("#unroll"));
|
||||
gb_unused(close);
|
||||
}
|
||||
|
||||
Token for_token = expect_token(f, Token_for);
|
||||
Ast *val0 = nullptr;
|
||||
Ast *val1 = nullptr;
|
||||
@@ -5180,7 +5216,7 @@ gb_internal Ast *parse_unrolled_for_loop(AstFile *f, Token unroll_token) {
|
||||
if (bad_stmt) {
|
||||
return ast_bad_stmt(f, unroll_token, f->curr_token);
|
||||
}
|
||||
return ast_unroll_range_stmt(f, unroll_token, for_token, val0, val1, in_token, expr, body);
|
||||
return ast_unroll_range_stmt(f, unroll_token, slice_from_array(args), for_token, val0, val1, in_token, expr, body);
|
||||
}
|
||||
|
||||
gb_internal Ast *parse_stmt(AstFile *f) {
|
||||
|
||||
@@ -563,6 +563,7 @@ AST_KIND(_ComplexStmtBegin, "", bool) \
|
||||
AST_KIND(UnrollRangeStmt, "#unroll range statement", struct { \
|
||||
Scope *scope; \
|
||||
Token unroll_token; \
|
||||
Slice<Ast *> args; \
|
||||
Token for_token; \
|
||||
Ast *val0; \
|
||||
Ast *val1; \
|
||||
|
||||
Reference in New Issue
Block a user