diff --git a/build.zig b/build.zig index e1aa692..747c1b4 100644 --- a/build.zig +++ b/build.zig @@ -61,233 +61,6 @@ const CmakeBuildType = enum { } }; -const LLVM = struct { - module: *std.Build.Module, - - fn setup(b: *std.Build, path: []const u8) !LLVM { - var llvm_libs = std.ArrayList([]const u8).init(b.allocator); - var flags = std.ArrayList([]const u8).init(b.allocator); - const llvm_config_path = if (b.option([]const u8, "llvm_prefix", "LLVM prefix")) |llvm_prefix| blk: { - const full_path = try std.mem.concat(b.allocator, u8, &.{ llvm_prefix, "/bin/llvm-config" }); - const f = std.fs.cwd().openFile(full_path, .{}) catch return error.llvm_not_found; - f.close(); - break :blk full_path; - } else if (system_llvm) executable_find_in_path(b.allocator, "llvm-config", path) orelse return error.llvm_not_found else blk: { - const home_env = switch (@import("builtin").os.tag) { - .windows => "USERPROFILE", - else => "HOME", - }; - const home_path = env.get(home_env) orelse unreachable; - const is_ci = std.mem.eql(u8, (env.get("BB_CI") orelse "0"), "1"); - const download_dir = try std.mem.concat(b.allocator, u8, &.{ home_path, "/Downloads" }); - std.fs.makeDirAbsolute(download_dir) catch {}; - const cmake_build_type = if (is_ci) CmakeBuildType.from_zig_build_type(optimize) else CmakeBuildType.Release; - const llvm_base = try std.mem.concat(b.allocator, u8, &.{ "llvm-", @tagName(target.result.cpu.arch), "-", @tagName(target.result.os.tag), "-", @tagName(cmake_build_type) }); - const base = try std.mem.concat(b.allocator, u8, &.{ download_dir, "/", llvm_base }); - const full_path = try std.mem.concat(b.allocator, u8, &.{ base, "/bin/llvm-config" }); - - const f = std.fs.cwd().openFile(full_path, .{}) catch { - const url = try std.mem.concat(b.allocator, u8, &.{ "https://github.com/birth-software/llvm/releases/download/v19.1.7/", llvm_base, ".7z" }); - var result = try std.process.Child.run(.{ - .allocator = b.allocator, - .argv = &.{ "wget", "-P", download_dir, url }, - .max_output_bytes = std.math.maxInt(usize), - }); - var success = false; - switch (result.term) { - .Exited => |exit_code| { - success = exit_code == 0; - }, - else => {}, - } - - if (!success) { - std.debug.print("{s}\n{s}\n", .{ result.stdout, result.stderr }); - } - - if (success) { - const file_7z = try std.mem.concat(b.allocator, u8, &.{ base, ".7z" }); - result = try std.process.Child.run(.{ - .allocator = b.allocator, - .argv = &.{ "7z", "x", try std.mem.concat(b.allocator, u8, &.{ "-o", download_dir }), file_7z }, - .max_output_bytes = std.math.maxInt(usize), - }); - success = false; - switch (result.term) { - .Exited => |exit_code| { - success = exit_code == 0; - }, - else => {}, - } - - if (!success) { - std.debug.print("{s}\n{s}\n", .{ result.stdout, result.stderr }); - } - - break :blk full_path; - } - - return error.llvm_not_found; - }; - - f.close(); - break :blk full_path; - }; - const llvm_components_result = try run_process_and_capture_stdout(b, &.{ llvm_config_path, "--components" }); - var it = std.mem.splitScalar(u8, llvm_components_result, ' '); - var args = std.ArrayList([]const u8).init(b.allocator); - try args.append(llvm_config_path); - try args.append("--libs"); - while (it.next()) |component| { - try args.append(std.mem.trimRight(u8, component, "\n")); - } - const llvm_libs_result = try run_process_and_capture_stdout(b, args.items); - it = std.mem.splitScalar(u8, llvm_libs_result, ' '); - - while (it.next()) |lib| { - const llvm_lib = std.mem.trimLeft(u8, std.mem.trimRight(u8, lib, "\n"), "-l"); - try llvm_libs.append(llvm_lib); - } - - const llvm_cxx_flags_result = try run_process_and_capture_stdout(b, &.{ llvm_config_path, "--cxxflags" }); - it = std.mem.splitScalar(u8, llvm_cxx_flags_result, ' '); - while (it.next()) |flag| { - const llvm_cxx_flag = std.mem.trimRight(u8, flag, "\n"); - try flags.append(llvm_cxx_flag); - } - - const llvm_lib_dir = std.mem.trimRight(u8, try run_process_and_capture_stdout(b, &.{ llvm_config_path, "--libdir" }), "\n"); - - if (optimize != .ReleaseSmall) { - try flags.append("-g"); - } - - try flags.append("-fno-rtti"); - - const llvm = b.createModule(.{ - .target = target, - .optimize = optimize, - .sanitize_c = false, - }); - - llvm.addLibraryPath(.{ .cwd_relative = llvm_lib_dir }); - - const a = std.fs.cwd().openDir("/usr/lib/x86_64-linux-gnu/", .{}); - if (a) |_| { - var dir = a catch unreachable; - dir.close(); - llvm.addLibraryPath(.{ .cwd_relative = "/usr/lib/x86_64-linux-gnu/" }); - } else |err| { - err catch {}; - } - - llvm.addCSourceFiles(.{ - .files = &.{"src/llvm.cpp"}, - .flags = flags.items, - }); - - var dir = try std.fs.cwd().openDir("/usr/include/c++", .{ - .iterate = true, - }); - var iterator = dir.iterate(); - const gcc_version = while (try iterator.next()) |entry| { - if (entry.kind == .directory) { - break entry.name; - } - } else return error.include_cpp_dir_not_found; - dir.close(); - const general_cpp_include_dir = try std.mem.concat(b.allocator, u8, &.{ "/usr/include/c++/", gcc_version }); - llvm.addIncludePath(.{ .cwd_relative = general_cpp_include_dir }); - - { - const arch_cpp_include_dir = try std.mem.concat(b.allocator, u8, &.{ general_cpp_include_dir, "/x86_64-pc-linux-gnu" }); - const d2 = std.fs.cwd().openDir(arch_cpp_include_dir, .{}); - if (d2) |_| { - var d = d2 catch unreachable; - d.close(); - llvm.addIncludePath(.{ .cwd_relative = arch_cpp_include_dir }); - } else |err| err catch {}; - } - - { - const arch_cpp_include_dir = try std.mem.concat(b.allocator, u8, &.{ "/usr/include/x86_64-linux-gnu/c++/", gcc_version }); - const d2 = std.fs.cwd().openDir(arch_cpp_include_dir, .{}); - if (d2) |_| { - var d = d2 catch unreachable; - d.close(); - llvm.addIncludePath(.{ .cwd_relative = arch_cpp_include_dir }); - } else |err| err catch {}; - } - - var found_libcpp = false; - - if (std.fs.cwd().openFile("/usr/lib/libstdc++.so.6", .{})) |file| { - file.close(); - found_libcpp = true; - llvm.addObjectFile(.{ .cwd_relative = "/usr/lib/libstdc++.so.6" }); - } else |err| { - err catch {}; - } - - if (std.fs.cwd().openFile("/usr/lib/x86_64-linux-gnu/libstdc++.so.6", .{})) |file| { - file.close(); - found_libcpp = true; - llvm.addObjectFile(.{ .cwd_relative = "/usr/lib/x86_64-linux-gnu/libstdc++.so.6" }); - } else |err| { - err catch {}; - } - - if (!found_libcpp) { - return error.libcpp_not_found; - } - - const needed_libraries: []const []const u8 = &.{ "unwind", "z", "zstd" }; - - const lld_libs: []const []const u8 = &.{ "lldCommon", "lldCOFF", "lldELF", "lldMachO", "lldMinGW", "lldWasm" }; - - for (needed_libraries) |lib| { - llvm.linkSystemLibrary(lib, .{}); - } - - for (llvm_libs.items) |lib| { - llvm.linkSystemLibrary(lib, .{}); - } - - for (lld_libs) |lib| { - llvm.linkSystemLibrary(lib, .{}); - } - - return LLVM{ - .module = llvm, - }; - } - - fn link(llvm: LLVM, compile: *std.Build.Step.Compile) void { - if (compile.root_module != llvm.module) { - compile.root_module.addImport("llvm", llvm.module); - } else { - // TODO: should we allow this case? - unreachable; - } - } -}; - -fn debug_binary(b: *std.Build, exe: *std.Build.Step.Compile) *std.Build.Step.Run { - const run_step = std.Build.Step.Run.create(b, b.fmt("debug {s}", .{exe.name})); - run_step.addArg("gdb"); - run_step.addArg("-ex"); - run_step.addArg("r"); - if (b.args) |args| { - run_step.addArg("--args"); - run_step.addArtifactArg(exe); - run_step.addArgs(args); - } else { - run_step.addArtifactArg(exe); - } - - return run_step; -} - var system_llvm: bool = undefined; var target: std.Build.ResolvedTarget = undefined; var optimize: std.builtin.OptimizeMode = undefined; @@ -309,18 +82,16 @@ pub fn build(b: *std.Build) !void { target = b.standardTargetOptions(.{}); optimize = b.standardOptimizeOption(.{}); system_llvm = b.option(bool, "system_llvm", "Link against system LLVM libraries") orelse false; - const path = env.get("PATH") orelse unreachable; - const c_abi_module = b.createModule(.{ - .target = target, - .optimize = optimize, - .link_libc = true, - .sanitize_c = false, - }); const c_abi = b.addObject(.{ .name = "c_abi", .link_libc = true, - .root_module = c_abi_module, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .sanitize_c = false, + }), .optimize = optimize, }); c_abi.addCSourceFiles(.{ @@ -328,6 +99,18 @@ pub fn build(b: *std.Build) !void { .flags = &.{"-g"}, }); + const path = env.get("PATH") orelse unreachable; + + const stack_trace_library = b.addObject(.{ + .name = "stack_trace", + .root_module = b.createModule(.{ + .target = target, + .optimize = .ReleaseFast, + .root_source_file = b.path("src/stack_trace.zig"), + .link_libc = true, + }), + }); + const exe_mod = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, @@ -339,43 +122,241 @@ pub fn build(b: *std.Build) !void { configuration.addOptionPath("c_abi_object_path", c_abi.getEmittedBin()); exe_mod.addOptions("configuration", configuration); - const llvm = try LLVM.setup(b, path); - const exe = b.addExecutable(.{ .name = "bloat-buster", .root_module = exe_mod, .link_libc = true, }); + exe.addObject(stack_trace_library); + var llvm_libs = std.ArrayList([]const u8).init(b.allocator); + var flags = std.ArrayList([]const u8).init(b.allocator); + const llvm_config_path = if (b.option([]const u8, "llvm_prefix", "LLVM prefix")) |llvm_prefix| blk: { + const full_path = try std.mem.concat(b.allocator, u8, &.{ llvm_prefix, "/bin/llvm-config" }); + const f = std.fs.cwd().openFile(full_path, .{}) catch return error.llvm_not_found; + f.close(); + break :blk full_path; + } else if (system_llvm) executable_find_in_path(b.allocator, "llvm-config", path) orelse return error.llvm_not_found else blk: { + const home_env = switch (@import("builtin").os.tag) { + .windows => "USERPROFILE", + else => "HOME", + }; + const home_path = env.get(home_env) orelse unreachable; + const is_ci = std.mem.eql(u8, (env.get("BB_CI") orelse "0"), "1"); + const download_dir = try std.mem.concat(b.allocator, u8, &.{ home_path, "/Downloads" }); + std.fs.makeDirAbsolute(download_dir) catch {}; + const cmake_build_type = if (is_ci) CmakeBuildType.from_zig_build_type(optimize) else CmakeBuildType.Release; + const llvm_base = try std.mem.concat(b.allocator, u8, &.{ "llvm-", @tagName(target.result.cpu.arch), "-", @tagName(target.result.os.tag), "-", @tagName(cmake_build_type) }); + const base = try std.mem.concat(b.allocator, u8, &.{ download_dir, "/", llvm_base }); + const full_path = try std.mem.concat(b.allocator, u8, &.{ base, "/bin/llvm-config" }); - llvm.link(exe); + const f = std.fs.cwd().openFile(full_path, .{}) catch { + const url = try std.mem.concat(b.allocator, u8, &.{ "https://github.com/birth-software/llvm/releases/download/v19.1.7/", llvm_base, ".7z" }); + var result = try std.process.Child.run(.{ + .allocator = b.allocator, + .argv = &.{ "wget", "-P", download_dir, url }, + .max_output_bytes = std.math.maxInt(usize), + }); + var success = false; + switch (result.term) { + .Exited => |exit_code| { + success = exit_code == 0; + }, + else => {}, + } + + if (!success) { + std.debug.print("{s}\n{s}\n", .{ result.stdout, result.stderr }); + } + + if (success) { + const file_7z = try std.mem.concat(b.allocator, u8, &.{ base, ".7z" }); + result = try std.process.Child.run(.{ + .allocator = b.allocator, + .argv = &.{ "7z", "x", try std.mem.concat(b.allocator, u8, &.{ "-o", download_dir }), file_7z }, + .max_output_bytes = std.math.maxInt(usize), + }); + success = false; + switch (result.term) { + .Exited => |exit_code| { + success = exit_code == 0; + }, + else => {}, + } + + if (!success) { + std.debug.print("{s}\n{s}\n", .{ result.stdout, result.stderr }); + } + + break :blk full_path; + } + + return error.llvm_not_found; + }; + + f.close(); + break :blk full_path; + }; + const llvm_components_result = try run_process_and_capture_stdout(b, &.{ llvm_config_path, "--components" }); + var it = std.mem.splitScalar(u8, llvm_components_result, ' '); + { + var args = std.ArrayList([]const u8).init(b.allocator); + try args.append(llvm_config_path); + try args.append("--libs"); + while (it.next()) |component| { + try args.append(std.mem.trimRight(u8, component, "\n")); + } + const llvm_libs_result = try run_process_and_capture_stdout(b, args.items); + it = std.mem.splitScalar(u8, llvm_libs_result, ' '); + } + + while (it.next()) |lib| { + const llvm_lib = std.mem.trimLeft(u8, std.mem.trimRight(u8, lib, "\n"), "-l"); + try llvm_libs.append(llvm_lib); + } + + const llvm_cxx_flags_result = try run_process_and_capture_stdout(b, &.{ llvm_config_path, "--cxxflags" }); + it = std.mem.splitScalar(u8, llvm_cxx_flags_result, ' '); + while (it.next()) |flag| { + const llvm_cxx_flag = std.mem.trimRight(u8, flag, "\n"); + try flags.append(llvm_cxx_flag); + } + + const llvm_lib_dir = std.mem.trimRight(u8, try run_process_and_capture_stdout(b, &.{ llvm_config_path, "--libdir" }), "\n"); + + if (optimize != .ReleaseSmall) { + try flags.append("-g"); + } + + try flags.append("-fno-rtti"); + + exe.addLibraryPath(.{ .cwd_relative = llvm_lib_dir }); + + const a = std.fs.cwd().openDir("/usr/lib/x86_64-linux-gnu/", .{}); + if (a) |_| { + var dir = a catch unreachable; + dir.close(); + exe.addLibraryPath(.{ .cwd_relative = "/usr/lib/x86_64-linux-gnu/" }); + } else |err| { + err catch {}; + } + + exe.addCSourceFiles(.{ + .files = &.{"src/llvm.cpp"}, + .flags = flags.items, + }); + + var dir = try std.fs.cwd().openDir("/usr/include/c++", .{ + .iterate = true, + }); + var iterator = dir.iterate(); + const gcc_version = while (try iterator.next()) |entry| { + if (entry.kind == .directory) { + break entry.name; + } + } else return error.include_cpp_dir_not_found; + dir.close(); + const general_cpp_include_dir = try std.mem.concat(b.allocator, u8, &.{ "/usr/include/c++/", gcc_version }); + exe.addIncludePath(.{ .cwd_relative = general_cpp_include_dir }); + + { + const arch_cpp_include_dir = try std.mem.concat(b.allocator, u8, &.{ general_cpp_include_dir, "/x86_64-pc-linux-gnu" }); + const d2 = std.fs.cwd().openDir(arch_cpp_include_dir, .{}); + if (d2) |_| { + var d = d2 catch unreachable; + d.close(); + exe.addIncludePath(.{ .cwd_relative = arch_cpp_include_dir }); + } else |err| err catch {}; + } + + { + const arch_cpp_include_dir = try std.mem.concat(b.allocator, u8, &.{ "/usr/include/x86_64-linux-gnu/c++/", gcc_version }); + const d2 = std.fs.cwd().openDir(arch_cpp_include_dir, .{}); + if (d2) |_| { + var d = d2 catch unreachable; + d.close(); + exe.addIncludePath(.{ .cwd_relative = arch_cpp_include_dir }); + } else |err| err catch {}; + } + + var found_libcpp = false; + + if (std.fs.cwd().openFile("/usr/lib/libstdc++.so.6", .{})) |file| { + file.close(); + found_libcpp = true; + exe.addObjectFile(.{ .cwd_relative = "/usr/lib/libstdc++.so.6" }); + } else |err| { + err catch {}; + } + + if (std.fs.cwd().openFile("/usr/lib/x86_64-linux-gnu/libstdc++.so.6", .{})) |file| { + file.close(); + found_libcpp = true; + exe.addObjectFile(.{ .cwd_relative = "/usr/lib/x86_64-linux-gnu/libstdc++.so.6" }); + } else |err| { + err catch {}; + } + + if (!found_libcpp) { + return error.libcpp_not_found; + } + + const needed_libraries: []const []const u8 = &.{ "unwind", "z", "zstd" }; + for (needed_libraries) |lib| { + exe.linkSystemLibrary(lib); + } + + for (llvm_libs.items) |lib| { + exe.linkSystemLibrary(lib); + } + + const lld_libs: []const []const u8 = &.{ "lldCommon", "lldCOFF", "lldELF", "lldMachO", "lldMinGW", "lldWasm" }; + for (lld_libs) |lib| { + exe.linkSystemLibrary(lib); + } b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); + for ([_]bool{ false, true }) |is_test| { + const run_step_name = switch (is_test) { + true => "test", + false => "run", + }; + + const debug_step_name = switch (is_test) { + true => "debug_test", + false => "debug", + }; + + const command = b.addRunArtifact(exe); + command.step.dependOn(b.getInstallStep()); + + if (is_test) { + command.addArg("test"); + } + + if (b.args) |args| { + command.addArgs(args); + } + + const run_step = b.step(run_step_name, ""); + run_step.dependOn(&command.step); + + const debug_command = std.Build.Step.Run.create(b, b.fmt("{s} {s}", .{ debug_step_name, exe.name })); + debug_command.addArg("gdb"); + debug_command.addArg("-ex"); + debug_command.addArg("r"); + debug_command.addArg("--args"); + debug_command.addArtifactArg(exe); + + if (is_test) { + debug_command.addArg("test"); + } + + if (b.args) |args| { + debug_command.addArgs(args); + } + + const debug_step = b.step(debug_step_name, ""); + debug_step.dependOn(&debug_command.step); } - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); - - const debug_cmd = debug_binary(b, exe); - const debug_step = b.step("debug", "Debug the app"); - debug_step.dependOn(&debug_cmd.step); - - const exe_unit_tests = b.addTest(.{ - .root_module = exe_mod, - .link_libc = true, - }); - - llvm.link(exe); - - const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); - - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_exe_unit_tests.step); - - const debug_test_cmd = debug_binary(b, exe_unit_tests); - const debug_test_step = b.step("debug_test", "Debug the tests"); - debug_test_step.dependOn(&debug_test_cmd.step); } diff --git a/src/compiler.bbb b/src/compiler.bbb index 5b48559..93fa3e9 100644 --- a/src/compiler.bbb +++ b/src/compiler.bbb @@ -17,7 +17,7 @@ c_string_length = fn (c_string: &u8) u64 c_string_to_slice = fn (c_string: &u8) []u8 { >length = c_string_length(c_string); - return #slice(c_string, length); + return c_string[0..length]; } string_equal = fn(a: []u8, b: []u8) u1 @@ -251,10 +251,10 @@ global_state_initialize = fn () void return 1; } - //if (!string_equal(#slice(relative_file_path, extension_start), ".bbb")) - //{ - // return 1; - //} + if (!string_equal(relative_file_path[extension_start..], ".bbb")) + { + return 1; + } global_state_initialize(); return 0; diff --git a/src/converter.zig b/src/converter.zig index 9bbc4ff..4bcc43b 100644 --- a/src/converter.zig +++ b/src/converter.zig @@ -19,7 +19,7 @@ const max_argument_count = 64; fn array_type_name(arena: *Arena, array_type: ArrayType) [:0]const u8 { var buffer: [256]u8 = undefined; - var i: usize = 0; + var i: u64 = 0; buffer[i] = left_bracket; i += 1; i += lib.string_format.integer_decimal(buffer[i..], array_type.element_count.?); @@ -51,14 +51,6 @@ fn is_identifier_ch(ch: u8) bool { return is_identifier_start_ch(ch) or is_decimal_ch(ch); } -fn string_to_enum(comptime E: type, string: []const u8) ?E { - inline for (@typeInfo(E).@"enum".fields) |e| { - if (lib.string.equal(e.name, string)) { - return @field(E, e.name); - } - } else return null; -} - const GlobalKeyword = enum { @"export", @"extern", @@ -122,6 +114,7 @@ const Module = struct { slice_type_count: u32 = 0, anonymous_pair_type_count: u32 = 0, arena_restore_position: u64, + silent: bool, fn get_zero_value(module: *Module, ty: *Type) *Value { const value = module.values.add(); @@ -785,7 +778,7 @@ const Module = struct { } } - pub fn get_type(module: *Module, index: usize) *Type { + pub fn get_type(module: *Module, index: u64) *Type { assert(index < module.types.count); const result = &module.types.buffer[index]; return result; @@ -794,7 +787,7 @@ const Module = struct { pub fn integer_type(module: *Module, bit_count: u32, sign: bool) *Type { switch (bit_count) { 1...64 => { - const index = @as(usize, @intFromBool(sign)) * 64 + bit_count; + const index = @as(u64, @intFromBool(sign)) * 64 + bit_count; const result = module.get_type(index); assert(result.bb == .integer); assert(result.bb.integer.bit_count == bit_count); @@ -839,7 +832,7 @@ const Module = struct { @trap(); } - pub fn initialize(arena: *Arena, options: ConvertOptions) *Module { + pub fn initialize(arena: *Arena, options: Options) *Module { const arena_restore_position = arena.position; const context = llvm.Context.create(); const handle = context.create_module(options.name); @@ -897,6 +890,7 @@ const Module = struct { }, }, .arena_restore_position = arena_restore_position, + .silent = options.silent, }; module.void_type = module.types.add(.{ @@ -911,7 +905,7 @@ const Module = struct { for ([2]bool{ false, true }) |sign| { for (1..64 + 1) |bit_count| { const name_buffer = [3]u8{ if (sign) 's' else 'u', @intCast(if (bit_count < 10) bit_count % 10 + '0' else bit_count / 10 + '0'), if (bit_count > 9) @intCast(bit_count % 10 + '0') else 0 }; - const name_length = @as(usize, 2) + @intFromBool(bit_count > 9); + const name_length = @as(u64, 2) + @intFromBool(bit_count > 9); const name = arena.duplicate_string(name_buffer[0..name_length]); @@ -995,11 +989,6 @@ const Module = struct { alignment: ?u32 = null, }; - const Slice = struct { - type: *Type, - alignment: ?u32 = null, - }; - pub fn get_pointer_type(module: *Module, pointer: Pointer) *Type { const p = PointerType{ .type = pointer.type, @@ -1034,6 +1023,11 @@ const Module = struct { return pointer_type; } + const Slice = struct { + type: *Type, + alignment: ?u32 = null, + }; + pub fn get_slice_type(module: *Module, slice: Slice) *Type { const alignment = if (slice.alignment) |a| a else slice.type.get_byte_alignment(); const all_types = module.types.get(); @@ -1198,7 +1192,7 @@ pub const Value = struct { const Array = struct { buffer: [1024]Value = undefined, - count: usize = 0, + count: u64 = 0, pub fn add(values: *Array) *Value { const result = &values.buffer[values.count]; @@ -1220,8 +1214,8 @@ pub const Value = struct { const Field = struct { name: []const u8, type: *Type, - bit_offset: usize, - byte_offset: usize, + bit_offset: u64, + byte_offset: u64, }; const FunctionType = struct { @@ -1254,7 +1248,7 @@ const Bits = struct { }; pub const ArrayType = struct { - element_count: ?usize, + element_count: ?u64, element_type: *Type, }; @@ -1456,7 +1450,7 @@ pub const Type = struct { const Array = struct { buffer: [1024]Type = undefined, - count: usize = 0, + count: u64 = 0, const buffer_size = 1024; @@ -1528,9 +1522,9 @@ pub const Variable = struct { const Converter = struct { content: []const u8, - offset: usize, - line_offset: usize, - line_character_offset: usize, + offset: u64, + line_offset: u64, + line_character_offset: u64, fn get_line(converter: *const Converter) u32 { return @intCast(converter.line_offset + 1); @@ -1592,7 +1586,7 @@ const Converter = struct { } pub fn parse_condition_raw(noalias converter: *Converter, noalias module: *Module) *Value { - const condition = converter.parse_value(module, null, .value); + const condition = converter.parse_value(module, .{}); const boolean_type = module.integer_type(1, false); if (condition.type != boolean_type) { const llvm_value = switch (condition.type.bb) { @@ -1652,7 +1646,10 @@ const Converter = struct { const slice_type = module.get_slice_type(.{ .type = element_type }); return slice_type; } else { - const length_expression = converter.parse_value(module, module.integer_type(64, false), .value); + const length_expression = converter.parse_value(module, ValueBuilder{ + .type = module.integer_type(64, false), + .kind = .value, + }); converter.skip_space(); converter.expect_character(right_bracket); @@ -2296,7 +2293,10 @@ const Converter = struct { converter.expect_character('='); - const value = converter.parse_value(module, local_type_stated, .value); + const value = converter.parse_value(module, .{ + .type = local_type_stated, + .kind = .value, + }); const local_storage = module.values.add(); const is_inferred = local_type_stated == null; const is_inferred_pointer = is_inferred and value.dereference_to_assign; @@ -2375,8 +2375,16 @@ const Converter = struct { } else if (is_identifier_start_ch(statement_start_ch)) { const statement_start_identifier = converter.parse_identifier(); - if (string_to_enum(StatementStartKeyword, statement_start_identifier)) |statement_start_keyword| { + if (lib.string.to_enum(StatementStartKeyword, statement_start_identifier)) |statement_start_keyword| { switch (statement_start_keyword) { + ._ => { + converter.skip_space(); + converter.expect_character('='); + converter.skip_space(); + _ = converter.parse_value(module, .{ + .kind = .value, + }); + }, .@"return" => { converter.skip_space(); @@ -2386,7 +2394,10 @@ const Converter = struct { @trap(); } else { // TODO: take ABI into account - const return_value = converter.parse_value(module, return_type_abi.semantic_type, .value); + const return_value = converter.parse_value(module, .{ + .kind = .value, + .type = return_type_abi.semantic_type, + }); if (module.llvm.di_builder) |_| { module.llvm.builder.set_current_debug_location(statement_debug_location); @@ -2574,7 +2585,9 @@ const Converter = struct { } else { converter.offset -= statement_start_identifier.len; - const v = converter.parse_value(module, null, .maybe_pointer); + const v = converter.parse_value(module, .{ + .kind = .maybe_pointer, + }); converter.skip_space(); @@ -2731,7 +2744,10 @@ const Converter = struct { => 3, }; - const right_side = converter.parse_value(module, store_type, .value); + const right_side = converter.parse_value(module, .{ + .type = store_type, + .kind = .value, + }); const right_llvm = right_side.llvm; converter.skip_space(); @@ -2862,12 +2878,865 @@ const Converter = struct { maybe_pointer, }; - fn parse_value(noalias converter: *Converter, noalias module: *Module, maybe_expected_type: ?*Type, value_kind: ValueKind) *Value { + const Precedence = enum { + none, + assignment, + @"or", + @"and", + comparison, + bitwise, + shifting, + add_like, + div_like, + prefix, + aggregate_initialization, + postfix, + + pub fn increment(precedence: Precedence) Precedence { + return @enumFromInt(@intFromEnum(precedence) + 1); + } + }; + + const ExpressionOperator = enum {}; + + const Operator = enum { + none, + }; + + const ComparisonOperator = enum { + compare_equal, + compare_not_equal, + compare_less_than, + compare_less_equal, + compare_greater_than, + compare_greater_equal, + }; + + const AssignmentOp = enum(u8) { + @"=" = '=', + @"+=" = '+', + @"-=" = '-', + @"*=" = '*', + @"/=" = '/', + @"%=" = '%', + @"&=" = '&', + @"|=" = '|', + @"^=" = '^', + @"<<=" = '<', + @">>=" = '>', + }; + + const ArithmeticBinaryOperator = enum(u8) { + @"+" = '+', + @"-" = '-', + @"*" = '*', + @"/" = '/', + @"%" = '%', + @"&" = '&', + @"|" = '|', + @"^" = '^', + @"<<" = '<', + @">>" = '>', + }; + + const Rule = struct { + before: ?*const Rule.Function, + after: ?*const Rule.Function, + precedence: Precedence, + + const Function = fn (noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value; + }; + + fn rule_before_identifier(noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value { + const identifier = value_builder.token.identifier; + const current_function = module.current_function orelse converter.report_error(); + const variable = if (current_function.value.bb.function.locals.find(identifier)) |local| local else if (current_function.value.bb.function.arguments.find(identifier)) |argument| argument else if (module.globals.find(identifier)) |global| global else converter.report_error(); + assert(variable.value.type.bb == .pointer); + return variable.value; + } + + fn rule_before_value_keyword(noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value { + _ = value_builder; + _ = module; + _ = converter; + @trap(); + } + + fn rule_before_value_intrinsic(noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value { + const expected_type = value_builder.type; + const value_intrinsic = value_builder.token.value_intrinsic; + const has_parenthesis = switch (value_intrinsic) { + .byte_size, + .cast, + .cast_to, + .extend, + .integer_max, + .int_from_enum, + .int_from_pointer, + .pointer_cast, + .select, + .trap, + .truncate, + .va_start, + .va_end, + .va_copy, + .va_arg, + => true, + }; + + if (has_parenthesis) { + converter.expect_character(left_parenthesis); + } + + const value = switch (value_intrinsic) { + .integer_max => blk: { + const ty = converter.parse_type(module); + if (ty.bb != .integer) { + converter.report_error(); + } + const bit_count = ty.bb.integer.bit_count; + const max_value = if (bit_count == 64) ~@as(u64, 0) else (@as(u64, 1) << @intCast(bit_count - @intFromBool(ty.bb.integer.signed))) - 1; + const expected_ty = expected_type orelse ty; + if (ty.get_bit_size() > expected_ty.get_bit_size()) { + converter.report_error(); + } + const constant_integer = expected_ty.llvm.handle.to_integer().get_constant(max_value, @intFromBool(false)); + const value = module.values.add(); + value.* = .{ + .llvm = constant_integer.to_value(), + .type = expected_ty, + .bb = .{ + .constant_integer = .{ + .value = max_value, + .signed = false, + }, + }, + .lvalue = false, + .dereference_to_assign = false, + }; + break :blk value; + }, + else => @trap(), + }; + + if (has_parenthesis) { + converter.skip_space(); + converter.expect_character(right_parenthesis); + } + + return value; + } + + const Unary = enum { + @"-", + @"+", + }; + + fn rule_before_unary(noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value { + assert(value_builder.left == null); + const unary_token = value_builder.token; + const unary_expression: Unary = switch (unary_token) { + .none => unreachable, + .@"-" => .@"-", + else => |t| @panic(@tagName(t)), + }; + const right = converter.parse_precedence(module, value_builder.with_precedence(.prefix).with_token(.none)); + return switch (unary_expression) { + .@"+" => @trap(), + .@"-" => b: { + const value = module.values.add(); + value.* = .{ + .llvm = module.negate_value_llvm(right), + .bb = .instruction, + .type = right.type, + .lvalue = false, + .dereference_to_assign = false, + }; + break :b value; + }, + }; + } + + const BinaryOperationKind = enum { + integer_add, + integer_sub, + integer_mul, + integer_udiv, + integer_sdiv, + integer_urem, + integer_srem, + integer_and, + integer_or, + integer_xor, + integer_shl, + integer_ashr, + integer_lshr, + }; + + fn rule_after_binary(noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value { + const binary_operator_token = value_builder.token; + const binary_operator_token_precedence = rules[@intFromEnum(binary_operator_token)].precedence; + const left = value_builder.left orelse converter.report_error(); + assert(binary_operator_token_precedence != .assignment); // TODO: this may be wrong. Assignment operator is not allowed in expressions + const right_precedence = if (binary_operator_token_precedence == .assignment) .assignment else binary_operator_token_precedence.increment(); + const right = converter.parse_precedence(module, value_builder.with_precedence(right_precedence).with_token(.none).with_left(null)); + + const binary_operation_type = value_builder.type orelse converter.report_error(); + + const binary_operation_kind: BinaryOperationKind = switch (binary_operator_token) { + .none => unreachable, + .@"+" => switch (binary_operation_type.bb) { + .integer => .integer_add, + else => @trap(), + }, + .@"-" => switch (binary_operation_type.bb) { + .integer => .integer_sub, + else => @trap(), + }, + .@"*" => switch (binary_operation_type.bb) { + .integer => .integer_mul, + else => @trap(), + }, + .@"/" => switch (binary_operation_type.bb) { + .integer => |integer| switch (integer.signed) { + true => .integer_sdiv, + false => .integer_udiv, + }, + else => @trap(), + }, + .@"%" => switch (binary_operation_type.bb) { + .integer => |integer| switch (integer.signed) { + true => .integer_srem, + false => .integer_urem, + }, + else => @trap(), + }, + .@"&" => switch (binary_operation_type.bb) { + .integer => .integer_and, + else => @trap(), + }, + .@"|" => switch (binary_operation_type.bb) { + .integer => .integer_or, + else => @trap(), + }, + .@"^" => switch (binary_operation_type.bb) { + .integer => .integer_xor, + else => @trap(), + }, + .@"<<" => switch (binary_operation_type.bb) { + .integer => .integer_shl, + else => @trap(), + }, + .@">>" => switch (binary_operation_type.bb) { + .integer => |integer| switch (integer.signed) { + true => .integer_ashr, + false => .integer_lshr, + }, + else => @trap(), + }, + else => @trap(), + }; + + const result = module.values.add(); + + const llvm_value = switch (binary_operation_kind) { + .integer_add => module.llvm.builder.create_add(left.llvm, right.llvm), + .integer_sub => module.llvm.builder.create_sub(left.llvm, right.llvm), + .integer_mul => module.llvm.builder.create_mul(left.llvm, right.llvm), + .integer_udiv => module.llvm.builder.create_udiv(left.llvm, right.llvm), + .integer_sdiv => module.llvm.builder.create_sdiv(left.llvm, right.llvm), + .integer_urem => module.llvm.builder.create_urem(left.llvm, right.llvm), + .integer_srem => module.llvm.builder.create_srem(left.llvm, right.llvm), + .integer_and => module.llvm.builder.create_and(left.llvm, right.llvm), + .integer_or => module.llvm.builder.create_or(left.llvm, right.llvm), + .integer_xor => module.llvm.builder.create_xor(left.llvm, right.llvm), + .integer_shl => module.llvm.builder.create_shl(left.llvm, right.llvm), + .integer_ashr => module.llvm.builder.create_ashr(left.llvm, right.llvm), + .integer_lshr => module.llvm.builder.create_lshr(left.llvm, right.llvm), + }; + + result.* = .{ + .llvm = llvm_value, + .type = binary_operation_type, + .dereference_to_assign = false, + .lvalue = false, + .bb = .instruction, + }; + + return result; + } + + fn rule_before_integer(noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value { + const v = value_builder.token.integer.value; + const value = module.values.add(); + const expected_ty = value_builder.type orelse converter.report_error(); + if (expected_ty.bb != .integer) { + converter.report_error(); + } + value.* = .{ + .llvm = expected_ty.llvm.handle.to_integer().get_constant(v, @intFromBool(false)).to_value(), + .type = expected_ty, + .bb = .{ + .constant_integer = .{ + .value = v, + .signed = false, + }, + }, + .dereference_to_assign = false, + .lvalue = false, + }; + return value; + } + + const Token = union(Id) { + none, + end_of_statement, + integer: Integer, + identifier: []const u8, + value_keyword: ValueKeyword, + value_intrinsic: ValueIntrinsic, + // Assignment operators + @"=", + @"+=", + @"-=", + @"*=", + @"/=", + @"%=", + @"&=", + @"|=", + @"^=", + @"<<=", + @">>=", + // Comparison operators + @"==", + @"!=", + @"<", + @">", + @"<=", + @">=", + // Logical AND + @"and", + @"and?", + // Logical OR + @"or", + @"or?", + // Add-like operators + @"+", + @"-", + // Div-like operators + @"*", + @"/", + @"%", + // Bitwise operators + @"&", + @"|", + @"^", + // Shifting operators + @"<<", + @">>", + + const Id = enum { + none, + end_of_statement, + integer, + identifier, + value_keyword, + value_intrinsic, + // Assignment operators + @"=", + @"+=", + @"-=", + @"*=", + @"/=", + @"%=", + @"&=", + @"|=", + @"^=", + @"<<=", + @">>=", + // Comparison operators + @"==", + @"!=", + @"<", + @">", + @"<=", + @">=", + // Logical AND + @"and", + @"and?", + // Logical OR + @"or", + @"or?", + // Add-like operators + @"+", + @"-", + // Div-like operators + @"*", + @"/", + @"%", + // Bitwise operators + @"&", + @"|", + @"^", + // Shifting operators + @"<<", + @">>", + }; + + const Integer = struct { + value: u64, + kind: Kind, + + const Kind = enum { + hexadecimal, + decimal, + octal, + binary, + }; + }; + }; + + const rules = blk: { + var r: [@typeInfo(Token.Id).@"enum".fields.len]Rule = undefined; + var count: u32 = 0; + r[@intFromEnum(Token.Id.none)] = .{ + .before = null, + .after = null, + .precedence = .none, + }; + count += 1; + r[@intFromEnum(Token.Id.end_of_statement)] = .{ + .before = null, + .after = null, + .precedence = .none, + }; + count += 1; + r[@intFromEnum(Token.Id.identifier)] = .{ + .before = &rule_before_identifier, + .after = null, + .precedence = .none, + }; + count += 1; + r[@intFromEnum(Token.Id.value_keyword)] = .{ + .before = &rule_before_value_keyword, + .after = null, + .precedence = .none, + }; + count += 1; + r[@intFromEnum(Token.Id.value_intrinsic)] = .{ + .before = &rule_before_value_intrinsic, + .after = null, + .precedence = .none, + }; + count += 1; + r[@intFromEnum(Token.Id.integer)] = .{ + .before = &rule_before_integer, + .after = null, + .precedence = .none, + }; + count += 1; + + const assignment_operators = [_]Token.Id{ + .@"=", + .@"+=", + .@"-=", + .@"*=", + .@"/=", + .@"%=", + .@"&=", + .@"|=", + .@"^=", + .@"<<=", + .@">>=", + }; + + for (assignment_operators) |assignment_operator| { + r[@intFromEnum(assignment_operator)] = .{ + .before = null, + .after = rule_after_binary, + .precedence = .assignment, + }; + count += 1; + } + + const comparison_operators = [_]Token.Id{ + .@"==", + .@"!=", + .@"<", + .@">", + .@"<=", + .@">=", + }; + + for (comparison_operators) |comparison_operator| { + r[@intFromEnum(comparison_operator)] = .{ + .before = null, + .after = rule_after_binary, + .precedence = .comparison, + }; + count += 1; + } + + const and_operators = [_]Token.Id{ + .@"and", + .@"and?", + }; + + for (and_operators) |and_operator| { + r[@intFromEnum(and_operator)] = .{ + .before = null, + .after = rule_after_binary, + .precedence = .@"or", + }; + count += 1; + } + + const or_operators = [_]Token.Id{ + .@"or", + .@"or?", + }; + + for (or_operators) |or_operator| { + r[@intFromEnum(or_operator)] = .{ + .before = null, + .after = rule_after_binary, + .precedence = .@"or", + }; + count += 1; + } + + const add_like_operators = [_]Token.Id{ + .@"+", + .@"-", + }; + + for (add_like_operators) |add_like_operator| { + r[@intFromEnum(add_like_operator)] = .{ + .before = rule_before_unary, + .after = rule_after_binary, + .precedence = .add_like, + }; + count += 1; + } + + const div_like_operators = [_]Token.Id{ + .@"*", + .@"/", + .@"%", + }; + + for (div_like_operators) |div_like_operator| { + r[@intFromEnum(div_like_operator)] = .{ + .before = null, + .after = rule_after_binary, + .precedence = .div_like, + }; + count += 1; + } + + const bitwise_operators = [_]Token.Id{ + .@"&", + .@"|", + .@"^", + }; + + for (bitwise_operators) |bitwise_operator| { + r[@intFromEnum(bitwise_operator)] = .{ + .before = null, + .after = rule_after_binary, + .precedence = .bitwise, + }; + count += 1; + } + + const shifting_operators = [_]Token.Id{ + .@"<<", + .@">>", + }; + for (shifting_operators) |shifting_operator| { + r[@intFromEnum(shifting_operator)] = .{ + .before = null, + .after = rule_after_binary, + .precedence = .shifting, + }; + count += 1; + } + + assert(count == r.len); + break :blk r; + }; + + fn tokenize(converter: *Converter) Token { + converter.skip_space(); + + const start_index = converter.offset; + if (start_index == converter.content.len) { + converter.report_error(); + } + + const start_character = converter.content[start_index]; + const result: Token = switch (start_character) { + ';' => blk: { + converter.offset += 1; + break :blk .end_of_statement; + }, + 'a'...'z', 'A'...'Z', '_' => blk: { + assert(is_identifier_start_ch(start_character)); + const identifier = converter.parse_identifier(); + const token: Token = if (lib.string.to_enum(ValueKeyword, identifier)) |value_keyword| .{ .value_keyword = value_keyword } else .{ .identifier = identifier }; + break :blk token; + }, + '#' => if (is_identifier_start_ch(converter.content[converter.offset + 1])) blk: { + converter.offset += 1; + const value_intrinsic_identifier = converter.parse_identifier(); + const value_intrinsic = lib.string.to_enum(ValueIntrinsic, value_intrinsic_identifier) orelse converter.report_error(); + break :blk .{ + .value_intrinsic = value_intrinsic, + }; + } else { + @trap(); + }, + '0' => blk: { + const next_ch = converter.content[start_index + 1]; + const token_integer_kind: Token.Integer.Kind = switch (next_ch) { + 'x' => .hexadecimal, + 'o' => .octal, + 'b' => .binary, + else => .decimal, + }; + const value: u64 = switch (token_integer_kind) { + .decimal => switch (next_ch) { + 0...9 => converter.report_error(), + else => b: { + converter.offset += 1; + break :b 0; + }, + }, + else => @trap(), + }; + + if (converter.content[converter.offset] == '.') { + @trap(); + } else { + break :blk .{ .integer = .{ .value = value, .kind = token_integer_kind } }; + } + }, + '1'...'9' => blk: { + const decimal = converter.parse_decimal(); + if (converter.content[converter.offset] == '.') { + @trap(); + } else { + break :blk .{ .integer = .{ .value = decimal, .kind = .decimal } }; + } + }, + '+', '-', '*', '/', '%', '&', '|', '^' => |c| blk: { + const next_ch = converter.content[start_index + 1]; + const token_id: Token.Id = switch (next_ch) { + '=' => @trap(), + else => switch (c) { + '+' => .@"+", + '-' => .@"-", + '*' => .@"*", + '/' => .@"/", + '%' => .@"%", + '&' => .@"&", + '|' => .@"|", + '^' => .@"^", + else => unreachable, + }, + }; + + const token = switch (token_id) { + else => unreachable, + inline .@"+", + .@"-", + .@"*", + .@"/", + .@"%", + .@"&", + .@"|", + .@"^", + => |tid| @unionInit(Token, @tagName(tid), {}), + }; + + converter.offset += @as(u32, 1) + @intFromBool(next_ch == '='); + + break :blk token; + }, + '<' => blk: { + const next_ch = converter.content[start_index + 1]; + const token_id: Token.Id = switch (next_ch) { + '<' => switch (converter.content[start_index + 2]) { + '=' => .@"<<=", + else => .@"<<", + }, + '=' => .@"<=", + else => .@"<", + }; + + converter.offset += switch (token_id) { + .@"<<=" => 3, + .@"<<", .@"<=" => 2, + .@"<" => 1, + else => unreachable, + }; + + const token = switch (token_id) { + else => unreachable, + inline .@"<<=", + .@"<<", + .@"<=", + .@"<", + => |tid| @unionInit(Token, @tagName(tid), {}), + }; + break :blk token; + }, + '>' => blk: { + const next_ch = converter.content[start_index + 1]; + const token_id: Token.Id = switch (next_ch) { + '<' => switch (converter.content[start_index + 2]) { + '=' => .@">>=", + else => .@">>", + }, + '=' => .@">=", + else => .@">", + }; + + converter.offset += switch (token_id) { + .@">>=" => 3, + .@">>", .@">=" => 2, + .@">" => 1, + else => unreachable, + }; + + const token = switch (token_id) { + else => unreachable, + inline .@">>=", + .@">>", + .@">=", + .@">", + => |tid| @unionInit(Token, @tagName(tid), {}), + }; + break :blk token; + }, + else => @trap(), + }; + + assert(start_index != converter.offset); + + return result; + } + + fn parse_precedence(noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value { + assert(value_builder.token == .none); + const token = converter.tokenize(); + const rule = &rules[@intFromEnum(token)]; + if (rule.before) |before| { + const left = before(converter, module, value_builder.with_precedence(.none).with_token(token)); + + const result = converter.parse_precedence_left(module, value_builder.with_left(left)); + return result; + } else { + converter.report_error(); + } + } + + fn parse_precedence_left(noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value { + var result = value_builder.left; + const precedence = value_builder.precedence; + + while (true) { + const checkpoint = converter.offset; + const token = converter.tokenize(); + const token_rule = &rules[@intFromEnum(token)]; + if (@intFromEnum(precedence) > @intFromEnum(token_rule.precedence)) { + converter.offset = checkpoint; + break; + } + + const after_rule = token_rule.after orelse converter.report_error(); + const old = result; + const new = after_rule(converter, module, value_builder.with_token(token).with_precedence(.none).with_left(old)); + result = new; + } + + return result.?; + } + + const parse_value = parse_value2; + fn parse_value2(noalias converter: *Converter, noalias module: *Module, value_builder: ValueBuilder) *Value { + assert(value_builder.precedence == .none); + assert(value_builder.left == null); + const value = converter.parse_precedence(module, value_builder.with_precedence(.assignment)); + + const result = if (value_builder.type) |expected_type| blk: { + if (expected_type != value.type) { + const evaluation_kind = expected_type.get_evaluation_kind(); + if (value.lvalue and value.type.bb.pointer.type == expected_type) { + switch (evaluation_kind) { + .scalar => { + const load = module.create_load(.{ + .type = expected_type, + .value = value.llvm, + .alignment = value.type.bb.pointer.alignment, + }); + const result = module.values.add(); + result.* = .{ + .llvm = load, + .type = expected_type, + .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, + }; + break :blk result; + }, + else => @trap(), + } + } else { + @trap(); + } + } else { + break :blk value; + } + } else { + @trap(); + }; + + return result; + } + + const ValueBuilder = struct { + kind: ValueKind = .value, + type: ?*Type = null, + precedence: Precedence = .none, + left: ?*Value = null, + token: Token = .none, + + fn with_token(vb: ValueBuilder, token: Token) ValueBuilder { + var v = vb; + v.token = token; + return v; + } + + fn with_precedence(vb: ValueBuilder, precedence: Precedence) ValueBuilder { + var v = vb; + v.precedence = precedence; + return v; + } + + fn with_left(vb: ValueBuilder, left: ?*Value) ValueBuilder { + var v = vb; + v.left = left; + return v; + } + }; + + fn parse_value1(noalias converter: *Converter, noalias module: *Module, maybe_expected_type: ?*Type, value_kind: ValueKind) *Value { converter.skip_space(); var value_state = ExpressionState.none; var previous_value: ?*Value = null; - var iterations: usize = 0; + var iterations: u64 = 0; var iterative_expected_type = maybe_expected_type; const value: *Value = while (true) : (iterations += 1) { @@ -3090,6 +3959,10 @@ const Converter = struct { }, else => converter.report_error(), }, + '.' => switch (converter.content[converter.offset + 1]) { + '.' => .none, + else => @trap(), + }, else => converter.report_error(), }; @@ -3145,7 +4018,6 @@ const Converter = struct { int_from_pointer, pointer_cast, select, - slice, trap, truncate, va_start, @@ -3158,7 +4030,7 @@ const Converter = struct { converter.expect_character('#'); converter.skip_space(); const intrinsic_name = converter.parse_identifier(); - const intrinsic_keyword = string_to_enum(ValueIntrinsic, intrinsic_name) orelse converter.report_error(); + const intrinsic_keyword = lib.string.to_enum(ValueIntrinsic, intrinsic_name) orelse converter.report_error(); converter.skip_space(); converter.expect_character(left_parenthesis); @@ -3197,7 +4069,7 @@ const Converter = struct { const destination_type = converter.parse_type(module); converter.skip_space(); converter.expect_character(','); - const source_value = converter.parse_value(module, null, .value); + const source_value = converter.parse_value(module, .{}); converter.skip_space(); converter.expect_character(')'); @@ -3216,7 +4088,7 @@ const Converter = struct { } }, .extend => { - const source_value = converter.parse_value(module, null, .value); + const source_value = converter.parse_value(module, .{}); converter.skip_space(); converter.expect_character(right_parenthesis); const source_type = source_value.type; @@ -3273,7 +4145,7 @@ const Converter = struct { return value; }, .int_from_enum => { - const source_value = converter.parse_value(module, null, .value); + const source_value = converter.parse_value(module, .{}); converter.skip_space(); converter.expect_character(right_parenthesis); if (source_value.type.bb != .enumerator) { @@ -3296,7 +4168,7 @@ const Converter = struct { return value; }, .int_from_pointer => { - const source_value = converter.parse_value(module, null, .value); + const source_value = converter.parse_value(module, .{}); converter.skip_space(); converter.expect_character(right_parenthesis); if (source_value.type.bb != .pointer) { @@ -3328,7 +4200,7 @@ const Converter = struct { if (ty.bb != .pointer) { converter.report_error(); } - const source_value = converter.parse_value(module, null, .value); + const source_value = converter.parse_value(module, .{}); converter.skip_space(); converter.expect_character(right_parenthesis); if (source_value.type.bb != .pointer) { @@ -3354,7 +4226,9 @@ const Converter = struct { converter.expect_character(','); converter.skip_space(); - const true_value = converter.parse_value(module, expected_type, .value); + const true_value = converter.parse_value(module, .{ + .type = expected_type, + }); converter.skip_space(); converter.expect_character(','); @@ -3362,7 +4236,9 @@ const Converter = struct { const expected_ty = expected_type orelse true_value.type; - const false_value = converter.parse_value(module, expected_ty, .value); + const false_value = converter.parse_value(module, .{ + .type = expected_ty, + }); converter.skip_space(); converter.expect_character(right_parenthesis); @@ -3385,123 +4261,123 @@ const Converter = struct { }; return value; }, - .slice => { - const value = converter.parse_value(module, null, .value); - const u64_type = module.integer_type(64, false); - - converter.skip_space(); - - var found_right_parenthesis = false; - const second_argument: ?*Value = if (converter.consume_character_if_match(',')) b: { - converter.skip_space(); - if (!converter.consume_character_if_match(right_parenthesis)) { - break :b converter.parse_value(module, null, .value); - } else { - found_right_parenthesis = true; - break :b null; - } - } else null; - - const parse_third_argument = if (!found_right_parenthesis) b: { - converter.skip_space(); - const second_comma = converter.consume_character_if_match(','); - converter.skip_space(); - found_right_parenthesis = converter.consume_character_if_match(right_parenthesis); - if (second_comma and !found_right_parenthesis) { - @trap(); - } - - if (!found_right_parenthesis) { - converter.report_error(); - } - break :b false; - } else false; - const third_argument: ?*Value = if (parse_third_argument) converter.parse_value(module, null, .value) else null; - const element_count = @as(u32, 1) + @intFromBool(second_argument != null) + @intFromBool(third_argument != null); - - if (expected_type) |expected_ty| { - if (!expected_ty.is_slice()) { - converter.report_error(); - } - - const slice_type = expected_ty; - const slice_pointer_type = slice_type.bb.structure.fields[0].type; - const slice_element_type = slice_pointer_type.bb.pointer.type; - - assert(slice_type != value.type); - - switch (value.type.bb) { - .pointer => |pointer| { - const pointer_element_type = pointer.type; - if (slice_type == pointer_element_type) switch (element_count) { - 1 => @trap(), - 2 => { - // If a slice is found and two arguments are given, the second argument is a start - @trap(); - }, - 3 => @trap(), - else => unreachable, - } else if (pointer_element_type == slice_element_type) switch (element_count) { - 1 => @trap(), - 2 => { - // If a pointer is found and its element type matches the slice element type, then the second argument is the length of the slice - const length = second_argument orelse unreachable; - if (length.type.bb != .integer) { - converter.report_error(); - } - - if (length.type != u64_type) { - @trap(); - } - - const slice_poison = slice_type.llvm.handle.get_poison(); - const pointer_insert = module.llvm.builder.create_insert_value(slice_poison, value.llvm, 0); - const length_insert = module.llvm.builder.create_insert_value(pointer_insert, length.llvm, 1); - const slice_value = length_insert; - const result = module.values.add(); - result.* = .{ - .llvm = slice_value, - .type = slice_type, - .bb = .instruction, - .lvalue = false, - .dereference_to_assign = false, - }; - return result; - }, - 3 => @trap(), - else => unreachable, - } else switch (pointer_element_type.bb) { - .array => |array| { - const array_element_type = array.element_type; - if (array_element_type == slice_element_type) { - assert(element_count == 1); - const slice_poison = slice_type.llvm.handle.get_poison(); - const pointer_insert = module.llvm.builder.create_insert_value(slice_poison, value.llvm, 0); - const length_value = u64_type.llvm.handle.to_integer().get_constant(array.element_count.?, @intFromBool(false)); - const length_insert = module.llvm.builder.create_insert_value(pointer_insert, length_value.to_value(), 1); - const slice_value = length_insert; - const result = module.values.add(); - result.* = .{ - .llvm = slice_value, - .type = slice_type, - .bb = .instruction, - .lvalue = false, - .dereference_to_assign = false, - }; - return result; - } else { - converter.report_error(); - } - }, - else => @trap(), - } - }, - else => @trap(), - } - } else { - @trap(); - } - }, + // .slice => { + // const value = converter.parse_value(module, null, .value); + // const u64_type = module.integer_type(64, false); + // + // converter.skip_space(); + // + // var found_right_parenthesis = false; + // const second_argument: ?*Value = if (converter.consume_character_if_match(',')) b: { + // converter.skip_space(); + // if (!converter.consume_character_if_match(right_parenthesis)) { + // break :b converter.parse_value(module, null, .value); + // } else { + // found_right_parenthesis = true; + // break :b null; + // } + // } else null; + // + // const parse_third_argument = if (!found_right_parenthesis) b: { + // converter.skip_space(); + // const second_comma = converter.consume_character_if_match(','); + // converter.skip_space(); + // found_right_parenthesis = converter.consume_character_if_match(right_parenthesis); + // if (second_comma and !found_right_parenthesis) { + // @trap(); + // } + // + // if (!found_right_parenthesis) { + // converter.report_error(); + // } + // break :b false; + // } else false; + // const third_argument: ?*Value = if (parse_third_argument) converter.parse_value(module, null, .value) else null; + // const element_count = @as(u32, 1) + @intFromBool(second_argument != null) + @intFromBool(third_argument != null); + // + // if (expected_type) |expected_ty| { + // if (!expected_ty.is_slice()) { + // converter.report_error(); + // } + // + // const slice_type = expected_ty; + // const slice_pointer_type = slice_type.bb.structure.fields[0].type; + // const slice_element_type = slice_pointer_type.bb.pointer.type; + // + // assert(slice_type != value.type); + // + // switch (value.type.bb) { + // .pointer => |pointer| { + // const pointer_element_type = pointer.type; + // if (slice_type == pointer_element_type) switch (element_count) { + // 1 => @trap(), + // 2 => { + // // If a slice is found and two arguments are given, the second argument is a start + // @trap(); + // }, + // 3 => @trap(), + // else => unreachable, + // } else if (pointer_element_type == slice_element_type) switch (element_count) { + // 1 => @trap(), + // 2 => { + // // If a pointer is found and its element type matches the slice element type, then the second argument is the length of the slice + // const length = second_argument orelse unreachable; + // if (length.type.bb != .integer) { + // converter.report_error(); + // } + // + // if (length.type != u64_type) { + // @trap(); + // } + // + // const slice_poison = slice_type.llvm.handle.get_poison(); + // const pointer_insert = module.llvm.builder.create_insert_value(slice_poison, value.llvm, 0); + // const length_insert = module.llvm.builder.create_insert_value(pointer_insert, length.llvm, 1); + // const slice_value = length_insert; + // const result = module.values.add(); + // result.* = .{ + // .llvm = slice_value, + // .type = slice_type, + // .bb = .instruction, + // .lvalue = false, + // .dereference_to_assign = false, + // }; + // return result; + // }, + // 3 => @trap(), + // else => unreachable, + // } else switch (pointer_element_type.bb) { + // .array => |array| { + // const array_element_type = array.element_type; + // if (array_element_type == slice_element_type) { + // assert(element_count == 1); + // const slice_poison = slice_type.llvm.handle.get_poison(); + // const pointer_insert = module.llvm.builder.create_insert_value(slice_poison, value.llvm, 0); + // const length_value = u64_type.llvm.handle.to_integer().get_constant(array.element_count.?, @intFromBool(false)); + // const length_insert = module.llvm.builder.create_insert_value(pointer_insert, length_value.to_value(), 1); + // const slice_value = length_insert; + // const result = module.values.add(); + // result.* = .{ + // .llvm = slice_value, + // .type = slice_type, + // .bb = .instruction, + // .lvalue = false, + // .dereference_to_assign = false, + // }; + // return result; + // } else { + // converter.report_error(); + // } + // }, + // else => @trap(), + // } + // }, + // else => @trap(), + // } + // } else { + // @trap(); + // } + // }, .trap => { converter.expect_character(right_parenthesis); @@ -3527,7 +4403,7 @@ const Converter = struct { return value; }, .truncate => { - const source_value = converter.parse_value(module, null, .value); + const source_value = converter.parse_value(module, .{}); converter.skip_space(); converter.expect_character(right_parenthesis); const destination_type = expected_type orelse converter.report_error(); @@ -3568,7 +4444,10 @@ const Converter = struct { return value; }, .va_end => { - const va_list = converter.parse_value(module, module.get_pointer_type(.{ .type = module.get_va_list_type() }), .pointer); + const va_list = converter.parse_value(module, .{ + .type = module.get_pointer_type(.{ .type = module.get_va_list_type() }), + .kind = .pointer, + }); converter.skip_space(); converter.expect_character(right_parenthesis); const intrinsic_id = module.llvm.intrinsic_table.va_end; @@ -3591,7 +4470,10 @@ const Converter = struct { .va_copy => @trap(), .va_arg => { const va_list_type = module.get_va_list_type(); - const raw_va_list = converter.parse_value(module, module.get_pointer_type(.{ .type = va_list_type }), .pointer); + const raw_va_list = converter.parse_value(module, .{ + .type = module.get_pointer_type(.{ .type = va_list_type }), + .kind = .pointer, + }); const va_list = module.llvm.builder.create_gep(.{ .type = va_list_type.llvm.handle, .aggregate = raw_va_list.llvm, @@ -3731,7 +4613,7 @@ const Converter = struct { converter.expect_character('#'); converter.skip_space(); const intrinsic_name = converter.parse_identifier(); - const intrinsic_keyword = string_to_enum(TypeIntrinsic, intrinsic_name) orelse converter.report_error(); + const intrinsic_keyword = lib.string.to_enum(TypeIntrinsic, intrinsic_name) orelse converter.report_error(); converter.skip_space(); converter.expect_character(left_parenthesis); @@ -3752,7 +4634,6 @@ const Converter = struct { } const ValueKeyword = enum { - @"_", undefined, @"unreachable", zero, @@ -3841,7 +4722,7 @@ const Converter = struct { converter.skip_space(); } else { const identifier = converter.parse_identifier(); - if (string_to_enum(ValueKeyword, identifier)) |value_keyword| switch (value_keyword) { + if (lib.string.to_enum(ValueKeyword, identifier)) |value_keyword| switch (value_keyword) { ._ => converter.report_error(), .undefined => @trap(), .@"unreachable" => @trap(), @@ -4000,7 +4881,7 @@ const Converter = struct { return value; }, .bits => |bits| { - var field_count: usize = 0; + var field_count: u64 = 0; var llvm_value = bits.backing_type.llvm.handle.to_integer().get_constant(0, @intFromBool(false)).to_value(); @@ -4040,7 +4921,7 @@ const Converter = struct { converter.skip_space(); } else { const identifier = converter.parse_identifier(); - if (string_to_enum(ValueKeyword, identifier)) |value_keyword| switch (value_keyword) { + if (lib.string.to_enum(ValueKeyword, identifier)) |value_keyword| switch (value_keyword) { ._ => converter.report_error(), .undefined => @trap(), .zero => { @@ -4088,7 +4969,7 @@ const Converter = struct { const ty = expected_type orelse converter.report_error(); switch (ty.bb) { .array => |*array| { - var element_count: usize = 0; + var element_count: u64 = 0; var element_buffer: [64]*llvm.Value = undefined; var elements_are_constant = true; @@ -4153,6 +5034,39 @@ const Converter = struct { '&' => { converter.offset += 1; const value = converter.parse_value(module, expected_type, .pointer); + + if (expected_type) |expected_ty| { + if (expected_ty.is_slice()) { + switch (value.type.bb) { + .pointer => |pointer| switch (pointer.type.bb) { + .array => |array| { + switch (value_kind) { + .value => { + const slice_poison = expected_ty.llvm.handle.get_poison(); + const pointer_insert = module.llvm.builder.create_insert_value(slice_poison, value.llvm, 0); + const length_value = module.integer_type(64, false).llvm.handle.to_integer().get_constant(array.element_count.?, @intFromBool(false)); + const length_insert = module.llvm.builder.create_insert_value(pointer_insert, length_value.to_value(), 1); + const result = module.values.add(); + result.* = .{ + .llvm = length_insert, + .type = expected_ty, + .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, + }; + return result; + }, + else => |t| @panic(@tagName(t)), + } + }, + else => @trap(), + }, + else => @trap(), + } + @trap(); + } + } + return value; }, '!' => blk: { @@ -4275,7 +5189,7 @@ const Converter = struct { 'a'...'z', 'A'...'Z', '_' => b: { const identifier = converter.parse_identifier(); - if (string_to_enum(ValueKeyword, identifier)) |value_keyword| switch (value_keyword) { + if (lib.string.to_enum(ValueKeyword, identifier)) |value_keyword| switch (value_keyword) { ._ => return module.void_value, .undefined => { const expected_ty = expected_type orelse converter.report_error(); @@ -4473,83 +5387,171 @@ const Converter = struct { const index_type = module.integer_type(64, false); const index = converter.parse_value(module, index_type, .value); - converter.skip_space(); - converter.expect_character(right_bracket); + const ArrayExpressionKind = enum { + array, + slice, + }; + const array_expression_kind: ArrayExpressionKind = if (converter.consume_character_if_match(right_bracket)) .array else .slice; + switch (array_expression_kind) { + .array => { + const llvm_index_type = module.integer_type(64, false).llvm.handle.to_integer(); + const zero_index = llvm_index_type.get_constant(0, @intFromBool(false)).to_value(); - const llvm_index_type = module.integer_type(64, false).llvm.handle.to_integer(); - const zero_index = llvm_index_type.get_constant(0, @intFromBool(false)).to_value(); - - switch (value_kind) { - .pointer, .maybe_pointer => { - @trap(); - }, - .value => { - switch (appointee_type.bb) { - .array => |array| { - const gep = module.llvm.builder.create_gep(.{ - .type = appointee_type.llvm.handle, - .aggregate = variable.value.llvm, - .indices = &.{ zero_index, index.llvm }, - }); - - const load_type = array.element_type; - const load = module.values.add(); - load.* = .{ - .llvm = module.create_load(.{ .type = load_type, .value = gep }), - .type = load_type, - .bb = .instruction, - .lvalue = false, - .dereference_to_assign = false, - }; - break :b load; + switch (value_kind) { + .pointer, .maybe_pointer => { + @trap(); }, - .pointer => |pointer| { - const pointer_load = module.create_load(.{ .type = appointee_type, .value = variable.value.llvm }); - const gep = module.llvm.builder.create_gep(.{ - .type = pointer.type.llvm.handle, - .aggregate = pointer_load, - .indices = &.{index.llvm}, - .inbounds = false, - }); + .value => { + switch (appointee_type.bb) { + .array => |array| { + const gep = module.llvm.builder.create_gep(.{ + .type = appointee_type.llvm.handle, + .aggregate = variable.value.llvm, + .indices = &.{ zero_index, index.llvm }, + }); - const load_type = pointer.type; - const load = module.values.add(); - load.* = .{ - .llvm = module.create_load(.{ .type = load_type, .value = gep }), - .type = load_type, - .bb = .instruction, - .lvalue = false, - .dereference_to_assign = false, - }; - break :b load; - }, - .structure => |structure| { - if (!structure.is_slice) { - converter.report_error(); + const load_type = array.element_type; + const load = module.values.add(); + load.* = .{ + .llvm = module.create_load(.{ .type = load_type, .value = gep }), + .type = load_type, + .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, + }; + break :b load; + }, + .pointer => |pointer| { + const pointer_load = module.create_load(.{ .type = appointee_type, .value = variable.value.llvm }); + const gep = module.llvm.builder.create_gep(.{ + .type = pointer.type.llvm.handle, + .aggregate = pointer_load, + .indices = &.{index.llvm}, + .inbounds = false, + }); + + const load_type = pointer.type; + const load = module.values.add(); + load.* = .{ + .llvm = module.create_load(.{ .type = load_type, .value = gep }), + .type = load_type, + .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, + }; + break :b load; + }, + .structure => |structure| { + if (!structure.is_slice) { + converter.report_error(); + } + + const gep_to_pointer_field = module.llvm.builder.create_struct_gep(appointee_type.llvm.handle.to_struct(), variable.value.llvm, 0); + const pointer_type = structure.fields[0].type; + const element_type = pointer_type.bb.pointer.type; + const pointer_load = module.create_load(.{ .type = pointer_type, .value = gep_to_pointer_field }); + const gep_to_element = module.llvm.builder.create_gep(.{ + .type = element_type.llvm.handle, + .aggregate = pointer_load, + .indices = &.{index.llvm}, + .inbounds = false, + }); + const element_load = module.create_load(.{ .type = element_type, .value = gep_to_element, .alignment = pointer_type.bb.pointer.alignment }); + const load = module.values.add(); + load.* = .{ + .llvm = element_load, + .type = element_type, + .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, + }; + break :b load; + }, + else => converter.report_error(), } - - const gep_to_pointer_field = module.llvm.builder.create_struct_gep(appointee_type.llvm.handle.to_struct(), variable.value.llvm, 0); - const pointer_type = structure.fields[0].type; - const element_type = pointer_type.bb.pointer.type; - const pointer_load = module.create_load(.{ .type = pointer_type, .value = gep_to_pointer_field }); - const gep_to_element = module.llvm.builder.create_gep(.{ - .type = element_type.llvm.handle, - .aggregate = pointer_load, - .indices = &.{index.llvm}, - .inbounds = false, - }); - const element_load = module.create_load(.{ .type = element_type, .value = gep_to_element, .alignment = pointer_type.bb.pointer.alignment }); - const load = module.values.add(); - load.* = .{ - .llvm = element_load, - .type = element_type, - .bb = .instruction, - .lvalue = false, - .dereference_to_assign = false, - }; - break :b load; }, - else => converter.report_error(), + } + }, + .slice => { + const start_index = index; + converter.expect_character('.'); + converter.expect_character('.'); + converter.skip_space(); + + if (converter.consume_character_if_match(right_bracket)) { + switch (appointee_type.bb) { + .structure => |structure| { + if (!structure.is_slice) { + converter.report_error(); + } + + const slice_type = appointee_type; + const slice_pointer_type = structure.fields[0].type; + const slice_element_type = slice_pointer_type.bb.pointer.type; + const slice_load = module.create_load(.{ .type = slice_type, .value = variable.value.llvm }); + const original_pointer_field = module.llvm.builder.create_extract_value(slice_load, 0); + const original_length_field = module.llvm.builder.create_extract_value(slice_load, 1); + const pointer_field = module.llvm.builder.create_gep(.{ .type = slice_element_type.llvm.handle, .aggregate = original_pointer_field, .indices = &.{start_index.llvm} }); + const length_field = module.llvm.builder.create_sub(original_length_field, start_index.llvm); + + const slice_poison = slice_type.llvm.handle.get_poison(); + const slice_pointer = module.llvm.builder.create_insert_value(slice_poison, pointer_field, 0); + const slice_length = module.llvm.builder.create_insert_value(slice_pointer, length_field, 1); + const slice_value = slice_length; + + const result = module.values.add(); + result.* = .{ + .llvm = slice_value, + .type = slice_type, + .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, + }; + return result; + }, + else => @trap(), + } + } else { + const end_index = converter.parse_value(module, index_type, .value); + converter.skip_space(); + converter.expect_character(right_bracket); + + if (start_index.bb == .constant_integer and end_index.bb == .constant_integer) { + @trap(); + } else if (start_index.bb == .constant_integer) { + switch (appointee_type.bb) { + .pointer => { + const slice_type = module.get_slice_type(.{ .type = appointee_type }); + const pointer_load = module.create_load(.{ .type = appointee_type, .value = variable.value.llvm }); + const resulting_pointer = if (start_index.bb.constant_integer.value == 0) pointer_load else module.llvm.builder.create_gep(.{ + .type = variable.value.type.llvm.handle, + .aggregate = pointer_load, + .indices = &.{start_index.llvm}, + }); + const resulting_length = if (start_index.bb.constant_integer.value == 0) end_index.llvm else @trap(); + const slice_poison = slice_type.llvm.handle.get_poison(); + const pointer_insert = module.llvm.builder.create_insert_value(slice_poison, resulting_pointer, 0); + const length_insert = module.llvm.builder.create_insert_value(pointer_insert, resulting_length, 1); + const slice_value = length_insert; + + const result = module.values.add(); + result.* = .{ + .llvm = slice_value, + .type = slice_type, + .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, + }; + return result; + }, + else => @trap(), + } + @trap(); + } else if (end_index.bb == .constant_integer) { + @trap(); + } else { + @trap(); + } } }, } @@ -4619,6 +5621,7 @@ fn is_space(ch: u8) bool { } const StatementStartKeyword = enum { + @"_", @"return", @"if", // TODO: make `unreachable` a statement start keyword? @@ -5567,21 +6570,19 @@ pub const Abi = struct { }; }; -const ConvertOptions = struct { +pub const Options = struct { content: []const u8, path: [:0]const u8, executable: [:0]const u8, - build_mode: BuildMode, name: []const u8, - has_debug_info: bool, objects: []const [:0]const u8, target: Target, + build_mode: BuildMode, + has_debug_info: bool, + silent: bool, }; -pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { - const build_dir = "bb-cache"; - os.make_directory(build_dir); - +pub noinline fn convert(arena: *Arena, options: Options) void { var converter = Converter{ .content = options.content, .offset = 0, @@ -5614,7 +6615,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { while (converter.offset < converter.content.len) { const global_keyword_string = converter.parse_identifier(); - const global_keyword = string_to_enum(GlobalKeyword, global_keyword_string) orelse converter.report_error(); + const global_keyword = lib.string.to_enum(GlobalKeyword, global_keyword_string) orelse converter.report_error(); switch (global_keyword) { .@"export" => is_export = true, .@"extern" => is_extern = true, @@ -5656,7 +6657,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { const global_string = converter.parse_identifier(); converter.skip_space(); - if (string_to_enum(GlobalKind, global_string)) |global_kind| { + if (lib.string.to_enum(GlobalKind, global_string)) |global_kind| { global_keyword = true; switch (global_kind) { .@"fn" => { @@ -5668,7 +6669,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { while (converter.offset < converter.content.len) { const function_identifier = converter.parse_identifier(); - const function_keyword = string_to_enum(FunctionKeyword, function_identifier) orelse converter.report_error(); + const function_keyword = lib.string.to_enum(FunctionKeyword, function_identifier) orelse converter.report_error(); converter.skip_space(); @@ -5680,7 +6681,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { const calling_convention_string = converter.parse_identifier(); - calling_convention = string_to_enum(CallingConvention, calling_convention_string) orelse converter.report_error(); + calling_convention = lib.string.to_enum(CallingConvention, calling_convention_string) orelse converter.report_error(); converter.skip_space(); @@ -6258,7 +7259,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { var field_buffer: [256]Field = undefined; var llvm_field_type_buffer: [field_buffer.len]*llvm.Type = undefined; var llvm_debug_member_type_buffer: [field_buffer.len]*llvm.DI.Type.Derived = undefined; - var field_count: usize = 0; + var field_count: u64 = 0; var byte_offset: u64 = 0; var byte_alignment: u32 = 1; var bit_alignment: u32 = 1; @@ -6362,7 +7363,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { var field_buffer: [128]Field = undefined; var field_line_buffer: [128]u32 = undefined; - var field_count: usize = 0; + var field_count: u64 = 0; var field_bit_offset: u64 = 0; @@ -6550,7 +7551,9 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { } if (!global_keyword) { - const value = converter.parse_value(module, global_type, .value); + const value = converter.parse_value(module, .{ + .type = global_type, + }); const expected_type = global_type orelse value.type; converter.skip_space(); @@ -6604,7 +7607,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { os.abort(); } - if (!lib.is_test) { + if (!module.silent) { const module_string = module.llvm.handle.to_string(); lib.print_string_stderr(module_string); } diff --git a/src/lib.zig b/src/lib.zig index 4016595..576cfea 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1,7 +1,12 @@ const builtin = @import("builtin"); pub const is_test = builtin.is_test; pub const optimization_mode = builtin.mode; -const VariableArguments = @import("std").builtin.VaList; +pub const VariableArguments = extern struct { + gp_offset: c_uint, + fp_offset: c_uint, + overflow_arg_area: *anyopaque, + reg_save_area: *anyopaque, +}; extern "c" fn IsDebuggerPresent() bool; extern "c" fn __errno_location() *c_int; @@ -14,6 +19,9 @@ const CSlice = extern struct { length: usize, }; +extern fn dump_stack_trace(return_address: usize) void; +extern fn enable_signal_handlers() void; + pub const KB = 1024; pub const MB = 1024 * 1024; pub const GB = 1024 * 1024 * 1024; @@ -515,9 +523,7 @@ pub const os = struct { if (os.is_being_debugged()) { @trap(); } else { - if (is_test) { - @import("std").debug.dumpCurrentStackTrace(@returnAddress()); - } + dump_stack_trace(@returnAddress()); libc.exit(1); } @@ -566,6 +572,14 @@ pub const libc = struct { }; pub const string = struct { + pub fn to_enum(comptime E: type, str: []const u8) ?E { + inline for (@typeInfo(E).@"enum".fields) |e| { + if (equal(e.name, str)) { + return @field(E, e.name); + } + } else return null; + } + pub fn equal(a: []const u8, b: []const u8) bool { var result = a.len == b.len; if (result) { @@ -2684,143 +2698,147 @@ pub fn print_string_stderr(str: []const u8) void { os.get_stderr().write(str); } -pub const panic_struct = switch (is_test) { - true => @import("std").debug.FullPanic(@import("std").debug.defaultPanic), - false => struct { - const abort = os.abort; - pub fn call(_: []const u8, _: ?usize) noreturn { - @branchHint(.cold); - abort(); - } +pub const panic_struct = struct { + const abort = os.abort; + pub fn call(_: []const u8, _: ?usize) noreturn { + @branchHint(.cold); + abort(); + } - pub fn sentinelMismatch(_: anytype, _: anytype) noreturn { - @branchHint(.cold); - abort(); - } + pub fn sentinelMismatch(_: anytype, _: anytype) noreturn { + @branchHint(.cold); + abort(); + } - pub fn unwrapError(_: anyerror) noreturn { - @branchHint(.cold); - abort(); - } + pub fn unwrapError(_: anyerror) noreturn { + @branchHint(.cold); + abort(); + } - pub fn outOfBounds(_: usize, _: usize) noreturn { - @branchHint(.cold); - abort(); - } + pub fn outOfBounds(_: usize, _: usize) noreturn { + @branchHint(.cold); + abort(); + } - pub fn startGreaterThanEnd(_: usize, _: usize) noreturn { - @branchHint(.cold); - abort(); - } + pub fn startGreaterThanEnd(_: usize, _: usize) noreturn { + @branchHint(.cold); + abort(); + } - pub fn inactiveUnionField(_: anytype, _: anytype) noreturn { - @branchHint(.cold); - abort(); - } + pub fn inactiveUnionField(_: anytype, _: anytype) noreturn { + @branchHint(.cold); + abort(); + } - pub fn reachedUnreachable() noreturn { - @branchHint(.cold); - abort(); - } + pub fn reachedUnreachable() noreturn { + @branchHint(.cold); + abort(); + } - pub fn unwrapNull() noreturn { - @branchHint(.cold); - abort(); - } + pub fn unwrapNull() noreturn { + @branchHint(.cold); + abort(); + } - pub fn castToNull() noreturn { - @branchHint(.cold); - abort(); - } + pub fn castToNull() noreturn { + @branchHint(.cold); + abort(); + } - pub fn incorrectAlignment() noreturn { - @branchHint(.cold); - abort(); - } + pub fn incorrectAlignment() noreturn { + @branchHint(.cold); + abort(); + } - pub fn invalidErrorCode() noreturn { - @branchHint(.cold); - abort(); - } + pub fn invalidErrorCode() noreturn { + @branchHint(.cold); + abort(); + } - pub fn castTruncatedData() noreturn { - @branchHint(.cold); - abort(); - } + pub fn castTruncatedData() noreturn { + @branchHint(.cold); + abort(); + } - pub fn negativeToUnsigned() noreturn { - @branchHint(.cold); - abort(); - } + pub fn negativeToUnsigned() noreturn { + @branchHint(.cold); + abort(); + } - pub fn integerOverflow() noreturn { - @branchHint(.cold); - abort(); - } + pub fn integerOverflow() noreturn { + @branchHint(.cold); + abort(); + } - pub fn shlOverflow() noreturn { - @branchHint(.cold); - abort(); - } + pub fn shlOverflow() noreturn { + @branchHint(.cold); + abort(); + } - pub fn shrOverflow() noreturn { - @branchHint(.cold); - abort(); - } + pub fn shrOverflow() noreturn { + @branchHint(.cold); + abort(); + } - pub fn divideByZero() noreturn { - @branchHint(.cold); - abort(); - } + pub fn divideByZero() noreturn { + @branchHint(.cold); + abort(); + } - pub fn exactDivisionRemainder() noreturn { - @branchHint(.cold); - abort(); - } + pub fn exactDivisionRemainder() noreturn { + @branchHint(.cold); + abort(); + } - pub fn integerPartOutOfBounds() noreturn { - @branchHint(.cold); - abort(); - } + pub fn integerPartOutOfBounds() noreturn { + @branchHint(.cold); + abort(); + } - pub fn corruptSwitch() noreturn { - @branchHint(.cold); - abort(); - } + pub fn corruptSwitch() noreturn { + @branchHint(.cold); + abort(); + } - pub fn shiftRhsTooBig() noreturn { - @branchHint(.cold); - abort(); - } + pub fn shiftRhsTooBig() noreturn { + @branchHint(.cold); + abort(); + } - pub fn invalidEnumValue() noreturn { - @branchHint(.cold); - abort(); - } + pub fn invalidEnumValue() noreturn { + @branchHint(.cold); + abort(); + } - pub fn forLenMismatch() noreturn { - @branchHint(.cold); - abort(); - } + pub fn forLenMismatch() noreturn { + @branchHint(.cold); + abort(); + } - pub fn memcpyLenMismatch() noreturn { - @branchHint(.cold); - abort(); - } + pub fn memcpyLenMismatch() noreturn { + @branchHint(.cold); + abort(); + } - pub fn memcpyAlias() noreturn { - @branchHint(.cold); - abort(); - } + pub fn memcpyAlias() noreturn { + @branchHint(.cold); + abort(); + } - pub fn noreturnReturned() noreturn { - @branchHint(.cold); - abort(); - } + pub fn noreturnReturned() noreturn { + @branchHint(.cold); + abort(); + } - pub fn sliceCastLenRemainder(_: usize) noreturn { - @branchHint(.cold); - abort(); - } - }, + pub fn sliceCastLenRemainder(_: usize) noreturn { + @branchHint(.cold); + abort(); + } }; + +pub export fn main(argc: c_int, argv: [*:null]const ?[*:0]const u8) callconv(.C) c_int { + enable_signal_handlers(); + const arguments: []const [*:0]const u8 = @ptrCast(argv[0..@intCast(argc)]); + @import("root").entry_point(arguments); + return 0; +} diff --git a/src/main.zig b/src/main.zig index 7c8c362..b9d4105 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,18 +2,13 @@ const lib = @import("lib.zig"); const configuration = @import("configuration"); const os = lib.os; const llvm = @import("LLVM.zig"); -const converter = @import("converter.zig"); const Arena = lib.Arena; -pub const panic = lib.panic_struct; +const converter = @import("converter.zig"); +const BuildMode = converter.BuildMode; -comptime { - if (!lib.is_test) { - @export(&main, .{ - .name = "main", - }); - } -} +pub const panic = lib.panic_struct; +pub const std_options = lib.std_options; test { _ = lib; @@ -21,46 +16,134 @@ test { _ = converter; } -pub fn main(argc: c_int, argv: [*:null]const ?[*:0]const u8) callconv(.C) c_int { - if (argc != 2) { - lib.print_string("Failed to match argument count\n"); - return 1; - } - const relative_file_path_pointer = argv[1] orelse return 1; - const relative_file_path = lib.cstring.to_slice(relative_file_path_pointer); +fn fail() noreturn { + lib.libc.exit(1); +} + +pub const main = lib.main; + +const Command = enum { + @"test", + compile, +}; + +const Compile = struct { + relative_file_path: [:0]const u8, + build_mode: BuildMode, + has_debug_info: bool, + silent: bool, +}; + +fn compile_file(arena: *Arena, compile: Compile) converter.Options { + const checkpoint = arena.position; + defer arena.restore(checkpoint); + + const relative_file_path = compile.relative_file_path; if (relative_file_path.len < 5) { - return 1; + fail(); } - const extension_start = lib.string.last_character(relative_file_path, '.') orelse return 1; + const extension_start = lib.string.last_character(relative_file_path, '.') orelse fail(); if (!lib.string.equal(relative_file_path[extension_start..], ".bbb")) { - return 1; + fail(); } + const separator_index = lib.string.last_character(relative_file_path, '/') orelse 0; const base_start = separator_index + @intFromBool(separator_index != 0 or relative_file_path[separator_index] == '/'); const base_name = relative_file_path[base_start..extension_start]; - lib.GlobalState.initialize(); + const is_compiler = lib.string.equal(relative_file_path, "src/compiler.bbb"); + const output_path_dir = arena.join_string(&.{ + base_cache_dir, + if (is_compiler) "/compiler/" else "/", + @tagName(compile.build_mode), + "_", + if (compile.has_debug_info) "di" else "nodi", + }); - const arena = lib.global.arena; + os.make_directory(base_cache_dir); + if (is_compiler) { + os.make_directory(base_cache_dir ++ "/compiler"); + } + + os.make_directory(output_path_dir); + + const output_path_base = arena.join_string(&.{ + output_path_dir, + "/", + base_name, + }); - const build_dir = "bb-cache"; - const output_path_base = arena.join_string(&.{ build_dir, "/", base_name }); const output_object_path = arena.join_string(&.{ output_path_base, ".o" }); const output_executable_path = output_path_base; - const c_abi_object_path = arena.duplicate_string(configuration.c_abi_object_path); const file_content = lib.file.read(arena, relative_file_path); const file_path = os.absolute_path(arena, relative_file_path); - converter.convert(arena, .{ + const c_abi_object_path = arena.duplicate_string(configuration.c_abi_object_path); + + const convert_options = converter.Options{ .executable = output_executable_path, .objects = if (lib.string.equal(base_name, "c_abi")) &.{ output_object_path, c_abi_object_path } else &.{output_object_path}, .name = base_name, - .build_mode = .debug_none, + .build_mode = compile.build_mode, .content = file_content, .path = file_path, - .has_debug_info = true, + .has_debug_info = compile.has_debug_info, .target = converter.Target.get_native(), - }); - return 0; + .silent = compile.silent, + }; + + converter.convert(arena, convert_options); + + return convert_options; +} + +const base_cache_dir = "bb-cache"; + +pub fn entry_point(arguments: []const [*:0]const u8) void { + lib.GlobalState.initialize(); + const arena = lib.global.arena; + + if (arguments.len < 2) { + lib.print_string("error: Not enough arguments\n"); + fail(); + } + + const command = lib.string.to_enum(Command, lib.cstring.to_slice(arguments[1])) orelse fail(); + + switch (command) { + .compile => { + const relative_file_path = lib.cstring.to_slice(arguments[2]); + _ = compile_file(arena, .{ + .relative_file_path = relative_file_path, + .build_mode = .debug_none, + .has_debug_info = true, + .silent = false, + }); + }, + .@"test" => { + if (arguments.len != 2) { + fail(); + } + + inline for (@typeInfo(converter.BuildMode).@"enum".fields) |f| { + const build_mode = @field(converter.BuildMode, f.name); + inline for ([2]bool{ true, false }) |has_debug_info| { + const names = [_][]const u8{ "minimal", "constant_add", "minimal_stack" }; + for (names) |name| { + const position = arena.position; + defer arena.restore(position); + + const relative_file_path = arena.join_string(&.{ "tests/", name, ".bbb" }); + _ = compile_file(arena, .{ + .relative_file_path = relative_file_path, + .build_mode = build_mode, + .has_debug_info = has_debug_info, + .silent = true, + }); + } + } + } + }, + } } diff --git a/src/stack_trace.zig b/src/stack_trace.zig new file mode 100644 index 0000000..2d7f070 --- /dev/null +++ b/src/stack_trace.zig @@ -0,0 +1,20 @@ +const std = @import("std"); +export fn enable_signal_handlers() void { + std.debug.attachSegfaultHandler(); +} + +export fn dump_stack_trace(return_address: usize) void { + const stderr = std.io.getStdErr().writer(); + if (@import("builtin").strip_debug_info) { + stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; + return; + } + const debug_info = std.debug.getSelfDebugInfo() catch |err| { + stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; + return; + }; + std.debug.writeCurrentStackTrace(stderr, debug_info, std.io.tty.detectConfig(std.io.getStdErr()), return_address) catch |err| { + stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; + return; + }; +} diff --git a/tests/basic_slice.bbb b/tests/basic_slice.bbb index 60ddb66..da318c3 100644 --- a/tests/basic_slice.bbb +++ b/tests/basic_slice.bbb @@ -17,6 +17,6 @@ slice_receiver = fn (slice: []u8) void [export] main = fn [cc(c)] () s32 { >a: [_]u8 = [0, 1, 2]; - slice_receiver(#slice(a)); + slice_receiver(&a); return 0; } diff --git a/tests/c_string_to_slice.bbb b/tests/c_string_to_slice.bbb index 6e3cbce..e9d78d2 100644 --- a/tests/c_string_to_slice.bbb +++ b/tests/c_string_to_slice.bbb @@ -20,7 +20,7 @@ c_string_length = fn (c_string: &u8) u64 c_string_slice_build = fn (c_string: &u8, length: u64) []u8 { - return #slice(c_string, length); + return c_string[0..length]; } [export] main = fn [cc(c)] (argument_count: u32, argument_pointer: &&u8) s32 diff --git a/tests/comparison.bbb b/tests/comparison.bbb new file mode 100644 index 0000000..4d558c3 --- /dev/null +++ b/tests/comparison.bbb @@ -0,0 +1,14 @@ +trivial_comparison = fn (a: u32, b: u32) u1 +{ + return a + 1 == b + 1; +} + +[export] main = fn [cc(c)] (argument_count: u32) s32 +{ + >result = trivial_comparison(argument_count, argument_count); + if (!result) + { + #trap(); + } + return 0; +} diff --git a/tests/constant_add.bbb b/tests/constant_add.bbb index d7f216a..5366bca 100644 --- a/tests/constant_add.bbb +++ b/tests/constant_add.bbb @@ -2,4 +2,3 @@ { return -1 + 1; } - diff --git a/tests/slice.bbb b/tests/slice.bbb new file mode 100644 index 0000000..67ced61 --- /dev/null +++ b/tests/slice.bbb @@ -0,0 +1,34 @@ +c_string_length = fn (c_string: &u8) u64 +{ + >it = c_string; + + while (it.&) + { + it = it + 1; + } + + return #int_from_pointer(it) - #int_from_pointer(c_string); +} + +[export] main = fn (argument_count: u32, argument_pointer: &&u8) s32 +{ + if (argument_count == 0) + { + return 1; + } + >arg_ptr = argument_pointer[0]; + >a1 = arg_ptr[0..c_string_length(arg_ptr)]; + >a2 = a1[1..]; + + if (a1.pointer != a2.pointer - 1) + { + return 1; + } + + if (a1.length != a2.length + 1) + { + return 1; + } + + return 0; +}