From 1739e37ed3db5e95449d9d50d280063c75d385c2 Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Wed, 3 Apr 2024 07:51:08 -0600 Subject: [PATCH] Introduce sliceable structs --- bootstrap/Compilation.zig | 428 ++++++++++++++++--------- bootstrap/backend/llvm.zig | 3 +- bootstrap/frontend/parser.zig | 25 +- lib/std/build.nat | 8 +- lib/std/builtin.nat | 4 + lib/std/os.nat | 26 +- lib/std/os/linux.nat | 8 +- lib/std/std.nat | 22 +- test/standalone/foreach_slice/main.nat | 4 +- test/standalone/slice_len/main.nat | 4 +- test/standalone/sliceable/main.nat | 30 ++ 11 files changed, 372 insertions(+), 190 deletions(-) create mode 100644 test/standalone/sliceable/main.nat diff --git a/bootstrap/Compilation.zig b/bootstrap/Compilation.zig index 5119d85..8acaa77 100644 --- a/bootstrap/Compilation.zig +++ b/bootstrap/Compilation.zig @@ -42,6 +42,13 @@ const Error = struct { node: Node.Index, }; +const SliceField = enum { + pointer, + length, +}; + +const length_field_name = @tagName(SliceField.length); + pub fn createContext(allocator: Allocator, my_allocator: *MyAllocator) !*const Context { const context: *Context = try allocator.create(Context); @@ -3576,10 +3583,11 @@ pub const Instruction = union(enum) { }; pub const GEP = struct { + index: V, pointer: Instruction.Index, base_type: Type.Index, + name: u32, is_struct: bool, - index: V, }; const ExtractValue = struct { @@ -3815,27 +3823,6 @@ pub const Function = struct { expand, }; - // const AbiType = union(enum) { - // type: Type.Index, - // integer_bit_count: u32, - // invalid, - // - // fn is_promotable_integer_or_bool(abi_type: AbiType, unit: *Unit) bool { - // switch (abi_type) { - // .type => |type_index| if (get_integer_or_bool(type_index, unit)) |integer| { - // return integer.bit_count < 32; - // }, - // } - // } - // - // fn get_integer_or_bool(type_index: Type.Index, unit: *Unit) ?Type.Integer { - // return switch (unit.types.get(type_index).*) { - // .integer => true, - // else => false, - // }; - // } - // }; - const AbiAttributes = struct { by_reg: bool = false, zero_extend: bool = false, @@ -3873,6 +3860,18 @@ pub const Struct = struct { pub const Descriptor = struct { scope: Debug.Scope.Global, fields: UnpinnedArray(Struct.Field.Index) = .{}, + options: Options, + }; + pub const Options = struct { + sliceable: ?Sliceable = null, + pub const Id = enum { + sliceable, + }; + }; + + pub const Sliceable = struct { + pointer: u32, + length: u32, }; pub const Field = struct { @@ -4861,7 +4860,7 @@ pub const Builder = struct { _ = try builder.analyzeFile(unit, context, file_index); } - fn analyzeFile(builder: *Builder, unit: *Unit, context: *const Context, file_index: Debug.File.Index) !void { + fn analyzeFile(builder: *Builder, unit: *Unit, context: *const Context, file_index: Debug.File.Index) anyerror!void { const old_function = builder.current_function; builder.current_function = .null; defer builder.current_function = old_function; @@ -5188,7 +5187,7 @@ pub const Builder = struct { assert(declaration.kind == .argument); assert(scope.kind == .function); - const argument_declaration: *Debug.Declaration.Argument =@fieldParentPtr("declaration", declaration); + const argument_declaration: *Debug.Declaration.Argument = @fieldParentPtr("declaration", declaration); const function_scope: *Debug.Scope.Function = @fieldParentPtr("scope", scope); const instruction_index = function_scope.argument_map.get(argument_declaration).?; @@ -6163,12 +6162,7 @@ pub const Builder = struct { } else { const left = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, node.left, .left); const expected_right_type = switch (left.value) { - .runtime => |instr_index| switch (unit.instructions.get(instr_index).*) { - // .global => |global| global.declaration.type, - .stack_slot => |stack_slot| stack_slot.type, - .get_element_pointer => |gep| gep.base_type, - else => |t| @panic(@tagName(t)), - }, + .runtime => unit.types.get(left.type).pointer.type, .@"comptime" => |ct| switch (ct) { .global => |global| global.declaration.type, else => |t| @panic(@tagName(t)), @@ -6524,6 +6518,35 @@ pub const Builder = struct { return result; } + fn get_builtin_declaration(builder: *Builder, unit: *Unit, context: *const Context, name: []const u8) !*Debug.Declaration.Global { + const std_file_index = try builder.resolveImportStringLiteral(unit, context, Type.Expect{ .type = .type }, "std"); + const std_file = unit.files.get(std_file_index); + const std_file_struct_index = unit.types.get(std_file.type).@"struct"; + const std_file_struct = unit.structs.get(std_file_struct_index); + const builtin_hash = try unit.processIdentifier(context, "builtin"); + + const look_in_parent_scopes = false; + if (std_file_struct.kind.@"struct".scope.scope.lookupDeclaration(builtin_hash, look_in_parent_scopes)) |lookup| { + const builtin_declaration = try builder.referenceGlobalDeclaration(unit, context, &std_file_struct.kind.@"struct".scope.scope, lookup.declaration, .{}); + switch (builtin_declaration.initial_value) { + .type => |builtin_type_index| { + const builtin_type_struct_index = unit.types.get(builtin_type_index).@"struct"; + const builtin_type_struct = &unit.structs.get(builtin_type_struct_index).kind.@"struct"; + const hash = try unit.processIdentifier(context, name); + if (builtin_type_struct.scope.scope.lookupDeclaration(hash, look_in_parent_scopes)) |declaration_lookup| { + const declaration_global = try builder.referenceGlobalDeclaration(unit, context, declaration_lookup.scope, declaration_lookup.declaration, .{}); + return declaration_global; + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + } + } else { + @panic("Internal compiler error"); + } + } + fn resolveFunctionPrototype(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index, global_attributes: Debug.Declaration.Global.Attributes) !Type.Index { const node = unit.getNode(node_index); assert(node.id == .function_prototype); @@ -6544,32 +6567,8 @@ pub const Builder = struct { .function_attribute_naked => is_naked = true, .function_attribute_cc => { if (unit.cc_type == .null) { - const std_file_index = try builder.resolveImportStringLiteral(unit, context, Type.Expect{ .type = .type }, "std"); - const std_file = unit.files.get(std_file_index); - const std_file_struct_index = unit.types.get(std_file.type).@"struct"; - const std_file_struct = unit.structs.get(std_file_struct_index); - const builtin_hash = try unit.processIdentifier(context, "builtin"); - - const look_in_parent_scopes = false; - if (std_file_struct.kind.@"struct".scope.scope.lookupDeclaration(builtin_hash, look_in_parent_scopes)) |lookup| { - const builtin_declaration = try builder.referenceGlobalDeclaration(unit, context, &std_file_struct.kind.@"struct".scope.scope, lookup.declaration, .{}); - switch (builtin_declaration.initial_value) { - .type => |builtin_type_index| { - const builtin_type_struct_index = unit.types.get(builtin_type_index).@"struct"; - const builtin_type_struct = &unit.structs.get(builtin_type_struct_index).kind.@"struct"; - const cc_hash = try unit.processIdentifier(context, "CallingConvention"); - if (builtin_type_struct.scope.scope.lookupDeclaration(cc_hash, look_in_parent_scopes)) |cc_lookup| { - const cc_global = try builder.referenceGlobalDeclaration(unit, context, cc_lookup.scope, cc_lookup.declaration, .{}); - unit.cc_type = cc_global.initial_value.type; - } else { - unreachable; - } - }, - else => |t| @panic(@tagName(t)), - } - } else { - @panic("Internal compiler error"); - } + const calling_convention_declaration = try builder.get_builtin_declaration(unit, context, "CallingConvention"); + unit.cc_type = calling_convention_declaration.initial_value.type; } assert(unit.cc_type != .null); @@ -7502,6 +7501,48 @@ pub const Builder = struct { const data: Data = switch (container_type) { .@"struct" => b: { assert(container_node.id == .struct_type); + + var struct_options = Struct.Options{}; + + if (container_node.right != .null) { + const struct_option_nodes = unit.getNodeList(container_node.right); + var struct_options_value = false; + for (struct_option_nodes) |struct_option_node_index| { + const struct_option_node = unit.getNode(struct_option_node_index); + switch (struct_option_node.id) { + .anonymous_container_literal => { + if (struct_options_value) unreachable; + struct_options_value = true; + assert(struct_option_node.left == .null); + const nodes = unit.getNodeList(struct_option_node.right); + const struct_options_declaration = try builder.get_builtin_declaration(unit, context, "StructOptions"); + const struct_options_declaration_type_index = struct_options_declaration.initial_value.type; + const struct_options_literal = try builder.resolveContainerLiteral(unit, context, nodes, struct_options_declaration_type_index); + const constant_struct_index = struct_options_literal.value.@"comptime".constant_struct; + const constant_struct = unit.constant_structs.get(constant_struct_index); + const struct_options_struct_index = unit.types.get(struct_options_declaration_type_index).@"struct"; + const struct_options_struct = unit.structs.get(struct_options_struct_index); + + for (struct_options_struct.kind.@"struct".fields.slice(), constant_struct.fields) |field_index, field_value| { + const field = unit.struct_fields.get(field_index); + const name = unit.getIdentifier(field.name); + const option_id = data_structures.enumFromString(Struct.Options.Id, name) orelse unreachable; + switch (option_id) { + .sliceable => switch (field_value.bool) { + true => struct_options.sliceable = .{ + .pointer = 0, + .length = 1, + }, + false => unreachable, + }, + } + } + }, + else => |t| @panic(@tagName(t)), + } + } + } + const struct_index = try unit.structs.append(context.my_allocator, .{ .kind = .{ .@"struct" = .{ @@ -7518,6 +7559,7 @@ pub const Builder = struct { .file = builder.current_file, }, }, + .options = struct_options, }, }, }); @@ -7530,7 +7572,7 @@ pub const Builder = struct { // Save file type switch (builder.current_scope.kind) { .file => { - const global_scope: *Debug.Scope.Global= @fieldParentPtr("scope", builder.current_scope); + const global_scope: *Debug.Scope.Global = @fieldParentPtr("scope", builder.current_scope); const file: *Debug.File = @fieldParentPtr("scope", global_scope); file.type = type_index; try unit.scope_container_map.put_no_clobber(context.my_allocator, &struct_type.kind.@"struct".scope.scope, type_index); @@ -7555,7 +7597,9 @@ pub const Builder = struct { .kind = .comptime_int, }, else => e: { - const backing_type_index = try builder.resolveType(unit, context, container_node.right); + const node_list = unit.getNodeList(container_node.right); + assert(node_list.len == 1); + const backing_type_index = try builder.resolveType(unit, context, node_list[0]); const backing_type = unit.types.get(backing_type_index); break :e switch (backing_type.*) { .integer => |integer| switch (integer.kind) { @@ -7598,7 +7642,9 @@ pub const Builder = struct { const integer = switch (container_node.right) { .null => unreachable, else => e: { - const backing_type_index = try builder.resolveType(unit, context, container_node.right); + const argument_nodes = unit.getNodeList(container_node.right); + assert(argument_nodes.len == 1); + const backing_type_index = try builder.resolveType(unit, context, argument_nodes[0]); const backing_type = unit.types.get(backing_type_index); break :e switch (backing_type.*) { .integer => |integer| switch (integer.kind) { @@ -7811,6 +7857,9 @@ pub const Builder = struct { }, } + var sliceable_pointer_index: ?u32 = null; + var sliceable_length_index: ?u32 = null; + for (field_nodes.slice(), 0..) |field_node_index, index| { const field_node = unit.getNode(field_node_index); const identifier = unit.getExpectedTokenBytes(field_node.token, .identifier); @@ -7847,8 +7896,23 @@ pub const Builder = struct { .@"struct" => { assert(field_node.id == .container_field); const struct_type = unit.structs.get(ty.@"struct"); - const field_name = unit.getExpectedTokenBytes(field_node.token, .identifier); - const field_name_hash = try unit.processIdentifier(context, field_name); + if (struct_type.kind.@"struct".options.sliceable != null) { + inline for (@typeInfo(SliceField).Enum.fields) |field| { + if (byte_equal(field.name, identifier)) { + const v = @field(SliceField, field.name); + switch (v) { + .pointer => { + assert(sliceable_pointer_index == null); + sliceable_pointer_index = @intCast(index); + }, + .length => { + assert(sliceable_length_index == null); + sliceable_length_index = @intCast(index); + }, + } + } + } + } const field_type = try builder.resolveType(unit, context, field_node.left); const field_default_value: ?V.Comptime = switch (field_node.right) { .null => null, @@ -7856,7 +7920,7 @@ pub const Builder = struct { }; const struct_field = try unit.struct_fields.append(context.my_allocator, .{ - .name = field_name_hash, + .name = hash, .type = field_type, .default_value = field_default_value, }); @@ -7882,6 +7946,17 @@ pub const Builder = struct { }, } } + + switch (container_type) { + .@"struct" => { + const struct_type = unit.structs.get(ty.@"struct"); + if (struct_type.kind.@"struct".options.sliceable) |*sliceable| { + sliceable.pointer = sliceable_pointer_index orelse unreachable; + sliceable.length = sliceable_length_index orelse unreachable; + } + }, + else => {}, + } } if (count.comptime_blocks > 0) { @@ -8278,10 +8353,6 @@ pub const Builder = struct { }); try builder.appendInstruction(unit, context, first_store); - // TODO: should we use this? - // const offset = @divExact(high_offset, high_aligned_size); - // _ = offset; // autofix - const gep = try unit.instructions.append(context.my_allocator, .{ .get_element_pointer = .{ .pointer = stack, @@ -8297,6 +8368,7 @@ pub const Builder = struct { }, .type = .usize, }, + .name = try unit.processIdentifier(context, "direct_pair"), }, }); try builder.appendInstruction(unit, context, gep); @@ -9498,6 +9570,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = try unit.processIdentifier(context, "slice_end_gep"), }, }); try builder.appendInstruction(unit, context, gep); @@ -9614,6 +9687,7 @@ pub const Builder = struct { .is_struct = false, .base_type = slice.child_type, .index = range_start, + .name = try unit.processIdentifier(context, "slice_pointer_gep"), }, }); try builder.appendInstruction(unit, context, pointer_gep); @@ -9667,6 +9741,7 @@ pub const Builder = struct { .is_struct = false, .base_type = pointer.type, .index = range_start, + .name = try unit.processIdentifier(context, "pointer_many_slice"), }, }); try builder.appendInstruction(unit, context, pointer_gep); @@ -9736,6 +9811,7 @@ pub const Builder = struct { .base_type = array.type, .is_struct = false, .index = range_start, + .name = try unit.processIdentifier(context, "array_slice"), }, }); try builder.appendInstruction(unit, context, pointer_gep); @@ -9813,6 +9889,7 @@ pub const Builder = struct { .base_type = child_pointer.type, .is_struct = false, .index = range_start, + .name = try unit.processIdentifier(context, "double_many_pointer_slice"), }, }); try builder.appendInstruction(unit, context, pointer_gep); @@ -9889,6 +9966,7 @@ pub const Builder = struct { .base_type = array.type, .is_struct = false, .index = range_start, + .name = try unit.processIdentifier(context, "double_array_slice"), }, }); try builder.appendInstruction(unit, context, pointer_gep); @@ -9980,6 +10058,7 @@ pub const Builder = struct { .base_type = slice.child_type, .is_struct = false, .index = range_start, + .name = try unit.processIdentifier(context, "slice_ptr_gep"), }, }); try builder.appendInstruction(unit, context, pointer_gep); @@ -10495,76 +10574,26 @@ pub const Builder = struct { assert(node.right != .null); const array_like_expression = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, node.left, .left); - const index = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .usize }, node.right, .right); + const original_index_value = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, node.right, .right); + const index = switch (original_index_value.type) { + .comptime_int => V{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = original_index_value.value.@"comptime".comptime_int.value, + }, + }, + }, + .type = .usize, + }, + else => original_index_value, + }; const gep: V = switch (unit.types.get(array_like_expression.type).*) { .pointer => |pointer| switch (pointer.many) { true => unreachable, false => switch (unit.types.get(pointer.type).*) { - .slice => |slice| b: { - const gep = try unit.instructions.append(context.my_allocator, .{ - .get_element_pointer = .{ - .pointer = array_like_expression.value.runtime, - .base_type = pointer.type, - .is_struct = true, - .index = .{ - .value = .{ - .@"comptime" = .{ - .constant_int = .{ - .value = 0, - }, - }, - }, - .type = .u32, - }, - }, - }); - try builder.appendInstruction(unit, context, gep); - - const pointer_to_slice_pointer = try unit.getPointerType(context, .{ - .type = slice.child_pointer_type, - .mutability = pointer.mutability, - .termination = .none, - .many = false, - .nullable = false, - }); - - const pointer_load = try unit.instructions.append(context.my_allocator, .{ - .load = .{ - .value = .{ - .value = .{ - .runtime = gep, - }, - .type = pointer_to_slice_pointer, - }, - .type = slice.child_pointer_type, - }, - }); - try builder.appendInstruction(unit, context, pointer_load); - - const slice_pointer_gep = try unit.instructions.append(context.my_allocator, .{ - .get_element_pointer = .{ - .pointer = pointer_load, - .base_type = slice.child_type, - .is_struct = false, - .index = index, - }, - }); - try builder.appendInstruction(unit, context, slice_pointer_gep); - - break :b .{ - .value = .{ - .runtime = slice_pointer_gep, - }, - .type = try unit.getPointerType(context, .{ - .type = slice.child_type, - .mutability = slice.mutability, - .many = false, - .nullable = false, - .termination = .none, - }), - }; - }, + .slice => |slice| try builder.build_slice_indexed_access(unit, context, array_like_expression, pointer.type, slice.child_pointer_type, slice.child_type, slice.mutability, .{ .pointer = 0, .length = 1 }, index), .array => |array| b: { const gep = try unit.instructions.append(context.my_allocator, .{ .get_element_pointer = .{ @@ -10572,6 +10601,7 @@ pub const Builder = struct { .base_type = array.type, .is_struct = false, .index = index, + .name = try unit.processIdentifier(context, "indexed_array_gep"), }, }); try builder.appendInstruction(unit, context, gep); @@ -10606,6 +10636,7 @@ pub const Builder = struct { .base_type = child_pointer.type, .is_struct = false, .index = index, + .name = try unit.processIdentifier(context, "indexed_many_pointer"), }, }); try builder.appendInstruction(unit, context, gep); @@ -10641,6 +10672,7 @@ pub const Builder = struct { .base_type = array.type, .is_struct = false, .index = index, + .name = try unit.processIdentifier(context, "indexed_pointer_array"), }, }); try builder.appendInstruction(unit, context, gep); @@ -10678,6 +10710,7 @@ pub const Builder = struct { .base_type = child_pointer.type, .is_struct = false, .index = index, + .name = try unit.processIdentifier(context, "many_pointer_integer"), }, }); try builder.appendInstruction(unit, context, gep); @@ -10699,6 +10732,36 @@ pub const Builder = struct { }, else => |t| @panic(@tagName(t)), }, + .@"struct" => |struct_index| switch (unit.structs.get(struct_index).kind) { + .@"struct" => |*struct_type| b: { + if (struct_type.options.sliceable) |sliceable| { + const load = try unit.instructions.append(context.my_allocator, .{ + .load = .{ + .value = array_like_expression, + .type = pointer.type, + }, + }); + try builder.appendInstruction(unit, context, load); + + const pointer_field_index = struct_type.fields.slice()[sliceable.pointer]; + const pointer_field = unit.struct_fields.get(pointer_field_index); + const pointer_type = unit.types.get(pointer_field.type).pointer; + const child_type_index = pointer_type.type; + + const load_value = V{ + .value = .{ + .runtime = load, + }, + .type = pointer.type, + }; + const v = try builder.build_slice_indexed_access(unit, context, load_value, child_pointer.type, pointer_field.type, child_type_index, pointer_type.mutability, sliceable, index); + break :b v; + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + }, else => |t| @panic(@tagName(t)), }, }, @@ -11017,6 +11080,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = try unit.processIdentifier(context, "union_for_error_gep"), }, }); try builder.appendInstruction(unit, context, union_for_error_gep); @@ -11234,6 +11298,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = try unit.processIdentifier(context, "union_for_error_gep"), }, }); try builder.appendInstruction(unit, context, union_for_error_gep); @@ -11788,6 +11853,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = field.name, }, }); try builder.appendInstruction(unit, context, gep); @@ -11927,6 +11993,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = field.name, }, }); try builder.appendInstruction(unit, context, gep); @@ -12337,6 +12404,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = try unit.processIdentifier(context, "direct_pair_gep0"), }, }); try builder.appendInstruction(unit, context, gep0); @@ -12375,6 +12443,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = try unit.processIdentifier(context, "direct_pair_gep1"), }, }); try builder.appendInstruction(unit, context, gep1); @@ -12942,6 +13011,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = try unit.processIdentifier(context, "slice_for_payload"), }, }); try builder.appendInstruction(unit, context, gep); @@ -13134,14 +13204,6 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), } - // const catch_alloca = try builder.c - // // const catch_alloca = try builder.createStackVariable(unit, context, catch_alloca); - // // .stack_slot = .{ - // // .type = expression.type, - // // },; - // // }); - // try builder.appendInstruction(unit, context, catch_alloca); - const catch_type_expect = Type.Expect{ .type = error_union.type }; const is_error = try unit.instructions.append(context.my_allocator, .{ .extract_value = .{ @@ -13791,7 +13853,7 @@ pub const Builder = struct { .pointer => |pointer| switch (unit.types.get(pointer.type).*) { .array => |array| { assert(side == .right); - assert(byte_equal(identifier, "len")); + assert(byte_equal(identifier, length_field_name)); break :b switch (type_expect) { .type => |type_index| V{ .value = .{ @@ -13818,13 +13880,12 @@ pub const Builder = struct { }; }, .slice => |slice| { - const slice_field: enum { - ptr, - len, - } = if (byte_equal("ptr", identifier)) .ptr else if (byte_equal("len", identifier)) .len else unreachable; + const slice_field: SliceField = inline for (@typeInfo(SliceField).Enum.fields) |field| { + if (byte_equal(field.name, identifier)) break @enumFromInt(field.value); + } else unreachable; const field_type = switch (slice_field) { - .ptr => slice.child_pointer_type, - .len => Type.Index.usize, + .pointer => slice.child_pointer_type, + .length => Type.Index.usize, }; const field_index = @intFromEnum(slice_field); @@ -13843,6 +13904,10 @@ pub const Builder = struct { }, .type = .u32, }, + .name = try unit.processIdentifier(context, switch (slice_field) { + .pointer => "slice_pointer", + .length => "slice_length", + }), }, }); try builder.appendInstruction(unit, context, gep); @@ -13882,7 +13947,7 @@ pub const Builder = struct { }, .pointer => |child_pointer| switch (unit.types.get(child_pointer.type).*) { .array => |array| { - assert(byte_equal(identifier, "len")); + assert(byte_equal(identifier, length_field_name)); break :b switch (type_expect) { .type => |type_index| V{ @@ -13929,6 +13994,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = field.name, }, }); try builder.appendInstruction(unit, context, gep); @@ -14000,6 +14066,7 @@ pub const Builder = struct { }, .type = .u32, }, + .name = field.name, }, }); try builder.appendInstruction(unit, context, gep); @@ -14832,6 +14899,73 @@ pub const Builder = struct { .constant_slice = constant_slice, }; } + + fn build_slice_indexed_access(builder: *Builder, unit: *Unit, context: *const Context, array_like_expression: V, sliceable_type_index: Type.Index, sliceable_pointer_type_index: Type.Index, sliceable_child_type_index: Type.Index, mutability: Mutability, sliceable: Struct.Sliceable, index: V) !V { + const gep = try unit.instructions.append(context.my_allocator, .{ + .get_element_pointer = .{ + .pointer = array_like_expression.value.runtime, + .base_type = sliceable_type_index, + .is_struct = true, + .index = .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = sliceable.pointer, + }, + }, + }, + .type = .u32, + }, + .name = try unit.processIdentifier(context, "slice_pointer_access"), + }, + }); + try builder.appendInstruction(unit, context, gep); + + const pointer_to_slice_pointer = try unit.getPointerType(context, .{ + .type = sliceable_pointer_type_index, + .mutability = mutability, + .termination = .none, + .many = false, + .nullable = false, + }); + + const pointer_load = try unit.instructions.append(context.my_allocator, .{ + .load = .{ + .value = .{ + .value = .{ + .runtime = gep, + }, + .type = pointer_to_slice_pointer, + }, + .type = sliceable_pointer_type_index, + }, + }); + try builder.appendInstruction(unit, context, pointer_load); + + const slice_pointer_gep = try unit.instructions.append(context.my_allocator, .{ + .get_element_pointer = .{ + .pointer = pointer_load, + .base_type = sliceable_child_type_index, + .is_struct = false, + .index = index, + .name = try unit.processIdentifier(context, "indexed_slice_gep"), + }, + }); + try builder.appendInstruction(unit, context, slice_pointer_gep); + + return .{ + .value = .{ + .runtime = slice_pointer_gep, + }, + .type = try unit.getPointerType(context, .{ + .type = sliceable_child_type_index, + .mutability = mutability, + .many = false, + .nullable = false, + .termination = .none, + }), + }; + } }; pub const Enum = struct { diff --git a/bootstrap/backend/llvm.zig b/bootstrap/backend/llvm.zig index f9970a4..a3871c3 100644 --- a/bootstrap/backend/llvm.zig +++ b/bootstrap/backend/llvm.zig @@ -1782,7 +1782,8 @@ pub const LLVM = struct { const base_type = try llvm.getType(unit, context, gep.base_type); const in_bounds = true; if (gep.is_struct and gep.index.type != .u32) unreachable; - const get_element_pointer = llvm.builder.createGEP(base_type, pointer, indices.ptr, indices.len, "gep", "gep".len, in_bounds) orelse unreachable; + const gep_name = unit.getIdentifier(gep.name); + const get_element_pointer = llvm.builder.createGEP(base_type, pointer, indices.ptr, indices.len, gep_name.ptr, gep_name.len, in_bounds) orelse unreachable; try llvm.llvm_instruction_map.put_no_clobber(context.my_allocator, instruction_index, get_element_pointer); return get_element_pointer; } diff --git a/bootstrap/frontend/parser.zig b/bootstrap/frontend/parser.zig index 3cd3e85..575c14e 100644 --- a/bootstrap/frontend/parser.zig +++ b/bootstrap/frontend/parser.zig @@ -1606,7 +1606,10 @@ const Analyzer = struct { }); try list.append(analyzer.my_allocator, field_initialization); - _ = try analyzer.expectToken(.operator_comma); + switch (analyzer.peekToken()) { + .operator_comma => analyzer.consumeToken(), + else => {}, + } break :blk .container_field_names; }, @@ -1708,11 +1711,21 @@ const Analyzer = struct { else => unreachable, }; - const type_node = if (analyzer.hasTokens() and analyzer.peekToken() == .operator_left_parenthesis) b: { + const parameters_node = if (analyzer.hasTokens() and analyzer.peekToken() == .operator_left_parenthesis) b: { analyzer.consumeToken(); - const result = try analyzer.typeExpression(); - _ = try analyzer.expectToken(.operator_right_parenthesis); - break :b result; + var list = UnpinnedArray(Node.Index){}; + while (analyzer.peekToken() != .operator_right_parenthesis) { + const parameter_node = try analyzer.expression(); + try list.append(analyzer.my_allocator, parameter_node); + switch (analyzer.peekToken()) { + .operator_comma => analyzer.consumeToken(), + else => {}, + } + } + + analyzer.consumeToken(); + + break :b try analyzer.nodeList(list); } else Node.Index.null; if (maybe_token_id) |_| _ = try analyzer.expectToken(.operator_left_brace); @@ -1819,7 +1832,7 @@ const Analyzer = struct { .id = node_id, .token = token_i, .left = try analyzer.nodeList(node_list), - .right = type_node, + .right = parameters_node, }); } diff --git a/lib/std/build.nat b/lib/std/build.nat index f8ad4f2..b1e35ca 100644 --- a/lib/std/build.nat +++ b/lib/std/build.nat @@ -36,12 +36,12 @@ const Executable = struct{ link_libc_arg = "false"; } - if (executable.c_source_files.len > 0) { - assert(executable.c_source_files.len == 1); - const argv = [_:null] ?[&:0]const u8{ compiler_path, "exe", "-main_source_file", executable.main_source_path.ptr, "-link_libc", link_libc_arg, "-name", executable.name.ptr, "-c_source_files", executable.c_source_files[0].ptr }; + if (executable.c_source_files.length > 0) { + assert(executable.c_source_files.length == 1); + const argv = [_:null] ?[&:0]const u8{ compiler_path, "exe", "-main_source_file", executable.main_source_path.pointer, "-link_libc", link_libc_arg, "-name", executable.name.pointer, "-c_source_files", executable.c_source_files[0].pointer }; try std.os.execute(path = compiler_path, argv = argv.&, env = std.start.environment_values); } else { - const argv = [_:null] ?[&:0]const u8{ compiler_path, "exe", "-main_source_file", executable.main_source_path.ptr, "-link_libc", link_libc_arg, "-name", executable.name.ptr }; + const argv = [_:null] ?[&:0]const u8{ compiler_path, "exe", "-main_source_file", executable.main_source_path.pointer, "-link_libc", link_libc_arg, "-name", executable.name.pointer }; try std.os.execute(path = compiler_path, argv = argv.&, env = std.start.environment_values); } } else { diff --git a/lib/std/builtin.nat b/lib/std/builtin.nat index ec7aaac..e952fd3 100644 --- a/lib/std/builtin.nat +++ b/lib/std/builtin.nat @@ -33,3 +33,7 @@ const TestFunction = struct{ name: []const u8, function: &const fn () *!void, }; + +const StructOptions = struct{ + sliceable: bool = false, +}; diff --git a/lib/std/os.nat b/lib/std/os.nat index 8f215fa..76c76b9 100644 --- a/lib/std/os.nat +++ b/lib/std/os.nat @@ -44,10 +44,10 @@ const FileDescriptor = struct{ }; const read = fn(file_descriptor: FileDescriptor, bytes: []u8) ReadError!usize { - if (bytes.len > 0) { + if (bytes.length > 0) { switch (current) { .linux => { - const len: usize = #min(max_file_operation_byte_count, bytes.len); + const len: usize = #min(max_file_operation_byte_count, bytes.length); const syscall_result = system.read(file_descriptor, bytes); const byte_count = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, @@ -55,7 +55,7 @@ const FileDescriptor = struct{ return byte_count; }, .macos => { - const len: usize = #min(max_file_operation_byte_count, bytes.len); + const len: usize = #min(max_file_operation_byte_count, bytes.length); const syscall_result = system.read(file_descriptor, bytes); const byte_count = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, @@ -76,16 +76,16 @@ const FileDescriptor = struct{ const write = fn (file_descriptor: FileDescriptor, bytes: []const u8) WriteError!usize { switch (current) { .linux => { - const len: usize = #min(max_file_operation_byte_count, bytes.len); - const syscall_result = system.write(file_descriptor.handle, bytes[0..len]); + const length: usize = #min(max_file_operation_byte_count, bytes.length); + const syscall_result = system.write(file_descriptor.handle, bytes[0..length]); const byte_count = unwrap_syscall(syscall_result) catch |err| switch (err) { else => return WriteError.write_failed, }; return byte_count; }, .macos => { - const len: usize = #min(max_file_operation_byte_count, bytes.len); - const syscall_result = system.write(file_descriptor.handle, bytes.ptr, bytes.len); + const length: usize = #min(max_file_operation_byte_count, bytes.length); + const syscall_result = system.write(file_descriptor.handle, bytes.pointer, length); const byte_count = unwrap_syscall(syscall_result) catch |err| switch (err) { else => return WriteError.write_failed, }; @@ -170,7 +170,7 @@ const free_virtual_memory = fn(bytes: []const u8) FreeError!void { }; }, .macos => { - const syscall_result = system.munmap(bytes.ptr, bytes.len); + const syscall_result = system.munmap(bytes.pointer, bytes.length); _ = unwrap_syscall(syscall_result) catch |err| switch (err) { else => unreachable, }; @@ -218,19 +218,19 @@ const current_executable_path = fn(buffer: [:0]u8) CurrentExecutablePath![]u8 { }, .macos => { var symlink_path_buffer: [max_path_byte_count:0]u8 = undefined; - var symlink_path_len: u32 = symlink_path_buffer.len + 1; + var symlink_path_len: u32 = symlink_path_buffer.length + 1; const ns_result = c._NSGetExecutablePath(symlink_path_buffer.&, symlink_path_len.&); if (ns_result == 0) { const symlink_path = symlink_path_buffer[0..symlink_path_len]; - if (c.realpath(symlink_path.ptr, buffer.ptr)) |result| { + if (c.realpath(symlink_path.pointer, buffer.pointer)) |result| { var i: usize = 0; - while (i < buffer.len) { + while (i < buffer.length) { if (result[i] == 0) { break; } i += 1; } - assert(i < buffer.len); + assert(i < buffer.length); const r: []u8 = result[0..i]; return r; @@ -373,7 +373,7 @@ const PollFileDescriptor = system.PollFileDescriptor; const poll = fn(file_descriptors: []PollFileDescriptor, timeout: s32) ?usize { switch (current) { .linux => { - if (linux.unwrap_syscall(syscall_result = linux.poll(file_descriptors = file_descriptors.ptr, file_descriptor_count = file_descriptors.len, timeout = timeout))) |result| { + if (linux.unwrap_syscall(syscall_result = linux.poll(file_descriptors = file_descriptors.pointer, file_descriptor_count = file_descriptors.length, timeout = timeout))) |result| { return result; } else { return null; diff --git a/lib/std/os/linux.nat b/lib/std/os/linux.nat index 15ed190..1727865 100644 --- a/lib/std/os/linux.nat +++ b/lib/std/os/linux.nat @@ -873,12 +873,12 @@ const mmap = fn(address: ?[&]u8, length: usize, protection_flags: ProtectionFlag } const munmap = fn(bytes: []const u8) usize { - const result = #syscall(#cast(Syscall.munmap), #cast(bytes.ptr), bytes.len); + const result = #syscall(#cast(Syscall.munmap), #cast(bytes.pointer), bytes.length); return result; } const readlink = fn(file_path: [&:0]const u8, bytes: []u8) usize { - const result = #syscall(#cast(Syscall.readlink), #cast(file_path), #cast(bytes.ptr), bytes.len); + const result = #syscall(#cast(Syscall.readlink), #cast(file_path), #cast(bytes.pointer), bytes.length); return result; } @@ -913,12 +913,12 @@ const openat = fn(directory_file_descriptor: FileDescriptor, path: [&:0]const u8 } const read = fn(file_descriptor: FileDescriptor, bytes: []u8) usize { - const result = #syscall(#cast(Syscall.read), #cast(file_descriptor), #cast(bytes.ptr), bytes.len); + const result = #syscall(#cast(Syscall.read), #cast(file_descriptor), #cast(bytes.pointer), bytes.length); return result; } const write = fn(file_descriptor: FileDescriptor, bytes: []const u8) usize { - const result = #syscall(#cast(Syscall.write), #cast(file_descriptor), #cast(bytes.ptr), bytes.len); + const result = #syscall(#cast(Syscall.write), #cast(file_descriptor), #cast(bytes.pointer), bytes.length); return result; } diff --git a/lib/std/std.nat b/lib/std/std.nat index eb7d7fb..5a0d7af 100644 --- a/lib/std/std.nat +++ b/lib/std/std.nat @@ -31,7 +31,7 @@ const print = fn(bytes: []const u8) void { } const format_usize = fn(n: usize, buffer: &[65]u8) []u8 { - var index: usize = buffer.len; + var index: usize = buffer.length; var absolute = n; while (true) { @@ -53,7 +53,7 @@ const format_usize = fn(n: usize, buffer: &[65]u8) []u8 { const print_usize = fn(n: usize) void { var buffer: [65]u8 = undefined; const bytes = format_usize(n, buffer = buffer.&); - assert(bytes.len < buffer.len); + assert(bytes.length < buffer.length); const file_descriptor = os.StdFileDescriptor.get(descriptor = .stdout); const file_writer = FileWriter{ .descriptor = file_descriptor, @@ -79,11 +79,11 @@ const Allocator = struct { } const free = fn (allocator: &Allocator, bytes: []const u8) Allocator.Error!void { - _ = try allocator.handler(allocator, old_ptr = bytes.ptr, old_size = bytes.len, new_size = 0, alignment = 0); + _ = try allocator.handler(allocator, old_ptr = bytes.pointer, old_size = bytes.length, new_size = 0, alignment = 0); } const duplicate_bytes = fn (allocator: &Allocator, bytes: []const u8) Allocator.Error![]u8 { - const result = try allocator.allocate(size = bytes.len, alignment = 0); + const result = try allocator.allocate(size = bytes.length, alignment = 0); copy_bytes(destination = result, source = bytes); return result; } @@ -154,18 +154,18 @@ const FileWriter = struct{ const write_all = fn(file_writer: FileWriter, bytes: []const u8) Writer.Error!void { var bytes_written: usize = 0; - while (bytes_written < bytes.len) { + while (bytes_written < bytes.length) { const iteration_written_byte_count = try file_writer.write(bytes = bytes[bytes_written..]); bytes_written += iteration_written_byte_count; } - assert(bytes_written == bytes.len); + assert(bytes_written == bytes.length); } }; const copy_bytes = fn(destination: []u8, source: []const u8) void { - assert(ok = destination.len == source.len); - for (0..destination.len) |i| { + assert(ok = destination.length == source.length); + for (0..destination.length) |i| { destination[i] = source[i]; } } @@ -173,14 +173,14 @@ const copy_bytes = fn(destination: []u8, source: []const u8) void { const concatenate_bytes = fn(allocator: &Allocator, slices: []const []const u8) Allocator.Error![]u8 { var total_byte_count: usize = 0; for (slices) |slice| { - total_byte_count += slice.len; + total_byte_count += slice.length; } const bytes = try allocator.allocate(total_byte_count, 1); var offset: usize = 0; for (slice) |slice| { - copy_bytes(bytes[offset..][0..slice.len], slice); - offset += slice.len; + copy_bytes(bytes[offset..][0..slice.length], slice); + offset += slice.length; } return bytes; diff --git a/test/standalone/foreach_slice/main.nat b/test/standalone/foreach_slice/main.nat index de1d761..77ad2ab 100644 --- a/test/standalone/foreach_slice/main.nat +++ b/test/standalone/foreach_slice/main.nat @@ -5,7 +5,7 @@ const count_slice_byte_count = fn(slices: []const []const u8) usize { var byte_count: usize = 0; for (slices) |slice| { - byte_count += slice.len; + byte_count += slice.length; } return byte_count; @@ -27,7 +27,7 @@ const Error = error{ const main = fn () Error!void { const a = [_]u8{1, 1, 4, 5, 6}; const b = [_]u8{1, 4, 6}; - const expected_result: usize = a.len + b.len; + const expected_result: usize = a.length + b.length; const result = count_slice_byte_count(slices = .{a.&, b.&}.&); print_values(slice = a.&); if (expected_result - result != 0) { diff --git a/test/standalone/slice_len/main.nat b/test/standalone/slice_len/main.nat index 4ab85ee..a5743cf 100644 --- a/test/standalone/slice_len/main.nat +++ b/test/standalone/slice_len/main.nat @@ -8,8 +8,8 @@ const Error = error{ const main = fn() Error!void { var buffer: [65]u8 = undefined; const slice = foo(5, buffer.&); - assert(slice.len + 5 == buffer.len); - const result: u32 = #cast(slice.len + 5 - buffer.len); + assert(slice.length + 5 == buffer.length); + const result: u32 = #cast(slice.length + 5 - buffer.length); if (result != 0) { return Error.unexpected_result; } diff --git a/test/standalone/sliceable/main.nat b/test/standalone/sliceable/main.nat new file mode 100644 index 0000000..936bcc8 --- /dev/null +++ b/test/standalone/sliceable/main.nat @@ -0,0 +1,30 @@ +const std = #import("std"); +const expect = std.testing.expect; +const assert = std.assert; + +const Foo = struct(.{ .sliceable = true }) { + pointer: [&]u8, + length: u32, + capacity: u32, + + const add = fn (foo: &Foo, item: u8) void { + const index = foo.length; + assert(index < foo.capacity); + foo.length += 1; + foo[index] = item; + } +}; + +const main = fn () *!void { + var foo = [1]u8{0}; + var s = Foo{ + .pointer = foo.&, + .length = 0, + .capacity = 1, + }; + + s.add(5); + + try expect(s.length == 1); + try expect(foo[0] == 5); +}