Implement for

This commit is contained in:
David Gonzalez Martin 2025-06-14 20:34:37 -06:00
parent b24b5f29f7
commit c96f292ab9
2 changed files with 603 additions and 5 deletions

View File

@ -2782,8 +2782,8 @@ StatementFor = struct
{
first_local: &Local,
last_local: &Local,
kinds: ValueKind,
iteratables: &Value,
kinds: []ValueKind,
iteratables: []&Value,
predicate: &Statement,
scope: Scope,
kind: StatementForKind,
@ -2811,6 +2811,12 @@ Statement = struct
id: StatementId,
}
scope_to_for = fn (scope: &Scope) &StatementFor
{
assert(scope.kind == .for_each);
return #field_parent_pointer(scope, "scope");
}
scope_to_block = fn (scope: &Scope) &Block
{
assert(scope.kind == .local);
@ -2852,7 +2858,17 @@ new_local = fn (module: &Module, scope: &Scope) &Local
},
.for_each =>
{
#trap();
>for_each = scope_to_for(scope);
if (for_each.last_local)
{
for_each.last_local.next = result;
}
else
{
for_each.first_local = result;
}
for_each.last_local = result;
}
else =>
{
@ -4918,7 +4934,20 @@ reference_identifier = fn (module: &Module, current_scope: &Scope, identifier: [
},
.for_each =>
{
#trap();
assert(scope.parent != zero);
>for_each = scope_to_for(scope);
>local = for_each.first_local;
while (local)
{
if (string_equal(identifier, local.variable.name))
{
variable = &local.variable;
break;
}
local = local.next;
}
},
.macro_declaration =>
{
@ -6030,7 +6059,137 @@ parse_statement = fn (module: &Module, scope: &Scope) &Statement
},
.for =>
{
#trap();
skip_space(module);
expect_character(module, left_parenthesis);
skip_space(module);
>parent_scope = scope;
statement.content = {
.for = {
.scope = {
.parent = parent_scope,
.line = statement_line,
.column = statement_column,
.kind = .for_each,
zero,
},
zero,
},
};
statement.id = .for;
>scope = &statement.content.for.scope;
>left_value_buffer: [64]ValueKind = undefined;
>left_value_count: u64 = 0;
while (1)
{
skip_space(module);
>is_left = module.content[module.offset] == '&';
module.offset += #extend(is_left);
>for_local_line = get_line(module);
>for_local_column = get_column(module);
if (is_identifier_start(module.content[module.offset]))
{
>local_name = arena_duplicate_string(module.arena, parse_identifier(module));
>local = new_local(module, scope);
local.& = {
.variable = {
.scope = scope,
.name = local_name,
.line = for_local_line,
.column = for_local_column,
zero,
},
zero,
};
>kind: ValueKind = #select(is_left, .left, .right);
left_value_buffer[left_value_count] = kind;
left_value_count += 1;
}
else
{
#trap();
}
skip_space(module);
if (!consume_character_if_match(module, ','))
{
expect_character(module, ':');
break;
}
}
skip_space(module);
>right_value_buffer: [64]&Value = undefined;
>right_value_count: u64 = 0;
skip_space(module);
right_value_buffer[right_value_count] = parse_value(module, scope, zero);
right_value_count += 1;
skip_space(module);
>token = tokenize(module);
>for_kind: StatementForKind = undefined;
switch (token.id)
{
.double_dot =>
{
if (left_value_count != 1)
{
report_error();
}
right_value_buffer[right_value_count] = parse_value(module, scope, zero);
right_value_count += 1;
expect_character(module, right_parenthesis);
for_kind = .range;
},
.right_parenthesis =>
{
right_value_buffer[0].kind = .left;
for_kind = .slice;
},
else => { report_error(); },
}
statement.content.for.kind = for_kind;
if (for_kind == .slice and left_value_count != right_value_count)
{
report_error();
}
>left_values = arena_allocate_slice[ValueKind](module.arena, left_value_count);
memcpy(#pointer_cast(left_values.pointer), #pointer_cast(&left_value_buffer), left_value_count * #byte_size(ValueKind));
>right_values = arena_allocate_slice[&Value](module.arena, right_value_count);
memcpy(#pointer_cast(right_values.pointer), #pointer_cast(&right_value_buffer), right_value_count * #byte_size(&Value));
statement.content.for.kinds = left_values;
statement.content.for.iteratables = right_values;
skip_space(module);
>predicate = parse_statement(module, scope);
statement.content.for.predicate = predicate;
skip_space(module);
require_semicolon = 0;
},
.while =>
{
@ -13471,6 +13630,441 @@ analyze_statement = fn (module: &Module, scope: &Scope, statement: &Statement) v
LLVMClearInsertionPosition(module.llvm.builder);
}
},
.for =>
{
if (module.has_debug_info)
{
>lexical_block = LLVMDIBuilderCreateLexicalBlock(module.llvm.di_builder, statement.content.for.scope.parent.llvm, module.llvm.file, statement.content.for.scope.line, statement.content.for.scope.column);
statement.content.for.scope.llvm = lexical_block;
}
>index_type = uint64(module);
resolve_type_in_place(module, index_type);
>index_zero = LLVMConstNull(index_type.llvm.abi);
>entry_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "for_each.entry");
>body_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "for_each.body");
>continue_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "for_each.continue");
>exit_block = LLVMAppendBasicBlockInContext(module.llvm.context, llvm_function, "for_each.exit");
>previous_continue_block = module.llvm.continue_block;
>previous_exit_block = module.llvm.exit_block;
module.llvm.continue_block = continue_block;
module.llvm.exit_block = exit_block;
>kinds = statement.content.for.kinds;
>iteratables = statement.content.for.iteratables;
switch (statement.content.for.kind)
{
.slice =>
{
// Construct loop pre-initialization
assert(kinds.length == iteratables.length);
>local = statement.content.for.first_local;
for (i: 0..iteratables.length)
{
>kind = kinds[i];
>iteratable = iteratables[i];
analyze_type(module, iteratable, zero, zero);
if (iteratable.kind == .right)
{
if (!type_is_slice(iteratable.type))
{
reanalyze_type_as_left_value(module, iteratable);
}
}
>aggregate_type: &Type = zero;
if (iteratable.kind == .left and iteratable.type.id != .pointer)
{
if (!type_is_slice(iteratable.type))
{
report_error();
}
iteratable.kind = .right;
}
switch (iteratable.kind)
{
.left =>
{
>pointer_type = iteratable.type;
if (pointer_type.id != .pointer)
{
report_error();
}
aggregate_type = pointer_type.content.pointer.element_type;
},
.right =>
{
aggregate_type = iteratable.type;
assert(type_is_slice(aggregate_type));
},
}
>child_type: &Type = zero;
switch (aggregate_type.id)
{
.array =>
{
child_type = aggregate_type.content.array.element_type;
},
.struct =>
{
if (!type_is_slice(aggregate_type))
{
report_error();
}
>pointer_type = aggregate_type.content.struct.fields[0].type;
assert(pointer_type.id == .pointer);
child_type = pointer_type.content.pointer.element_type;
},
else => { unreachable; },
}
assert(child_type != zero);
assert(!local.variable.type);
>local_type = child_type;
if (kind == .left)
{
local_type = get_pointer_type(module, child_type);
}
assert(local_type != zero);
local.variable.type = local_type;
emit_local(module, local);
emit_value(module, iteratable, .memory, 0);
local = local.next;
}
assert(!local);
>length_value: &LLVMValue = zero;
// TODO: we are only taking the first length in order to operate
for (value: iteratables)
{
>value_type = value.type;
>aggregate_type = value_type;
if (value.kind == .left)
{
assert(value_type.id == .pointer);
aggregate_type = value_type.content.pointer.element_type;
}
assert(aggregate_type != zero);
>llvm_value = value.llvm;
switch (aggregate_type.id)
{
.array =>
{
assert(value.kind == .left);
>element_count = aggregate_type.content.array.element_count;
length_value = LLVMConstInt(index_type.llvm.abi, element_count, 0);
},
.struct =>
{
assert(type_is_slice(aggregate_type));
switch (value.kind)
{
.right =>
{
length_value = LLVMBuildExtractValue(module.llvm.builder, llvm_value, 1, "slice.length");
},
.left =>
{
>gep = LLVMBuildStructGEP2(module.llvm.builder, aggregate_type.llvm.abi, llvm_value, 1, "slice.length.pointer");
>load = create_load(module, {
.type = index_type,
.pointer = gep,
zero,
});
length_value = load;
},
}
},
else => { unreachable; },
}
break;
}
>index_alloca = create_alloca(module, {
.type = index_type,
.name = "for_each.index",
zero,
});
create_store(module, {
.source = index_zero,
.destination = index_alloca,
.type = index_type,
zero,
});
assert(length_value != zero);
// Construct loop header
LLVMBuildBr(module.llvm.builder, entry_block);
LLVMPositionBuilderAtEnd(module.llvm.builder, entry_block);
>header_index_load = create_load(module, {
.type = index_type,
.pointer = index_alloca,
zero,
});
>index_compare = LLVMBuildICmp(module.llvm.builder, .ult, header_index_load, length_value, "");
LLVMBuildCondBr(module.llvm.builder, index_compare, body_block, exit_block);
// Construct loop body
LLVMPositionBuilderAtEnd(module.llvm.builder, body_block);
>body_index_load = create_load(module, {
.type = index_type,
.pointer = index_alloca,
zero,
});
local = statement.content.for.first_local;
for (i: 0..iteratables.length)
{
>kind = kinds[i];
>iteratable = iteratables[i];
>iteratable_type = iteratable.type;
>iteratable_kind = iteratable.kind;
>iteratable_llvm = iteratable.llvm;
>aggregate_type: &Type = iteratable_type;
if (iteratable_kind == .left)
{
assert(iteratable_type.id == .pointer);
aggregate_type = iteratable_type.content.pointer.element_type;
}
assert(aggregate_type != zero);
>element_pointer_value: &LLVMValue = zero;
switch (aggregate_type.id)
{
.array =>
{
assert(iteratable_kind == .left);
element_pointer_value = create_gep(module, {
.type = aggregate_type.llvm.memory,
.pointer = iteratable_llvm,
.indices = [ index_zero, body_index_load ][..],
zero,
});
},
.struct =>
{
#trap();
},
else => { unreachable; },
}
assert(element_pointer_value != zero);
>local_type = local.variable.type;
switch (kind)
{
.right =>
{
>evaluation_kind = get_evaluation_kind(local_type);
if (evaluation_kind == .scalar or type_is_slice(aggregate_type) or type_is_slice(local_type))
{
>load = create_load(module, {
.type = local_type,
.pointer = element_pointer_value,
zero,
});
create_store(module, {
.source = load,
.destination = local.variable.storage.llvm,
.type = local_type,
zero,
});
}
else
{
#trap();
}
},
.left =>
{
create_store(module, {
.source = element_pointer_value,
.destination = local.variable.storage.llvm,
.type = local_type,
zero,
});
},
}
local = local.next;
}
assert(!local);
analyze_statement(module, &statement.content.for.scope, statement.content.for.predicate);
if (LLVMGetInsertBlock(module.llvm.builder))
{
LLVMBuildBr(module.llvm.builder, continue_block);
}
LLVMPositionBuilderAtEnd(module.llvm.builder, continue_block);
>continue_index_load = create_load(module, {
.type = index_type,
.pointer = index_alloca,
zero,
});
>inc = LLVMBuildAdd(module.llvm.builder, continue_index_load, LLVMConstInt(index_type.llvm.abi, 1, 0), "");
create_store(module, {
.source = inc,
.destination = index_alloca,
.type = index_type,
zero,
});
LLVMBuildBr(module.llvm.builder, entry_block);
LLVMPositionBuilderAtEnd(module.llvm.builder, exit_block);
},
.range =>
{
>local = statement.content.for.first_local;
assert(local != zero);
assert(!local.next);
assert(!local.variable.type);
assert(kinds.length == 1);
if (iteratables.length == 2)
{
>start = iteratables[0];
>end = iteratables[1];
>local_type: &Type = zero;
if (value_is_constant(start))
{
#trap();
}
else
{
analyze_binary_type(module, start, end, 0, zero, 0);
assert(start.type == end.type);
local_type = start.type;
}
assert(local_type != zero);
for (iteratable: iteratables)
{
if (!iteratable.type)
{
analyze_type(module, iteratable, local_type, zero);
}
}
local.variable.type = local_type;
emit_local(module, local);
emit_value(module, start, .memory, 0);
>index_alloca = local.variable.storage.llvm;
create_store(module, {
.source = start.llvm,
.destination = index_alloca,
.type = local_type,
zero,
});
LLVMBuildBr(module.llvm.builder, entry_block);
LLVMPositionBuilderAtEnd(module.llvm.builder, entry_block);
>header_index_load = create_load(module, {
.type = local_type,
.pointer = index_alloca,
zero,
});
emit_value(module, end, .abi, 0);
>length_value = end.llvm;
>index_compare = LLVMBuildICmp(module.llvm.builder, .ult, header_index_load, length_value, "");
LLVMBuildCondBr(module.llvm.builder, index_compare, body_block, exit_block);
LLVMPositionBuilderAtEnd(module.llvm.builder, body_block);
analyze_statement(module, &statement.content.for.scope, statement.content.for.predicate);
if (LLVMGetInsertBlock(module.llvm.builder))
{
LLVMBuildBr(module.llvm.builder, continue_block);
}
LLVMPositionBuilderAtEnd(module.llvm.builder, continue_block);
>continue_index_load = create_load(module, {
.type = local_type,
.pointer = index_alloca,
zero,
});
>inc = LLVMBuildAdd(module.llvm.builder, continue_index_load, LLVMConstInt(local_type.llvm.abi, 1, 0), "");
create_store(module, {
.source = inc,
.destination = index_alloca,
.type = local_type,
zero,
});
LLVMBuildBr(module.llvm.builder, entry_block);
LLVMPositionBuilderAtEnd(module.llvm.builder, exit_block);
}
else
{
// TODO: case for reverse range
#trap();
}
},
}
// END OF SCOPE
module.llvm.continue_block = previous_continue_block;
module.llvm.exit_block = previous_exit_block;
},
else =>
{
#trap();
@ -14918,6 +15512,8 @@ names: [_][]u8 =
"else_if_complicated",
"basic_shortcircuiting_if",
"shortcircuiting_if",
"field_access_left_assign",
"for_each",
];
[export] main = fn [cc(c)] (argument_count: u32, argv: &&u8, envp: &&u8) s32

View File

@ -8944,6 +8944,8 @@ fn void analyze_statement(Module* module, Scope* scope, Statement* statement, u3
}
}
assert(!local);
analyze_statement(module, &statement->for_each.scope, statement->for_each.predicate, last_line, last_column, last_debug_location);
if (LLVMGetInsertBlock(module->llvm.builder))