From 27bd13648766a6eeb4966a59c2fe0bccadf6943e Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Sat, 22 Feb 2025 21:52:04 -0600 Subject: [PATCH] Basic call --- src/LLVM.zig | 30 +++++++++++++++-- src/converter.zig | 29 +++++++++++++++-- src/converter_test.zig | 73 +++++++++++++++++++++++------------------- src/llvm.cpp | 6 ++++ src/llvm_api.zig | 5 +++ tests/basic_call.bbb | 9 ++++++ 6 files changed, 115 insertions(+), 37 deletions(-) create mode 100644 tests/basic_call.bbb diff --git a/src/LLVM.zig b/src/LLVM.zig index d972548..f9c62c2 100644 --- a/src/LLVM.zig +++ b/src/LLVM.zig @@ -636,6 +636,10 @@ pub const Builder = opaque { } pub const create_conditional_branch = api.LLVMBuildCondBr; + + pub fn create_call(builder: *Builder, function_type: *Type.Function, function_value: *Value, arguments: []const *Value) *Value { + return api.LLVMBuildCall2(builder, function_type, function_value, arguments.ptr, @intCast(arguments.len), ""); + } }; pub const GlobalValue = opaque { @@ -699,10 +703,32 @@ pub const Value = opaque { return api.LLVMIsConstant(value) != 0; } + pub const is_instruction = api.llvm_value_is_instruction; + pub fn to_constant(value: *Value) *Constant { assert(value.is_constant()); return @ptrCast(value); } + + pub fn to_instruction(value: *Value) *Instruction { + assert(value.is_instruction()); + return @ptrCast(value); + } + + pub fn get_calling_convention(value: *Value) CallingConvention { + if (value.is_instruction()) { + const instruction = value.to_instruction(); + return instruction.get_calling_convention(); + } else { + const function = @as(*Function, @ptrCast(value)); + return function.get_calling_convention(); + } + } +}; + +pub const Instruction = opaque { + pub const set_calling_convention = api.LLVMSetInstructionCallConv; + pub const get_calling_convention = api.LLVMGetInstructionCallConv; }; pub const DI = struct { @@ -827,6 +853,7 @@ pub const DI = struct { pub const Type = opaque { pub const is_function = api.llvm_type_is_function; pub const is_integer = api.llvm_type_is_integer; + pub fn to_function(t: *Type) *Type.Function { assert(t.is_function()); return @ptrCast(t); @@ -1260,8 +1287,7 @@ pub fn link(arena: *Arena, options: LinkOptions) lld.Result { } if (link_libc) { - const scrt1_path = arena.join_string(&.{ scrt1_object_directory_path, "/Scrt1.o" }); - arg_builder.add(scrt1_path); + arg_builder.add(arena.join_string(&.{ scrt1_object_directory_path, "/", "Scrt1.o" })); arg_builder.add("-lc"); } diff --git a/src/converter.zig b/src/converter.zig index a270f25..f63f99d 100644 --- a/src/converter.zig +++ b/src/converter.zig @@ -474,7 +474,19 @@ const Converter = struct { }, } } else { - converter.report_error(); + converter.skip_space(); + + if (converter.consume_character_if_match(left_parenthesis)) { + // This is a call + const variable = if (function.locals.find(statement_start_identifier)) |local| local else if (module.globals.find(statement_start_identifier)) |global| global else { + converter.report_error(); + }; + const call = thread.builder.create_call(variable.type.get().to_function(), variable.storage, &.{}); + _ = call; + @trap(); + } else { + converter.report_error(); + } } } else { converter.report_error(); @@ -688,7 +700,20 @@ const Converter = struct { converter.report_error(); } }; - break :b thread.builder.create_load(variable.type.get(), variable.storage); + + converter.skip_space(); + + if (converter.consume_character_if_match(left_parenthesis)) { + // TODO: arguments + converter.skip_space(); + converter.expect_character(right_parenthesis); + const call = thread.builder.create_call(variable.type.get().to_function(), variable.storage, &.{}); + call.to_instruction().set_calling_convention(variable.storage.get_calling_convention()); + break :b call; + } else { + const load = thread.builder.create_load(variable.type.get(), variable.storage); + break :b load; + } } else { converter.report_error(); } diff --git a/src/converter_test.zig b/src/converter_test.zig index 28a7c22..37ecedb 100644 --- a/src/converter_test.zig +++ b/src/converter_test.zig @@ -20,35 +20,16 @@ fn invoke(name: []const u8) !void { defer tmp_dir.cleanup(); const base_path = lib.global.arena.join_string(&.{ ".zig-cache/tmp/", &tmp_dir.sub_path, "/", name }); const executable_path = base_path; - invoke_wrapper(.{ + const directory_path = lib.global.arena.join_string(&.{ ".zig-cache/tmp/", &tmp_dir.sub_path }); + try unit_test(allocator, .{ .object_path = lib.global.arena.join_string(&.{ base_path, ".o" }), .executable_path = executable_path, .file_path = lib.global.arena.join_string(&.{ "tests/", name, ".bbb" }), .name = name, - }, build_mode, has_debug_info); - const run_result = std.process.Child.run(.{ - .allocator = allocator, - .argv = &.{executable_path}, - }) catch |err| { - std.debug.print("error: {}\n", .{err}); - const path = lib.global.arena.join_string(&.{ ".zig-cache/tmp/", &tmp_dir.sub_path }); - const r = try std.process.Child.run(.{ - .allocator = allocator, - .argv = &.{ "/usr/bin/ls", "-lasR", path }, - .max_output_bytes = std.math.maxInt(usize), - }); - defer allocator.free(r.stdout); - defer allocator.free(r.stderr); - std.debug.print("ls {s} {s}\n", .{ path, r.stdout }); - return err; - }; - const success = switch (run_result.term) { - .Exited => |exit_code| exit_code == 0, - else => false, - }; - if (!success) { - return error.executable_failed_to_run_successfully; - } + .directory_path = directory_path, + .build_mode = build_mode, + .has_debug_info = has_debug_info, + }); } } } @@ -58,14 +39,12 @@ const InvokeWrapper = struct { object_path: [:0]const u8, file_path: [:0]const u8, name: []const u8, + build_mode: BuildMode, + has_debug_info: bool, + directory_path: [:0]const u8, }; -// We invoke a function with comptime parameters so it's easily visible in CI stack traces -fn invoke_wrapper(options: InvokeWrapper, comptime build_mode: BuildMode, comptime has_debug_info: bool) void { - return invoke_single(options, build_mode, has_debug_info); -} - -fn invoke_single(options: InvokeWrapper, build_mode: BuildMode, has_debug_info: bool) void { +fn unit_test(allocator: std.mem.Allocator, options: InvokeWrapper) !void { const file_content = lib.file.read(lib.global.arena, options.file_path); converter.convert(.{ @@ -73,10 +52,34 @@ fn invoke_single(options: InvokeWrapper, build_mode: BuildMode, has_debug_info: .content = file_content, .object = options.object_path, .executable = options.executable_path, - .build_mode = build_mode, + .build_mode = options.build_mode, .name = options.name, - .has_debug_info = has_debug_info, + .has_debug_info = options.has_debug_info, }); + const run_result = std.process.Child.run(.{ + .allocator = allocator, + .argv = &.{options.executable_path}, + }) catch |err| { + std.debug.print("error: {}\n", .{err}); + const r = try std.process.Child.run(.{ + .allocator = allocator, + .argv = &.{ "/usr/bin/ls", "-lasR", options.directory_path }, + .max_output_bytes = std.math.maxInt(usize), + }); + defer allocator.free(r.stdout); + defer allocator.free(r.stderr); + std.debug.print("ls {s} {s}\n", .{ options.directory_path, r.stdout }); + return err; + }; + + const success = switch (run_result.term) { + .Exited => |exit_code| exit_code == 0, + else => false, + }; + if (!success) { + std.debug.print("{}\n{}\n", .{ run_result, options }); + return error.executable_failed_to_run_successfully; + } } fn invsrc(src: std.builtin.SourceLocation) !void { @@ -146,3 +149,7 @@ test "global" { test "simple_branch" { try invsrc(@src()); } + +test "basic_call" { + try invsrc(@src()); +} diff --git a/src/llvm.cpp b/src/llvm.cpp index e103b6b..d8939db 100644 --- a/src/llvm.cpp +++ b/src/llvm.cpp @@ -53,6 +53,12 @@ EXPORT Module* llvm_context_create_module(LLVMContext& context, BBLLVMString nam return new Module(name.string_ref(), context); } +EXPORT bool llvm_value_is_instruction(Value* value) +{ + auto result = isa(value); + return result; +} + EXPORT bool llvm_type_is_function(const Type& type) { auto result = type.isFunctionTy(); diff --git a/src/llvm_api.zig b/src/llvm_api.zig index 83b1e5c..4c0dc10 100644 --- a/src/llvm_api.zig +++ b/src/llvm_api.zig @@ -17,6 +17,9 @@ pub extern fn LLVMGetBasicBlockTerminator(basic_block: *llvm.BasicBlock) ?*llvm. pub extern fn LLVMSetFunctionCallConv(function: *llvm.Function, calling_convention: llvm.CallingConvention) void; pub extern fn LLVMGetFunctionCallConv(function: *llvm.Function) llvm.CallingConvention; +pub extern fn LLVMSetInstructionCallConv(instruction: *llvm.Instruction, calling_convention: llvm.CallingConvention) void; +pub extern fn LLVMGetInstructionCallConv(instruction: *llvm.Instruction) llvm.CallingConvention; + pub extern fn llvm_function_to_string(function: *llvm.Function) *llvm.String; pub extern fn llvm_function_verify(function: *llvm.Function, error_message: *llvm.String) bool; pub extern fn llvm_module_verify(module: *llvm.Module, error_message: *llvm.String) bool; @@ -45,11 +48,13 @@ pub extern fn LLVMBuildCondBr(builder: *llvm.Builder, condition: *llvm.Value, ta pub extern fn llvm_builder_create_alloca(builder: *llvm.Builder, ty: *llvm.Type, address_space: c_uint, name: llvm.String) *llvm.Value; pub extern fn LLVMBuildStore(builder: *llvm.Builder, value: *llvm.Value, pointer: *llvm.Value) *llvm.Value; pub extern fn LLVMBuildLoad2(builder: *llvm.Builder, ty: *llvm.Type, pointer: *llvm.Value, name: [*:0]const u8) *llvm.Value; +pub extern fn LLVMBuildCall2(builder: *llvm.Builder, ty: *llvm.Type.Function, pointer: *llvm.Value, argument_pointer: [*]const *llvm.Value, argument_count: c_uint, name: [*:0]const u8) *llvm.Value; pub extern fn LLVMSetCurrentDebugLocation2(builder: *llvm.Builder, location: ?*llvm.DI.Location) void; pub extern fn LLVMTypeOf(value: *llvm.Value) *llvm.Type; pub extern fn LLVMGlobalGetValueType(value: *llvm.GlobalValue) *llvm.Type; +pub extern fn llvm_value_is_instruction(value: *llvm.Value) bool; // TYPES // Types: integers diff --git a/tests/basic_call.bbb b/tests/basic_call.bbb new file mode 100644 index 0000000..0e60991 --- /dev/null +++ b/tests/basic_call.bbb @@ -0,0 +1,9 @@ +foo = fn() s32 +{ + return 0; +} + +[export] main = fn[cc(c)] () s32 +{ + return foo(); +}