More enum metaprogramming

This commit is contained in:
David Gonzalez Martin 2024-04-18 21:46:48 -06:00
parent 6b63c21e03
commit 767c9d3b7a
3 changed files with 284 additions and 42 deletions

View File

@ -3916,6 +3916,7 @@ pub const Function = struct {
body: Debug.Block.Index, body: Debug.Block.Index,
return_pointer: Instruction.Index = .null, return_pointer: Instruction.Index = .null,
alloca_index: u32 = 1, alloca_index: u32 = 1,
has_debug_info: bool,
pub const List = BlockList(@This(), enum {}); pub const List = BlockList(@This(), enum {});
pub usingnamespace @This().List.Index; pub usingnamespace @This().List.Index;
@ -3933,9 +3934,6 @@ pub const Function = struct {
attributes: Attributes = .{}, attributes: Attributes = .{},
calling_convention: CallingConvention = .auto, calling_convention: CallingConvention = .auto,
has_polymorphic_parameters: bool = false, has_polymorphic_parameters: bool = false,
// comptime_parameter_declarations: []const ComptimeParameterDeclaration = &.{},
// comptime_parameter_instantiations: []const V.Comptime = &.{},
// is_member: bool = false,
const Attributes = struct { const Attributes = struct {
naked: bool = false, naked: bool = false,
@ -4872,7 +4870,241 @@ pub const Builder = struct {
}), }),
}; };
}, },
.name => {
assert(argument_node_list.len == 1);
const v = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, argument_node_list[0], .right);
switch (v.value) {
.runtime => switch (unit.types.get(v.type).*) {
.integer => |*integer| switch (integer.kind) {
.@"enum" => {
const name_function = try builder.get_name_function(unit, context, v.type);
var args = try UnpinnedArray(V).initialize_with_capacity(context.my_allocator, 1);
args.append_with_capacity(v);
const call = try unit.instructions.append(context.my_allocator, .{
.call = .{
.callable = .{
.value = .{
.@"comptime" = .{
.global = name_function,
},
},
.type = name_function.declaration.type,
},
.function_type = name_function.declaration.type,
.arguments = args.slice(),
},
});
try builder.appendInstruction(unit, context, call);
return V{
.value = .{
.runtime = call,
},
.type = unit.function_prototypes.get(unit.types.get(name_function.declaration.type).function).return_type,
};
},
else => |t| @panic(@tagName(t)), else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
}
},
else => |t| @panic(@tagName(t)),
}
}
fn get_name_function(builder: *Builder, unit: *Unit, context: *const Context, type_index: Type.Index) !*Debug.Declaration.Global {
if (unit.name_functions.get(type_index)) |result| return result else {
var argument_types = try UnpinnedArray(Type.Index).initialize_with_capacity(context.my_allocator, 1);
argument_types.append_with_capacity(type_index);
const return_type_index = try unit.getSliceType(context, .{
.child_pointer_type = try unit.getPointerType(context, .{
.type = .u8,
// TODO: zero-terminate?
.termination = .none,
.mutability = .@"const",
.many = true,
.nullable = false,
}),
.child_type = .u8,
// TODO: zero-terminate?
.termination = .none,
.mutability = .@"const",
.nullable = false,
});
const function_prototype_index = try unit.function_prototypes.append(context.my_allocator, .{
.argument_types = argument_types.slice(),
.return_type = return_type_index,
.abi = .{
.return_type = return_type_index,
.parameter_types = argument_types.slice(),
},
});
const function_type_index = try unit.types.append(context.my_allocator, .{
.function = function_prototype_index,
});
const function_definition_index = try unit.function_definitions.append(context.my_allocator, .{
.scope = .{
.scope = .{
.file = builder.current_file,
.line = 0,
.column = 0,
.kind = .function,
.local = true,
.level = builder.current_scope.level + 1,
},
},
.type = function_type_index,
.body = .null,
.has_debug_info = false,
});
const function_definition = unit.function_definitions.get(function_definition_index);
const old_scope = builder.current_scope;
builder.current_scope = &function_definition.scope.scope;
defer builder.current_scope = old_scope;
const old_function = builder.current_function;
builder.current_function = function_definition_index;
defer builder.current_function = old_function;
const old_basic_block = builder.current_basic_block;
defer builder.current_basic_block = old_basic_block;
const argument_name_hash = try unit.processIdentifier(context, "_enum_value_");
const argument_declaration_index = try unit.argument_declarations.append(context.my_allocator, .{
.declaration = .{
.scope = builder.current_scope,
.name = argument_name_hash,
.type = type_index,
.mutability = .@"const",
.line = 0,
.column = 0,
.kind = .argument,
},
.index = 0,
});
comptime assert(@TypeOf(argument_declaration_index) == Debug.Declaration.Argument.Index);
const argument = unit.argument_declarations.get(argument_declaration_index);
try builder.current_scope.declarations.put_no_clobber(context.my_allocator, argument_name_hash, &argument.declaration);
const entry_block = try builder.newBasicBlock(unit, context);
const exit_block = try builder.newBasicBlock(unit, context);
builder.current_basic_block = entry_block;
const argument_instruction = try unit.instructions.append(context.my_allocator, .{
.abi_argument = 0,
});
try builder.appendInstruction(unit, context, argument_instruction);
const switch_instruction_index = try unit.instructions.append(context.my_allocator, .{
.@"switch" = .{
.condition = .{
.value = .{
.runtime = argument_instruction,
},
.type = type_index,
},
.block_type = return_type_index,
},
});
try builder.appendInstruction(unit, context, switch_instruction_index);
const switch_instruction = &unit.instructions.get(switch_instruction_index).@"switch";
const phi_instruction_index = try unit.instructions.append(context.my_allocator, .{
.phi = .{
.type = return_type_index,
},
});
const phi = &unit.instructions.get(phi_instruction_index).phi;
const cases = switch (unit.types.get(type_index).*) {
.integer => |*integer| switch (integer.kind) {
.@"enum" => |*enum_type| b: {
var cases = try UnpinnedArray(Instruction.Switch.Case).initialize_with_capacity(context.my_allocator, enum_type.fields.length);
for (enum_type.fields.slice()) |enum_field_index| {
builder.current_basic_block = entry_block;
const enum_field = unit.enum_fields.get(enum_field_index);
const case_block = try builder.newBasicBlock(unit, context);
builder.current_basic_block = case_block;
const identifier = unit.getIdentifier(enum_field.name);
const identifier_z = try context.allocator.dupeZ(u8, identifier);
const string_literal = try builder.processStringLiteralFromStringAndDebugInfo(unit, context, identifier_z, .{
.line = 0,
.column = 0,
});
const slice = try unit.constant_slices.append(context.my_allocator,.{
.array = string_literal,
.start = 0,
.end = identifier_z.len,
.type = return_type_index,
});
const v = V{
.value = .{
.@"comptime" = .{
.constant_slice = slice,
},
},
.type = return_type_index,
};
try phi.addIncoming(context, v, builder.current_basic_block);
try builder.jump(unit, context, exit_block);
const case = Instruction.Switch.Case{
.condition = .{
.enum_value = enum_field_index,
},
.basic_block = case_block,
};
cases.append_with_capacity(case);
}
break :b cases;
},
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
};
switch_instruction.cases = cases;
switch_instruction.else_block = try builder.create_unreachable_block(unit, context);
builder.current_basic_block = exit_block;
try builder.appendInstruction(unit, context, phi_instruction_index);
const ret = try unit.instructions.append(context.my_allocator, .{
.ret = .{
.type = return_type_index,
.value = .{
.runtime = phi_instruction_index,
},
},
});
try builder.appendInstruction(unit, context, ret);
const global_index = try unit.global_declarations.append(context.my_allocator, .{
.declaration = .{
.scope = builder.current_scope,
.name = try unit.processIdentifier(context, try std.fmt.allocPrint(context.allocator, "get_enum_name_{}", .{@intFromEnum(type_index)})),
.type = function_type_index,
.line = 0,
.column = 0,
.mutability = .@"const",
.kind = .global,
},
.initial_value = .{
.function_definition = function_definition_index,
},
.type_node_index = .null,
.attributes = .{},
});
const global = unit.global_declarations.get(global_index);
try unit.code_to_emit.put_no_clobber(context.my_allocator, function_definition_index, global);
try unit.name_functions.put_no_clobber(context.my_allocator, type_index, global);
return global;
} }
} }
@ -8662,6 +8894,7 @@ pub const Builder = struct {
.file = builder.current_file, .file = builder.current_file,
}, },
}, },
.has_debug_info = true,
}); });
defer builder.current_function = old_function; defer builder.current_function = old_function;
@ -14626,11 +14859,7 @@ pub const Builder = struct {
} }
if (switch_instruction.else_block == .null) { if (switch_instruction.else_block == .null) {
switch_instruction.else_block = try builder.newBasicBlock(unit, context); switch_instruction.else_block = try builder.create_unreachable_block(unit, context);
const old_block = builder.current_basic_block;
builder.current_basic_block = switch_instruction.else_block;
try builder.buildUnreachable(unit, context);
builder.current_basic_block = old_block;
} }
if (phi_info) |phi| { if (phi_info) |phi| {
@ -14685,6 +14914,16 @@ pub const Builder = struct {
} }
} }
fn create_unreachable_block(builder: *Builder, unit: *Unit, context: *const Context) !BasicBlock.Index {
const block = try builder.newBasicBlock(unit, context);
const old_block = builder.current_basic_block;
builder.current_basic_block = block;
try builder.buildUnreachable(unit, context);
builder.current_basic_block = old_block;
return block;
}
fn resolveFieldAccess(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, node_index: Node.Index, side: Side, new_parameters: []const V.Comptime) !V { fn resolveFieldAccess(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, node_index: Node.Index, side: Side, new_parameters: []const V.Comptime) !V {
const node = unit.getNode(node_index); const node = unit.getNode(node_index);
const right_node = unit.getNode(node.right); const right_node = unit.getNode(node.right);
@ -14804,6 +15043,23 @@ pub const Builder = struct {
break :b result; break :b result;
}, },
.global => |global| switch (unit.types.get(global.declaration.type).*) {
.array => |array| if (byte_equal(identifier, length_field_name)) switch (type_expect) {
.none => V{
.value = .{
.@"comptime" = .{
.comptime_int = .{
.value = array.count,
.signedness = .unsigned,
},
},
},
.type = .comptime_int,
},
else => |t| @panic(@tagName(t)),
} else unreachable,
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)), else => |t| @panic(@tagName(t)),
}, },
.runtime => |_| b: { .runtime => |_| b: {
@ -16015,6 +16271,7 @@ pub const Unit = struct {
error_unions: MyHashMap(Type.Error.Union.Descriptor, Type.Index) = .{}, error_unions: MyHashMap(Type.Error.Union.Descriptor, Type.Index) = .{},
two_structs: MyHashMap([2]Type.Index, Type.Index) = .{}, two_structs: MyHashMap([2]Type.Index, Type.Index) = .{},
fields_array: MyHashMap(Type.Index, *Debug.Declaration.Global) = .{}, fields_array: MyHashMap(Type.Index, *Debug.Declaration.Global) = .{},
name_functions: MyHashMap(Type.Index, *Debug.Declaration.Global) = .{},
error_count: u32 = 0, error_count: u32 = 0,
code_to_emit: MyHashMap(Function.Definition.Index, *Debug.Declaration.Global) = .{}, code_to_emit: MyHashMap(Function.Definition.Index, *Debug.Declaration.Global) = .{},

View File

@ -2270,29 +2270,6 @@ pub const LLVM = struct {
try llvm.setCallOrFunctionAttributes(unit, context, function_prototype, .{ try llvm.setCallOrFunctionAttributes(unit, context, function_prototype, .{
.function = function, .function = function,
}); });
// const calling_convention = getCallingConvention(function_prototype.calling_convention);
// function.setCallingConvention(calling_convention);
//
// const function_attribute_set = llvm.getFunctionAttributes(unit, context, function_prototype);
//
// var parameter_attribute_sets = try UnpinnedArray(*const LLVM.Attribute.Set).initialize_with_capacity(context.my_allocator, @intCast(function_prototype.abi.parameter_types_abi.len + @intFromBool(function_prototype.abi.return_type_abi.kind == .indirect)));
// const return_attribute_set = blk: {
// const attribute_set = try llvm.emitParameterAttributes(unit, context, function_prototype.abi.return_type_abi, true);
// break :blk switch (function_prototype.abi.return_type_abi.kind) {
// .indirect => b: {
// parameter_attribute_sets.append_with_capacity(attribute_set);
// break :b llvm.context.getAttributeSet(null, 0);
// },
// else => attribute_set,
// };
// };
//
// for (function_prototype.abi.parameter_types_abi) |parameter_type_abi| {
// const parameter_attribute_set = try llvm.emitParameterAttributes(unit, context, parameter_type_abi, false);
// parameter_attribute_sets.append_with_capacity(parameter_attribute_set);
// }
// function.setAttributes(llvm.context, function_attribute_set, return_attribute_set, parameter_attribute_sets.pointer, parameter_attribute_sets.length);
switch (declaration.initial_value) { switch (declaration.initial_value) {
.function_declaration => try llvm.function_declaration_map.put_no_clobber(context.my_allocator, declaration, function), .function_declaration => try llvm.function_declaration_map.put_no_clobber(context.my_allocator, declaration, function),
@ -2300,7 +2277,13 @@ pub const LLVM = struct {
else => unreachable, else => unreachable,
} }
if (unit.descriptor.generate_debug_information) { const generate_debug_information = unit.descriptor.generate_debug_information and switch (declaration.initial_value) {
.function_declaration => true,
.function_definition => |function_definition_index| unit.function_definitions.get(function_definition_index).has_debug_info,
else => unreachable,
};
if (generate_debug_information) {
// if (data_structures.byte_equal(name, "nat_split_struct_ints")) @breakpoint(); // if (data_structures.byte_equal(name, "nat_split_struct_ints")) @breakpoint();
const debug_file = try llvm.getDebugInfoFile(unit, context, declaration.declaration.scope.file); const debug_file = try llvm.getDebugInfoFile(unit, context, declaration.declaration.scope.file);
var parameter_types = try UnpinnedArray(*LLVM.DebugInfo.Type).initialize_with_capacity(context.my_allocator, @intCast(function_prototype.argument_types.len)); var parameter_types = try UnpinnedArray(*LLVM.DebugInfo.Type).initialize_with_capacity(context.my_allocator, @intCast(function_prototype.argument_types.len));
@ -2522,7 +2505,9 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
llvm.sema_function = function_declaration; llvm.sema_function = function_declaration;
llvm.inside_branch = false; llvm.inside_branch = false;
if (unit.descriptor.generate_debug_information) { const generate_debug_information = unit.descriptor.generate_debug_information and function_definition.has_debug_info;
if (generate_debug_information) {
const subprogram = llvm.function.getSubprogram() orelse unreachable; const subprogram = llvm.function.getSubprogram() orelse unreachable;
llvm.file = subprogram.getFile() orelse unreachable; llvm.file = subprogram.getFile() orelse unreachable;
llvm.scope = subprogram.toLocalScope().toScope(); llvm.scope = subprogram.toLocalScope().toScope();
@ -2552,7 +2537,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
switch (sema_instruction.*) { switch (sema_instruction.*) {
.push_scope => |push_scope| { .push_scope => |push_scope| {
if (unit.descriptor.generate_debug_information) { if (generate_debug_information) {
const old_scope = try llvm.getScope(unit, context, push_scope.old); const old_scope = try llvm.getScope(unit, context, push_scope.old);
assert(@intFromEnum(push_scope.old.kind) >= @intFromEnum(Compilation.Debug.Scope.Kind.function)); assert(@intFromEnum(push_scope.old.kind) >= @intFromEnum(Compilation.Debug.Scope.Kind.function));
@ -2562,7 +2547,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
} }
}, },
.pop_scope => |pop_scope| { .pop_scope => |pop_scope| {
if (unit.descriptor.generate_debug_information) { if (generate_debug_information) {
const new = try llvm.getScope(unit, context, pop_scope.new); const new = try llvm.getScope(unit, context, pop_scope.new);
if (pop_scope.new.kind == .function) { if (pop_scope.new.kind == .function) {
assert(new.toSubprogram() orelse unreachable == llvm.function.getSubprogram() orelse unreachable); assert(new.toSubprogram() orelse unreachable == llvm.function.getSubprogram() orelse unreachable);
@ -2577,7 +2562,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
} }
}, },
.debug_checkpoint => |debug_checkpoint| { .debug_checkpoint => |debug_checkpoint| {
if (unit.descriptor.generate_debug_information) { if (generate_debug_information) {
const scope = try llvm.getScope(unit, context, debug_checkpoint.scope); const scope = try llvm.getScope(unit, context, debug_checkpoint.scope);
llvm.builder.setCurrentDebugLocation(llvm.context, debug_checkpoint.line + 1, debug_checkpoint.column + 1, scope, llvm.function); llvm.builder.setCurrentDebugLocation(llvm.context, debug_checkpoint.line + 1, debug_checkpoint.column + 1, scope, llvm.function);
} }
@ -2921,7 +2906,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
try llvm.llvm_instruction_map.put_no_clobber(context.my_allocator, instruction_index, argument.toValue()); try llvm.llvm_instruction_map.put_no_clobber(context.my_allocator, instruction_index, argument.toValue());
}, },
.debug_declare_argument => |debug_declare| { .debug_declare_argument => |debug_declare| {
if (unit.descriptor.generate_debug_information) { if (generate_debug_information) {
const argument_index: c_uint = debug_declare.argument.index; const argument_index: c_uint = debug_declare.argument.index;
const declaration = &debug_declare.argument.declaration; const declaration = &debug_declare.argument.declaration;
const debug_declaration_type = try llvm.getDebugType(unit, context, declaration.type); const debug_declaration_type = try llvm.getDebugType(unit, context, declaration.type);
@ -2966,7 +2951,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
} }
}, },
.debug_declare_local_variable => |declare_local_variable| { .debug_declare_local_variable => |declare_local_variable| {
if (unit.descriptor.generate_debug_information) { if (generate_debug_information) {
const local_variable = declare_local_variable.variable; const local_variable = declare_local_variable.variable;
const debug_declaration_type = try llvm.getDebugType(unit, context, local_variable.declaration.type); const debug_declaration_type = try llvm.getDebugType(unit, context, local_variable.declaration.type);
const always_preserve = true; const always_preserve = true;
@ -3190,7 +3175,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
@panic("Function block with no termination"); @panic("Function block with no termination");
} }
if (unit.descriptor.generate_debug_information) { if (generate_debug_information) {
llvm.debug_info_builder.finalizeSubprogram(llvm.function.getSubprogram() orelse unreachable, llvm.function); llvm.debug_info_builder.finalizeSubprogram(llvm.function.getSubprogram() orelse unreachable, llvm.function);
} }

View File

@ -10,15 +10,15 @@ const Enum = enum(u32) {
const main = fn () *!void { const main = fn () *!void {
var result: u32 = 0; var result: u32 = 0;
for (#fields(Enum)) |e| { for (#fields(Enum)) |e| {
//print(#name(e)); print(#name(e));
//print(": "); print(": ");
const value: u32 = #cast(e); const value: u32 = #cast(e);
print_usize(value); print_usize(value);
print("\n"); print("\n");
result += value; result += value;
} }
//try expect(#fields(Enum).length == 3); try expect(#fields(Enum).length == 3);
try expect(result == 3); try expect(result == 3);
} }