From 73e6b6529b301349820a1d2f4bc84712a17d6531 Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Sat, 22 Feb 2025 18:59:45 -0600 Subject: [PATCH] Implement basic global variables --- src/LLVM.zig | 45 +++++- src/converter.zig | 303 ++++++++++++++++++++++++++--------------- src/converter_test.zig | 4 + src/llvm.cpp | 11 ++ src/llvm_api.zig | 5 + tests/global.bbb | 5 + 6 files changed, 261 insertions(+), 112 deletions(-) create mode 100644 tests/global.bbb diff --git a/src/LLVM.zig b/src/LLVM.zig index d7bed00..4bd6546 100644 --- a/src/LLVM.zig +++ b/src/LLVM.zig @@ -511,7 +511,7 @@ pub const Module = opaque { return api.llvm_module_to_string(module).to_slice().?; } - const FunctionCreate = struct { + pub const FunctionCreate = struct { type: *Type.Function, linkage: LinkageType, address_space: c_uint = 0, @@ -522,6 +522,22 @@ pub const Module = opaque { return api.llvm_module_create_function(module, create.type, create.linkage, create.address_space, String.from_slice(create.name)); } + pub const GlobalCreate = struct { + type: *Type, + initial_value: *Constant, + name: []const u8, + before: ?*GlobalVariable = null, + address_space: c_uint = 0, + linkage: LinkageType, + thread_local_mode: ThreadLocalMode = .none, + is_constant: bool = false, + externally_initialized: bool = false, + }; + + pub fn create_global_variable(module: *Module, create: GlobalCreate) *GlobalVariable { + return api.llvm_module_create_global_variable(module, create.type, create.is_constant, create.linkage, create.initial_value, String.from_slice(create.name), create.before, create.thread_local_mode, create.address_space, create.externally_initialized); + } + pub fn verify(module: *Module) VerifyResult { var result: VerifyResult = undefined; var string: String = undefined; @@ -618,6 +634,13 @@ pub const GlobalValue = opaque { pub const get_type = api.LLVMGlobalGetValueType; }; +pub const GlobalVariable = opaque { + pub const add_debug_info = api.llvm_global_variable_add_debug_info; + pub fn to_value(global_variable: *GlobalVariable) *Value { + return @ptrCast(global_variable); + } +}; + pub const Function = opaque { pub fn get_type(function: *Function) *Type.Function { return function.to_global_value().get_type().to_function(); @@ -656,6 +679,15 @@ pub const Constant = opaque { pub const Value = opaque { pub const get_type = api.LLVMTypeOf; + + pub fn is_constant(value: *Value) bool { + return api.LLVMIsConstant(value) != 0; + } + + pub fn to_constant(value: *Value) *Constant { + assert(value.is_constant()); + return @ptrCast(value); + } }; pub const DI = struct { @@ -697,6 +729,12 @@ pub const DI = struct { } pub const insert_declare_record_at_end = api.LLVMDIBuilderInsertDeclareRecordAtEnd; + + pub fn create_global_variable(builder: *DI.Builder, scope: *DI.Scope, name: []const u8, linkage_name: []const u8, file: *DI.File, line: c_uint, global_type: *DI.Type, local_to_unit: bool, expression: *DI.Expression, align_in_bits: u32) *DI.GlobalVariableExpression { + const declaration: ?*DI.Metadata = null; + return api.LLVMDIBuilderCreateGlobalVariableExpression(builder, scope, name.ptr, name.len, linkage_name.ptr, linkage_name.len, file, line, global_type, @intFromBool(local_to_unit), expression, declaration, align_in_bits); + } + // pub extern fn LLVMDIBuilderCreateGlobalVariableExpression(builder: *llvm.DI.Builder, scope: *llvm.DI.Scope, name_pointer: [*]const u8, name_length: usize, linkage_name_pointer: [*]const u8, linkage_name_length: usize, file: *llvm.DI.File, line: c_uint, global_type: *llvm.DI.Type, local_to_unit: Bool, expression: *llvm.DI.Expression, declaration: ?*llvm.DI.Metadata, align_in_bits: u32) *llvm.DI.GlobalVariableExpression; }; pub const create_debug_location = api.LLVMDIBuilderCreateDebugLocation; @@ -710,6 +748,7 @@ pub const DI = struct { pub const Scope = opaque {}; pub const Subprogram = opaque {}; pub const Expression = opaque {}; + pub const GlobalVariableExpression = opaque {}; pub const LocalVariable = opaque {}; pub const Location = opaque {}; pub const Metadata = opaque {}; @@ -931,6 +970,10 @@ pub const LinkageType = enum(c_int) { CommonLinkage, }; +pub const ThreadLocalMode = enum(c_uint) { + none = 0, +}; + pub const lld = struct { pub const Result = extern struct { stdout: String, diff --git a/src/converter.zig b/src/converter.zig index 511939b..5b9c6eb 100644 --- a/src/converter.zig +++ b/src/converter.zig @@ -63,19 +63,52 @@ const CallingConvention = enum { c, }; -const Local = struct { +const Variable = struct { name: []const u8, - alloca: *llvm.Value, + storage: *llvm.Value, type: Type, }; +const VariableArray = struct { + buffer: [64]Variable = undefined, + count: u32 = 0, + + pub fn get(variables: *VariableArray) []Variable { + return variables.buffer[0..variables.count]; + } + + pub fn add(variables: *VariableArray) *Variable { + const result = &variables.buffer[variables.count]; + variables.count += 1; + return result; + } + + pub fn find(variables: *VariableArray, name: []const u8) ?*Variable { + for (variables.get()) |*variable| { + if (lib.string.equal(variable.name, name)) { + return variable; + } + } else { + return null; + } + } +}; + +const ModuleBuilder = struct { + handle: *llvm.Module, + di_builder: ?*llvm.DI.Builder, + global_scope: *llvm.DI.Scope, + file: *llvm.DI.File, + integer_types: [8]*llvm.DI.Type, + globals: VariableArray = .{}, +}; + pub const FunctionBuilder = struct { function: *llvm.Function, current_basic_block: *llvm.BasicBlock, current_scope: *llvm.DI.Scope, return_type: Type, - local_buffer: [64]Local = undefined, - local_count: u32 = 0, + locals: VariableArray = .{}, }; const Type = packed struct(u64) { @@ -284,8 +317,8 @@ const Converter = struct { converter.expect_character(left_brace); - const local_offset = function.local_count; - defer function.local_count = local_offset; + const local_offset = function.locals.count; + defer function.locals.count = local_offset; while (true) { converter.skip_space(); @@ -353,11 +386,10 @@ const Converter = struct { } _ = thread.builder.create_store(value, alloca); - const local = &function.local_buffer[function.local_count]; - function.local_count += 1; + const local = function.locals.add(); local.* = .{ .name = local_name, - .alloca = alloca, + .storage = alloca, .type = local_type, }; } else { @@ -408,7 +440,7 @@ const Converter = struct { xor, }; - fn parse_value(noalias converter: *Converter, noalias thread: *llvm.Thread, noalias module: *ModuleBuilder, noalias function: *FunctionBuilder, expected_type: Type) *llvm.Value { + fn parse_value(noalias converter: *Converter, noalias thread: *llvm.Thread, noalias module: *ModuleBuilder, noalias function: ?*FunctionBuilder, expected_type: Type) *llvm.Value { converter.skip_space(); var value_state = ExpressionState.none; @@ -522,15 +554,17 @@ const Converter = struct { negative, }; - fn parse_single_value(noalias converter: *Converter, noalias thread: *llvm.Thread, noalias module: *ModuleBuilder, noalias function: *FunctionBuilder, expected_type: Type) *llvm.Value { + fn parse_single_value(noalias converter: *Converter, noalias thread: *llvm.Thread, noalias module: *ModuleBuilder, noalias maybe_function: ?*FunctionBuilder, expected_type: Type) *llvm.Value { converter.skip_space(); - if (module.di_builder) |_| { - const line = converter.get_line(); - const column = converter.get_column(); - const inlined_at: ?*llvm.DI.Metadata = null; // TODO - const debug_location = llvm.DI.create_debug_location(thread.context, line, column, function.current_scope, inlined_at); - thread.builder.set_current_debug_location(debug_location); + if (maybe_function) |function| { + if (module.di_builder) |_| { + const line = converter.get_line(); + const column = converter.get_column(); + const inlined_at: ?*llvm.DI.Metadata = null; // TODO + const debug_location = llvm.DI.create_debug_location(thread.context, line, column, function.current_scope, inlined_at); + thread.builder.set_current_debug_location(debug_location); + } } const prefix_offset = converter.offset; @@ -551,13 +585,20 @@ const Converter = struct { const value_start_ch = converter.content[value_offset]; const value = switch (value_start_ch) { 'a'...'z', 'A'...'Z', '_' => b: { - const identifier = converter.parse_identifier(); - for (function.local_buffer[0..function.local_count]) |*local| { - if (lib.string.equal(identifier, local.name)) { - break :b thread.builder.create_load(local.type.get(), local.alloca); - } + if (maybe_function) |function| { + const identifier = converter.parse_identifier(); + const variable = blk: { + if (function.locals.find(identifier)) |local| { + break :blk local; + } else if (module.globals.find(identifier)) |global| { + break :blk global; + } else { + converter.report_error(); + } + }; + break :b thread.builder.create_load(variable.type.get(), variable.storage); } else { - os.abort(); + converter.report_error(); } }, '0'...'9' => converter.parse_integer(expected_type, prefix == .negative), @@ -626,14 +667,6 @@ const ConvertOptions = struct { has_debug_info: bool, }; -const ModuleBuilder = struct { - handle: *llvm.Module, - di_builder: ?*llvm.DI.Builder, - global_scope: *llvm.DI.Scope, - file: *llvm.DI.File, - integer_types: [8]*llvm.DI.Type, -}; - pub noinline fn convert(options: ConvertOptions) void { var converter = Converter{ .content = options.content, @@ -704,7 +737,9 @@ pub noinline fn convert(options: ConvertOptions) void { var is_export = false; - const global_line_offset = converter.line_offset; + const global_line = converter.get_line(); + const global_column = converter.get_column(); + _ = global_column; if (converter.content[converter.offset] == left_bracket) { converter.offset += 1; @@ -733,120 +768,166 @@ pub noinline fn convert(options: ConvertOptions) void { converter.skip_space(); + var global_type: ?Type = null; + if (converter.consume_character_if_match(':')) { + converter.skip_space(); + + global_type = converter.parse_type(thread); + + converter.skip_space(); + } + converter.expect_character('='); converter.skip_space(); - const global_kind_string = converter.parse_identifier(); + if (is_identifier_start_ch(converter.content[converter.offset])) { + const global_string = converter.parse_identifier(); + converter.skip_space(); - converter.skip_space(); + if (string_to_enum(GlobalKind, global_string)) |global_kind| { + switch (global_kind) { + .@"fn" => { + var calling_convention = CallingConvention.unknown; - const global_kind = string_to_enum(GlobalKind, global_kind_string) orelse converter.report_error(); + if (converter.consume_character_if_match(left_bracket)) { + while (converter.offset < converter.content.len) { + const function_identifier = converter.parse_identifier(); - switch (global_kind) { - .@"fn" => { - var calling_convention = CallingConvention.unknown; + const function_keyword = string_to_enum(FunctionKeyword, function_identifier) orelse converter.report_error(); - if (converter.consume_character_if_match(left_bracket)) { - while (converter.offset < converter.content.len) { - const function_identifier = converter.parse_identifier(); + converter.skip_space(); - const function_keyword = string_to_enum(FunctionKeyword, function_identifier) orelse converter.report_error(); + switch (function_keyword) { + .cc => { + converter.expect_character(left_parenthesis); + + converter.skip_space(); + + const calling_convention_string = converter.parse_identifier(); + + calling_convention = string_to_enum(CallingConvention, calling_convention_string) orelse converter.report_error(); + + converter.skip_space(); + + converter.expect_character(right_parenthesis); + }, + else => converter.report_error(), + } + + converter.skip_space(); + + switch (converter.content[converter.offset]) { + right_bracket => break, + else => converter.report_error(), + } + } + + converter.expect_character(right_bracket); + } converter.skip_space(); - switch (function_keyword) { - .cc => { - converter.expect_character(left_parenthesis); + converter.expect_character(left_parenthesis); - converter.skip_space(); + while (converter.offset < converter.content.len and converter.content[converter.offset] != right_parenthesis) { + // TODO: arguments + converter.report_error(); + } - const calling_convention_string = converter.parse_identifier(); + converter.expect_character(right_parenthesis); - calling_convention = string_to_enum(CallingConvention, calling_convention_string) orelse converter.report_error(); + converter.skip_space(); - converter.skip_space(); + const return_type = converter.parse_type(thread); + const function_type = llvm.Type.Function.get(return_type.get(), &.{}, false); - converter.expect_character(right_parenthesis); + const handle = module.handle.create_function(.{ + .name = global_name, + .linkage = switch (is_export) { + true => .ExternalLinkage, + false => .InternalLinkage, }, - else => converter.report_error(), + .type = function_type, + }); + + const entry_block = thread.context.create_basic_block("entry", handle); + thread.builder.position_at_end(entry_block); + + var function = FunctionBuilder{ + .function = handle, + .current_basic_block = entry_block, + .return_type = return_type, + .current_scope = undefined, + }; + + if (module.di_builder) |di_builder| { + const debug_return_type = return_type.to_debug_type(&module); + const subroutine_type = di_builder.create_subroutine_type(module.file, &.{debug_return_type}, .{}); + const linkage_name = global_name; + const scope_line: u32 = @intCast(converter.line_offset + 1); + const local_to_unit = !is_export; + const flags = llvm.DI.Flags{}; + const is_definition = true; + const subprogram = di_builder.create_function(module.global_scope, global_name, linkage_name, module.file, global_line, subroutine_type, local_to_unit, is_definition, scope_line, flags, options.build_mode.is_optimized()); + handle.set_subprogram(subprogram); + + function.current_scope = @ptrCast(subprogram); } - converter.skip_space(); + converter.parse_block(thread, &module, &function); - switch (converter.content[converter.offset]) { - right_bracket => break, - else => converter.report_error(), + if (module.di_builder) |di_builder| { + di_builder.finalize_subprogram(handle.get_subprogram()); } - } - converter.expect_character(right_bracket); + if (lib.optimization_mode == .Debug and module.di_builder == null) { + const verify_result = handle.verify(); + if (!verify_result.success) { + os.abort(); + } + } + }, + else => converter.report_error(), } - - converter.skip_space(); - - converter.expect_character(left_parenthesis); - - while (converter.offset < converter.content.len and converter.content[converter.offset] != right_parenthesis) { - // TODO: arguments - converter.report_error(); - } - - converter.expect_character(right_parenthesis); - - converter.skip_space(); - - const return_type = converter.parse_type(thread); - const function_type = llvm.Type.Function.get(return_type.get(), &.{}, false); - - const handle = module.handle.create_function(.{ - .name = global_name, + } else { + converter.report_error(); + } + } else { + if (global_type) |expected_type| { + const value = converter.parse_value(thread, &module, null, expected_type); + const global_variable = module.handle.create_global_variable(.{ .linkage = switch (is_export) { true => .ExternalLinkage, false => .InternalLinkage, }, - .type = function_type, + .name = global_name, + .initial_value = value.to_constant(), + .type = expected_type.get(), }); - const entry_block = thread.context.create_basic_block("entry", handle); - thread.builder.position_at_end(entry_block); - - var function = FunctionBuilder{ - .function = handle, - .current_basic_block = entry_block, - .return_type = return_type, - .current_scope = undefined, + const global = module.globals.add(); + global.* = .{ + .name = global_name, + .storage = global_variable.to_value(), + .type = expected_type, }; + converter.skip_space(); + + converter.expect_character(';'); + if (module.di_builder) |di_builder| { - const debug_return_type = return_type.to_debug_type(&module); - const subroutine_type = di_builder.create_subroutine_type(module.file, &.{debug_return_type}, .{}); + const debug_type = expected_type.to_debug_type(&module); const linkage_name = global_name; - const line: u32 = @intCast(global_line_offset + 1); - const scope_line: u32 = @intCast(converter.line_offset + 1); - const local_to_unit = !is_export; - const flags = llvm.DI.Flags{}; - const is_definition = true; - const subprogram = di_builder.create_function(module.global_scope, global_name, linkage_name, module.file, line, subroutine_type, local_to_unit, is_definition, scope_line, flags, options.build_mode.is_optimized()); - handle.set_subprogram(subprogram); - - function.current_scope = @ptrCast(subprogram); + const local_to_unit = is_export; // TODO: extern + const alignment = 0; // TODO + const global_variable_expression = di_builder.create_global_variable(module.global_scope, global_name, linkage_name, module.file, global_line, debug_type, local_to_unit, di_builder.null_expression(), alignment); + global_variable.add_debug_info(global_variable_expression); } - - converter.parse_block(thread, &module, &function); - - if (module.di_builder) |di_builder| { - di_builder.finalize_subprogram(handle.get_subprogram()); - } - - if (lib.optimization_mode == .Debug and module.di_builder == null) { - const verify_result = handle.verify(); - if (!verify_result.success) { - os.abort(); - } - } - }, - else => converter.report_error(), + } else { + converter.report_error(); + } } } diff --git a/src/converter_test.zig b/src/converter_test.zig index 44d4c30..5f9f005 100644 --- a/src/converter_test.zig +++ b/src/converter_test.zig @@ -138,3 +138,7 @@ test "stack_add" { test "stack_sub" { try invsrc(@src()); } + +test "global" { + try invsrc(@src()); +} diff --git a/src/llvm.cpp b/src/llvm.cpp index 61cd3f3..d731668 100644 --- a/src/llvm.cpp +++ b/src/llvm.cpp @@ -71,6 +71,17 @@ EXPORT unsigned llvm_integer_type_get_bit_count(const IntegerType& integer_type) return result; } +EXPORT GlobalVariable* llvm_module_create_global_variable(Module& module, Type* type, bool is_constant, GlobalValue::LinkageTypes linkage_type, Constant* initial_value, BBLLVMString name, GlobalVariable* before, GlobalValue::ThreadLocalMode thread_local_mode, unsigned address_space, bool externally_initialized) +{ + auto* global_variable = new GlobalVariable(module, type, is_constant, linkage_type, initial_value, name.string_ref(), before, thread_local_mode, address_space, externally_initialized); + return global_variable; +} + +EXPORT void llvm_global_variable_add_debug_info(GlobalVariable& global_variable, DIGlobalVariableExpression* debug_global_variable) +{ + global_variable.addDebugInfo(debug_global_variable); +} + EXPORT Function* llvm_module_create_function(Module* module, FunctionType* function_type, GlobalValue::LinkageTypes linkage_type, unsigned address_space, BBLLVMString name) { auto* function = Function::Create(function_type, linkage_type, address_space, name.string_ref(), module); diff --git a/src/llvm_api.zig b/src/llvm_api.zig index 4adf863..534211f 100644 --- a/src/llvm_api.zig +++ b/src/llvm_api.zig @@ -8,6 +8,7 @@ pub extern fn LLVMContextCreate() *llvm.Context; pub extern fn LLVMCreateBuilderInContext(context: *llvm.Context) *llvm.Builder; // Module +pub extern fn llvm_module_create_global_variable(module: *llvm.Module, global_type: *llvm.Type, is_constant: bool, linkage: llvm.LinkageType, initial_value: *llvm.Constant, name: llvm.String, before: ?*llvm.GlobalVariable, thread_local_mode: llvm.ThreadLocalMode, address_space: c_uint, externally_initialized: bool) *llvm.GlobalVariable; pub extern fn llvm_module_create_function(module: *llvm.Module, function_type: *llvm.Type.Function, linkage_type: llvm.LinkageType, address_space: c_uint, name: llvm.String) *llvm.Function; pub extern fn llvm_context_create_basic_block(context: *llvm.Context, name: llvm.String, parent: *llvm.Function) *llvm.BasicBlock; @@ -90,6 +91,8 @@ pub extern fn llvm_integer_type_get_bit_count(integer_type: *llvm.Type.Integer) // VALUES pub extern fn LLVMConstInt(type: *llvm.Type.Integer, value: c_ulonglong, sign_extend: Bool) *llvm.Constant.Integer; +pub extern fn LLVMIsConstant(value: *llvm.Value) Bool; + // Debug info API pub extern fn LLVMCreateDIBuilder(module: *llvm.Module) *llvm.DI.Builder; pub extern fn LLVMDIBuilderFinalize(builder: *llvm.DI.Builder) void; @@ -103,6 +106,8 @@ pub extern fn LLVMDIBuilderCreateDebugLocation(context: *llvm.Context, line: c_u pub extern fn LLVMDIBuilderCreateBasicType(builder: *llvm.DI.Builder, name_pointer: [*]const u8, name_length: usize, bit_count: u64, dwarf_type: llvm.Dwarf.Type, flags: llvm.DI.Flags) *llvm.DI.Type; pub extern fn LLVMDIBuilderCreateAutoVariable(builder: *llvm.DI.Builder, scope: *llvm.DI.Scope, name_pointer: [*]const u8, name_length: usize, file: *llvm.DI.File, line: c_uint, type: *llvm.DI.Type, always_preserve: Bool, flags: llvm.DI.Flags, align_in_bits: u32) *llvm.DI.LocalVariable; pub extern fn LLVMDIBuilderInsertDeclareRecordAtEnd(builder: *llvm.DI.Builder, storage: *llvm.Value, local_variable: *llvm.DI.LocalVariable, expression: *llvm.DI.Expression, debug_location: *llvm.DI.Location, basic_block: *llvm.BasicBlock) *llvm.DI.Record; +pub extern fn LLVMDIBuilderCreateGlobalVariableExpression(builder: *llvm.DI.Builder, scope: *llvm.DI.Scope, name_pointer: [*]const u8, name_length: usize, linkage_name_pointer: [*]const u8, linkage_name_length: usize, file: *llvm.DI.File, line: c_uint, global_type: *llvm.DI.Type, local_to_unit: Bool, expression: *llvm.DI.Expression, declaration: ?*llvm.DI.Metadata, align_in_bits: u32) *llvm.DI.GlobalVariableExpression; +pub extern fn llvm_global_variable_add_debug_info(global_variable: *llvm.GlobalVariable, debug_global_variable: *llvm.DI.GlobalVariableExpression) void; // Target pub extern fn llvm_default_target_triple() llvm.String; diff --git a/tests/global.bbb b/tests/global.bbb new file mode 100644 index 0000000..1859a25 --- /dev/null +++ b/tests/global.bbb @@ -0,0 +1,5 @@ +result: s32 = 0; + +[export] main = fn [cc(c)] () s32 { + return result; +}