Integrate optimization pipeline

This commit is contained in:
David Gonzalez Martin 2024-04-14 00:05:09 -06:00
parent a9977e40f8
commit 801582b56d
8 changed files with 180 additions and 151 deletions

View File

@ -49,6 +49,17 @@ const SliceField = enum {
const length_field_name = @tagName(SliceField.length); const length_field_name = @tagName(SliceField.length);
const Optimization = enum{
none,
debug_prefer_fast,
debug_prefer_size,
lightly_optimize_for_speed,
optimize_for_speed,
optimize_for_size,
aggressively_optimize_for_speed,
aggressively_optimize_for_size,
};
pub fn createContext(allocator: Allocator, my_allocator: *MyAllocator) !*const Context { pub fn createContext(allocator: Allocator, my_allocator: *MyAllocator) !*const Context {
const context: *Context = try allocator.create(Context); const context: *Context = try allocator.create(Context);
@ -96,6 +107,7 @@ pub fn compileBuildExecutable(context: *const Context, arguments: []const []cons
.object_path = "nat/build.o", .object_path = "nat/build.o",
.link_libc = @import("builtin").os.tag == .macos, .link_libc = @import("builtin").os.tag == .macos,
.link_libcpp = false, .link_libcpp = false,
.optimization = .none,
.generate_debug_information = true, .generate_debug_information = true,
.name = "build", .name = "build",
.is_test = false, .is_test = false,
@ -2790,6 +2802,7 @@ pub fn buildExecutable(context: *const Context, arguments: []const []const u8, o
var link_libc = false; var link_libc = false;
var maybe_executable_name: ?[]const u8 = null; var maybe_executable_name: ?[]const u8 = null;
var c_source_files = UnpinnedArray([]const u8){}; var c_source_files = UnpinnedArray([]const u8){};
var optimization = Optimization.none;
const generate_debug_information = true; const generate_debug_information = true;
if (arguments.len == 0) return error.InvalidInput; if (arguments.len == 0) return error.InvalidInput;
@ -2907,6 +2920,15 @@ pub fn buildExecutable(context: *const Context, arguments: []const []const u8, o
} else { } else {
reportUnterminatedArgumentError(current_argument); reportUnterminatedArgumentError(current_argument);
} }
} else if (byte_equal(current_argument, "-optimize")) {
if (i + 1 != arguments.len) {
i += 1;
const optimize_string = arguments[i];
optimization = data_structures.enumFromString(Optimization, optimize_string) orelse unreachable;
} else {
reportUnterminatedArgumentError(current_argument);
}
} else { } else {
@panic(current_argument); @panic(current_argument);
// std.debug.panic("Unrecognized argument: {s}", .{current_argument}); // std.debug.panic("Unrecognized argument: {s}", .{current_argument});
@ -2948,6 +2970,7 @@ pub fn buildExecutable(context: *const Context, arguments: []const []const u8, o
.arch = arch, .arch = arch,
.os = os, .os = os,
.abi = abi, .abi = abi,
.optimization = optimization,
.link_libc = switch (os) { .link_libc = switch (os) {
.linux => link_libc, .linux => link_libc,
.macos => true, .macos => true,
@ -16364,6 +16387,7 @@ pub const Descriptor = struct {
arch: Arch, arch: Arch,
os: Os, os: Os,
abi: Abi, abi: Abi,
optimization: Optimization,
only_parse: bool, only_parse: bool,
link_libc: bool, link_libc: bool,
link_libcpp: bool, link_libcpp: bool,
@ -16691,3 +16715,5 @@ pub fn write(kind: LogKind, string: []const u8) !void {
try std.io.getStdOut().writeAll(string); try std.io.getStdOut().writeAll(string);
} }
} }

View File

@ -123,6 +123,7 @@ pub const LLVM = struct {
const createDebugInfoBuilder = bindings.NativityLLVMModuleCreateDebugInfoBuilder; const createDebugInfoBuilder = bindings.NativityLLVMModuleCreateDebugInfoBuilder;
const setTargetMachineDataLayout = bindings.NativityLLVMModuleSetTargetMachineDataLayout; const setTargetMachineDataLayout = bindings.NativityLLVMModuleSetTargetMachineDataLayout;
const setTargetTriple = bindings.NativityLLVMModuleSetTargetTriple; const setTargetTriple = bindings.NativityLLVMModuleSetTargetTriple;
const runOptimizationPipeline = bindings.NativityLLVMRunOptimizationPipeline;
const addPassesToEmitFile = bindings.NativityLLVMModuleAddPassesToEmitFile; const addPassesToEmitFile = bindings.NativityLLVMModuleAddPassesToEmitFile;
}; };
@ -498,6 +499,11 @@ pub const LLVM = struct {
aggressive = 3, aggressive = 3,
}; };
pub const OptimizationLevel = extern struct{
speed_level: c_uint,
size_level: c_uint,
};
pub const FramePointerKind = enum(c_uint) { pub const FramePointerKind = enum(c_uint) {
none = 0, none = 0,
non_leaf = 1, non_leaf = 1,
@ -2341,18 +2347,23 @@ pub const LLVM = struct {
function.setSubprogram(subprogram); function.setSubprogram(subprogram);
switch (declaration.initial_value) { switch (declaration.initial_value) {
.function_declaration => {
try llvm.llvm_external_functions.put_no_clobber(context.my_allocator, declaration, function);
},
.function_definition => |function_definition_index| { .function_definition => |function_definition_index| {
const function_definition = unit.function_definitions.get(function_definition_index); const function_definition = unit.function_definitions.get(function_definition_index);
const scope = subprogram.toLocalScope().toScope(); const scope = subprogram.toLocalScope().toScope();
try llvm.scope_map.put_no_clobber(context.my_allocator, &function_definition.scope.scope, scope); try llvm.scope_map.put_no_clobber(context.my_allocator, &function_definition.scope.scope, scope);
}, },
.function_declaration => {},
else => |t| @panic(@tagName(t)), else => |t| @panic(@tagName(t)),
} }
} }
switch (declaration.initial_value) {
.function_declaration => try llvm.llvm_external_functions.put_no_clobber(context.my_allocator, declaration, function),
.function_definition => {},
else => |t| @panic(@tagName(t)),
}
} }
}; };
@ -2392,7 +2403,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
.context = llvm_context, .context = llvm_context,
.module = module, .module = module,
.builder = builder, .builder = builder,
.debug_info_builder = module.createDebugInfoBuilder() orelse return Error.debug_info_builder, .debug_info_builder = if (unit.descriptor.generate_debug_information) module.createDebugInfoBuilder() orelse return Error.debug_info_builder else undefined,
.attributes = .{ .attributes = .{
.naked = llvm_context.getAttributeFromEnum(.Naked, 0), .naked = llvm_context.getAttributeFromEnum(.Naked, 0),
.noreturn = llvm_context.getAttributeFromEnum(.NoReturn, 0), .noreturn = llvm_context.getAttributeFromEnum(.NoReturn, 0),
@ -2401,6 +2412,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
.@"noalias" = llvm_context.getAttributeFromEnum(.NoAlias, 0), .@"noalias" = llvm_context.getAttributeFromEnum(.NoAlias, 0),
}, },
}; };
unit.descriptor.generate_debug_information = false;
if (unit.descriptor.generate_debug_information) { if (unit.descriptor.generate_debug_information) {
const full_path = try std.fs.cwd().realpathAlloc(context.allocator, unit.descriptor.main_package_path); const full_path = try std.fs.cwd().realpathAlloc(context.allocator, unit.descriptor.main_package_path);
@ -2531,14 +2543,17 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
switch (sema_instruction.*) { switch (sema_instruction.*) {
.push_scope => |push_scope| { .push_scope => |push_scope| {
if (unit.descriptor.generate_debug_information) {
const old_scope = try llvm.getScope(unit, context, push_scope.old); const old_scope = try llvm.getScope(unit, context, push_scope.old);
assert(@intFromEnum(push_scope.old.kind) >= @intFromEnum(Compilation.Debug.Scope.Kind.function)); assert(@intFromEnum(push_scope.old.kind) >= @intFromEnum(Compilation.Debug.Scope.Kind.function));
const lexical_block = llvm.debug_info_builder.createLexicalBlock(old_scope, llvm.file, push_scope.new.line + 1, push_scope.new.column + 1) orelse unreachable; const lexical_block = llvm.debug_info_builder.createLexicalBlock(old_scope, llvm.file, push_scope.new.line + 1, push_scope.new.column + 1) orelse unreachable;
try llvm.scope_map.put_no_clobber(context.my_allocator, push_scope.new, lexical_block.toScope()); try llvm.scope_map.put_no_clobber(context.my_allocator, push_scope.new, lexical_block.toScope());
llvm.scope = lexical_block.toScope(); llvm.scope = lexical_block.toScope();
}
}, },
.pop_scope => |pop_scope| { .pop_scope => |pop_scope| {
if (unit.descriptor.generate_debug_information) {
const new = try llvm.getScope(unit, context, pop_scope.new); const new = try llvm.getScope(unit, context, pop_scope.new);
if (pop_scope.new.kind == .function) { if (pop_scope.new.kind == .function) {
assert(new.toSubprogram() orelse unreachable == llvm.function.getSubprogram() orelse unreachable); assert(new.toSubprogram() orelse unreachable == llvm.function.getSubprogram() orelse unreachable);
@ -2550,11 +2565,13 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
} }
const subprogram_scope = try llvm.getScope(unit, context, scope); const subprogram_scope = try llvm.getScope(unit, context, scope);
assert(llvm.function.getSubprogram() orelse unreachable == subprogram_scope.toSubprogram() orelse unreachable); assert(llvm.function.getSubprogram() orelse unreachable == subprogram_scope.toSubprogram() orelse unreachable);
}
}, },
.debug_checkpoint => |debug_checkpoint| { .debug_checkpoint => |debug_checkpoint| {
if (unit.descriptor.generate_debug_information) {
const scope = try llvm.getScope(unit, context, debug_checkpoint.scope); const scope = try llvm.getScope(unit, context, debug_checkpoint.scope);
// assert(scope == llvm.scope);
llvm.builder.setCurrentDebugLocation(llvm.context, debug_checkpoint.line + 1, debug_checkpoint.column + 1, scope, llvm.function); llvm.builder.setCurrentDebugLocation(llvm.context, debug_checkpoint.line + 1, debug_checkpoint.column + 1, scope, llvm.function);
}
}, },
.inline_assembly => |inline_assembly_index| { .inline_assembly => |inline_assembly_index| {
const assembly_block = unit.inline_assembly.get(inline_assembly_index); const assembly_block = unit.inline_assembly.get(inline_assembly_index);
@ -3251,7 +3268,13 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
const jit = false; const jit = false;
const code_model: LLVM.CodeModel = undefined; const code_model: LLVM.CodeModel = undefined;
const is_code_model_present = false; const is_code_model_present = false;
const codegen_optimization_level = LLVM.CodegenOptimizationLevel.none; const codegen_optimization_level: LLVM.CodegenOptimizationLevel = switch (unit.descriptor.optimization) {
.none => .none,
.debug_prefer_fast, .debug_prefer_size => .none,
.lightly_optimize_for_speed => .less,
.optimize_for_speed, .optimize_for_size => .default,
.aggressively_optimize_for_speed, .aggressively_optimize_for_size => .aggressive,
};
const target_machine = target.createTargetMachine(target_triple.ptr, target_triple.len, cpu, cpu.len, features, features.len, LLVM.RelocationModel.static, code_model, is_code_model_present, codegen_optimization_level, jit) orelse unreachable; const target_machine = target.createTargetMachine(target_triple.ptr, target_triple.len, cpu, cpu.len, features, features.len, LLVM.RelocationModel.static, code_model, is_code_model_present, codegen_optimization_level, jit) orelse unreachable;
llvm.module.setTargetMachineDataLayout(target_machine); llvm.module.setTargetMachineDataLayout(target_machine);
llvm.module.setTargetTriple(target_triple.ptr, target_triple.len); llvm.module.setTargetTriple(target_triple.ptr, target_triple.len);
@ -3265,6 +3288,21 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
const object_file_path = slice[0 .. slice.len - 1 :0]; const object_file_path = slice[0 .. slice.len - 1 :0];
break :blk object_file_path; break :blk object_file_path;
}; };
if (unit.descriptor.optimization != .none) {
const optimization_level: LLVM.OptimizationLevel = switch (unit.descriptor.optimization) {
.none => unreachable,
.debug_prefer_fast, .debug_prefer_size => .{ .speed_level = 0, .size_level = 0 }, // -O0
.lightly_optimize_for_speed => .{ .speed_level = 1, .size_level = 0 }, // -O1
.optimize_for_speed => .{ .speed_level = 2, .size_level = 0 }, // -O2
.optimize_for_size => .{ .speed_level = 2, .size_level = 1 }, // -Os
.aggressively_optimize_for_speed => .{ .speed_level = 3, .size_level = 0 }, // -O3
.aggressively_optimize_for_size => .{ .speed_level = 2, .size_level = 2 }, // -Oz
};
llvm.module.runOptimizationPipeline(target_machine, optimization_level);
}
const disable_verify = false; const disable_verify = false;
const result = llvm.module.addPassesToEmitFile(target_machine, object_file_path.ptr, object_file_path.len, LLVM.CodeGenFileType.object, disable_verify); const result = llvm.module.addPassesToEmitFile(target_machine, object_file_path.ptr, object_file_path.len, LLVM.CodeGenFileType.object, disable_verify);
if (!result) { if (!result) {

View File

@ -150,6 +150,7 @@ pub extern fn NativityLLVMGetTarget(target_triple_ptr: [*]const u8, target_tripl
pub extern fn NativityLLVMTargetCreateTargetMachine(target: *LLVM.Target, target_triple_ptr: [*]const u8, target_triple_len: usize, cpu_ptr: [*]const u8, cpu_len: usize, features_ptr: [*]const u8, features_len: usize, relocation_model: LLVM.RelocationModel, maybe_code_model: LLVM.CodeModel, is_code_model_present: bool, optimization_level: LLVM.CodegenOptimizationLevel, jit: bool) ?*LLVM.Target.Machine; pub extern fn NativityLLVMTargetCreateTargetMachine(target: *LLVM.Target, target_triple_ptr: [*]const u8, target_triple_len: usize, cpu_ptr: [*]const u8, cpu_len: usize, features_ptr: [*]const u8, features_len: usize, relocation_model: LLVM.RelocationModel, maybe_code_model: LLVM.CodeModel, is_code_model_present: bool, optimization_level: LLVM.CodegenOptimizationLevel, jit: bool) ?*LLVM.Target.Machine;
pub extern fn NativityLLVMModuleSetTargetMachineDataLayout(module: *LLVM.Module, target_machine: *LLVM.Target.Machine) void; pub extern fn NativityLLVMModuleSetTargetMachineDataLayout(module: *LLVM.Module, target_machine: *LLVM.Target.Machine) void;
pub extern fn NativityLLVMModuleSetTargetTriple(module: *LLVM.Module, target_triple_ptr: [*]const u8, target_triple_len: usize) void; pub extern fn NativityLLVMModuleSetTargetTriple(module: *LLVM.Module, target_triple_ptr: [*]const u8, target_triple_len: usize) void;
pub extern fn NativityLLVMRunOptimizationPipeline(module: *LLVM.Module, target_machine: *LLVM.Target.Machine, optimization_level: LLVM.OptimizationLevel) void;
pub extern fn NativityLLVMModuleAddPassesToEmitFile(module: *LLVM.Module, target_machine: *LLVM.Target.Machine, object_file_path_ptr: [*]const u8, object_file_path_len: usize, codegen_file_type: LLVM.CodeGenFileType, disable_verify: bool) bool; pub extern fn NativityLLVMModuleAddPassesToEmitFile(module: *LLVM.Module, target_machine: *LLVM.Target.Machine, object_file_path_ptr: [*]const u8, object_file_path_len: usize, codegen_file_type: LLVM.CodeGenFileType, disable_verify: bool) bool;
pub extern fn NativityLLVMTypeAssertEqual(a: *LLVM.Type, b: *LLVM.Type) void; pub extern fn NativityLLVMTypeAssertEqual(a: *LLVM.Type, b: *LLVM.Type) void;

View File

@ -23,7 +23,7 @@ pub fn build(b: *std.Build) !void {
const self_hosted_ci = b.option(bool, "self_hosted_ci", "This option enables the self-hosted CI behavior") orelse false; const self_hosted_ci = b.option(bool, "self_hosted_ci", "This option enables the self-hosted CI behavior") orelse false;
const third_party_ci = b.option(bool, "third_party_ci", "This option enables the third-party CI behavior") orelse false; const third_party_ci = b.option(bool, "third_party_ci", "This option enables the third-party CI behavior") orelse false;
const is_ci = self_hosted_ci or third_party_ci; const is_ci = self_hosted_ci or third_party_ci;
const print_stack_trace = b.option(bool, "print_stack_trace", "This option enables printing stack traces inside the compiler") orelse is_ci; //or os == .macos; const print_stack_trace = b.option(bool, "print_stack_trace", "This option enables printing stack traces inside the compiler") orelse is_ci or os == .macos;
const native_target = b.resolveTargetQuery(.{}); const native_target = b.resolveTargetQuery(.{});
const optimization = b.standardOptimizeOption(.{}); const optimization = b.standardOptimizeOption(.{});
const use_debug = b.option(bool, "use_debug", "This option enables the LLVM debug build in the development PC") orelse false; const use_debug = b.option(bool, "use_debug", "This option enables the LLVM debug build in the development PC") orelse false;

View File

@ -36,6 +36,7 @@ fn runStandalone(allocator: Allocator, args: struct {
group_name: []const u8, group_name: []const u8,
self_hosted: bool, self_hosted: bool,
is_test: bool, is_test: bool,
compiler_path: []const u8,
}) !void { }) !void {
const test_names = try collectDirectoryDirEntries(allocator, args.directory_path); const test_names = try collectDirectoryDirEntries(allocator, args.directory_path);
@ -55,7 +56,7 @@ fn runStandalone(allocator: Allocator, args: struct {
const compile_run = try std.ChildProcess.run(.{ const compile_run = try std.ChildProcess.run(.{
.allocator = allocator, .allocator = allocator,
// TODO: delete -main_source_file? // TODO: delete -main_source_file?
.argv = &.{ if (args.self_hosted) self_hosted_relative_path else bootstrap_relative_path, if (args.is_test) "test" else "exe", "-main_source_file", source_file_path }, .argv = &.{ args.compiler_path, if (args.is_test) "test" else "exe", "-main_source_file", source_file_path },
.max_output_bytes = std.math.maxInt(u64), .max_output_bytes = std.math.maxInt(u64),
}); });
ran_compilation_count += 1; ran_compilation_count += 1;
@ -120,100 +121,16 @@ fn runStandalone(allocator: Allocator, args: struct {
} }
} }
fn runStandaloneTests(allocator: Allocator, args: struct {
self_hosted: bool,
}) !void {
const standalone_test_dir_path = "test/standalone";
const standalone_test_names = try collectDirectoryDirEntries(allocator, standalone_test_dir_path);
const total_compilation_count = standalone_test_names.len;
var ran_compilation_count: usize = 0;
var failed_compilation_count: usize = 0;
var ran_test_count: usize = 0;
var failed_test_count: usize = 0;
const total_test_count = standalone_test_names.len;
for (standalone_test_names) |standalone_test_name| {
std.debug.print("{s}... ", .{standalone_test_name});
const source_file_path = try std.mem.concat(allocator, u8, &.{ standalone_test_dir_path, "/", standalone_test_name, "/main.nat" });
const compile_run = try std.ChildProcess.run(.{
.allocator = allocator,
// TODO: delete -main_source_file?
.argv = &.{ if (args.self_hosted) self_hosted_relative_path else bootstrap_relative_path, "exe", "-main_source_file", source_file_path },
.max_output_bytes = std.math.maxInt(u64),
});
ran_compilation_count += 1;
const compilation_result: TestError!bool = switch (compile_run.term) {
.Exited => |exit_code| if (exit_code == 0) true else error.abnormal_exit_code,
.Signal => error.signaled,
.Stopped => error.stopped,
.Unknown => error.unknown,
};
const compilation_success = compilation_result catch b: {
failed_compilation_count += 1;
break :b false;
};
std.debug.print("[COMPILATION {s}] ", .{if (compilation_success) "\x1b[32mOK\x1b[0m" else "\x1b[31mFAILED\x1b[0m"});
if (compile_run.stdout.len > 0) {
std.debug.print("STDOUT:\n\n{s}\n\n", .{compile_run.stdout});
}
if (compile_run.stderr.len > 0) {
std.debug.print("STDERR:\n\n{s}\n\n", .{compile_run.stderr});
}
if (compilation_success and !args.self_hosted) {
const test_path = try std.mem.concat(allocator, u8, &.{ "nat/", standalone_test_name });
const test_run = try std.ChildProcess.run(.{
.allocator = allocator,
// TODO: delete -main_source_file?
.argv = &.{test_path},
.max_output_bytes = std.math.maxInt(u64),
});
ran_test_count += 1;
const test_result: TestError!bool = switch (test_run.term) {
.Exited => |exit_code| if (exit_code == 0) true else error.abnormal_exit_code,
.Signal => error.signaled,
.Stopped => error.stopped,
.Unknown => error.unknown,
};
const test_success = test_result catch b: {
failed_test_count += 1;
break :b false;
};
std.debug.print("[TEST {s}]\n", .{if (test_success) "\x1b[32mOK\x1b[0m" else "\x1b[31mFAILED\x1b[0m"});
if (test_run.stdout.len > 0) {
std.debug.print("STDOUT:\n\n{s}\n\n", .{test_run.stdout});
}
if (test_run.stderr.len > 0) {
std.debug.print("STDERR:\n\n{s}\n\n", .{test_run.stderr});
}
} else {
std.debug.print("\n", .{});
}
}
std.debug.print("\nTOTAL COMPILATIONS: {}. FAILED: {}\n", .{ total_compilation_count, failed_compilation_count });
std.debug.print("TOTAL TESTS: {}. RAN: {}. FAILED: {}\n", .{ total_test_count, ran_test_count, failed_test_count });
if (failed_compilation_count > 0 or failed_test_count > 0) {
return error.fail;
}
}
fn runBuildTests(allocator: Allocator, args: struct { fn runBuildTests(allocator: Allocator, args: struct {
self_hosted: bool, self_hosted: bool,
compiler_path: []const u8,
}) !void { }) !void {
std.debug.print("\n[BUILD TESTS]\n\n", .{}); std.debug.print("\n[BUILD TESTS]\n\n", .{});
const previous_cwd = try std.fs.cwd().realpathAlloc(allocator, "."); const previous_cwd = try std.fs.cwd().realpathAlloc(allocator, ".");
const test_dir_path = "test/build"; const test_dir_path = "test/build";
const test_names = try collectDirectoryDirEntries(allocator, test_dir_path); const test_names = try collectDirectoryDirEntries(allocator, test_dir_path);
const test_dir_realpath = try std.fs.cwd().realpathAlloc(allocator, test_dir_path); const test_dir_realpath = try std.fs.cwd().realpathAlloc(allocator, test_dir_path);
const compiler_realpath = try std.fs.cwd().realpathAlloc(allocator, if (args.self_hosted) self_hosted_relative_path else bootstrap_relative_path); const compiler_realpath = try std.fs.cwd().realpathAlloc(allocator, args.compiler_path);
try std.posix.chdir(test_dir_realpath); try std.posix.chdir(test_dir_realpath);
const total_compilation_count = test_names.len; const total_compilation_count = test_names.len;
@ -303,13 +220,14 @@ fn runBuildTests(allocator: Allocator, args: struct {
fn runStdTests(allocator: Allocator, args: struct { fn runStdTests(allocator: Allocator, args: struct {
self_hosted: bool, self_hosted: bool,
compiler_path: []const u8,
}) !void { }) !void {
var errors = false; var errors = false;
std.debug.print("std... ", .{}); std.debug.print("std... ", .{});
const result = try std.ChildProcess.run(.{ const result = try std.ChildProcess.run(.{
.allocator = allocator, .allocator = allocator,
.argv = &.{ if (args.self_hosted) self_hosted_relative_path else bootstrap_relative_path, "test", "-main_source_file", "lib/std/std.nat", "-name", "std" }, .argv = &.{ args.compiler_path, "test", "-main_source_file", "lib/std/std.nat", "-name", "std" },
.max_output_bytes = std.math.maxInt(u64), .max_output_bytes = std.math.maxInt(u64),
}); });
const compilation_result: TestError!bool = switch (result.term) { const compilation_result: TestError!bool = switch (result.term) {
@ -362,12 +280,16 @@ fn runStdTests(allocator: Allocator, args: struct {
if (errors) return error.fail; if (errors) return error.fail;
} }
fn runCmakeTests(allocator: Allocator, dir_path: []const u8) !void { fn runCmakeTests(allocator: Allocator, args: struct {
dir_path: []const u8,
compiler_path: []const u8,
}) !void {
var errors = false; var errors = false;
const original_dir = try std.fs.cwd().realpathAlloc(allocator, "."); const original_dir = try std.fs.cwd().realpathAlloc(allocator, ".");
const cc_dir = try std.fs.cwd().openDir(dir_path, .{ const cc_dir = try std.fs.cwd().openDir(args.dir_path, .{
.iterate = true, .iterate = true,
}); });
const compiler_realpath = try std.fs.cwd().realpathAlloc(allocator, args.compiler_path);
const cc_dir_path = try cc_dir.realpathAlloc(allocator, "."); const cc_dir_path = try cc_dir.realpathAlloc(allocator, ".");
try std.posix.chdir(cc_dir_path); try std.posix.chdir(cc_dir_path);
@ -392,9 +314,9 @@ fn runCmakeTests(allocator: Allocator, dir_path: []const u8) !void {
// "--debug-output", // "--debug-output",
// "-G", "Unix Makefiles", // "-G", "Unix Makefiles",
// "-DCMAKE_VERBOSE_MAKEFILE=On", // "-DCMAKE_VERBOSE_MAKEFILE=On",
try std.mem.concat(allocator, u8, &.{ "-DCMAKE_C_COMPILER=", "nat;cc" }), try std.mem.concat(allocator, u8, &.{ "-DCMAKE_C_COMPILER=", compiler_realpath, ";cc" }),
try std.mem.concat(allocator, u8, &.{ "-DCMAKE_CXX_COMPILER=", "nat;c++" }), try std.mem.concat(allocator, u8, &.{ "-DCMAKE_CXX_COMPILER=", compiler_realpath, ";c++" }),
try std.mem.concat(allocator, u8, &.{ "-DCMAKE_ASM_COMPILER=", "nat;cc" }), try std.mem.concat(allocator, u8, &.{ "-DCMAKE_ASM_COMPILER=", compiler_realpath, ";cc" }),
}, },
.max_output_bytes = std.math.maxInt(u64), .max_output_bytes = std.math.maxInt(u64),
}); });
@ -500,11 +422,13 @@ const self_hosted_relative_path = "nat/" ++ self_hosted_exe_name;
fn compile_self_hosted(allocator: Allocator, args: struct { fn compile_self_hosted(allocator: Allocator, args: struct {
is_test: bool, is_test: bool,
}) !void { optimization: Optimization,
}) ![]const u8 {
const name = try std.mem.concat(allocator, u8, &.{self_hosted_exe_name, "_", @tagName(args.optimization)});
const compile_run = try std.ChildProcess.run(.{ const compile_run = try std.ChildProcess.run(.{
.allocator = allocator, .allocator = allocator,
// TODO: delete -main_source_file? // TODO: delete -main_source_file?
.argv = &.{ bootstrap_relative_path, if (args.is_test) "test" else "exe", "-main_source_file", "src/main.nat", "-name", self_hosted_exe_name }, .argv = &.{ bootstrap_relative_path, if (args.is_test) "test" else "exe", "-main_source_file", "src/main.nat", "-name", name, "-optimize", @tagName(args.optimization) },
.max_output_bytes = std.math.maxInt(u64), .max_output_bytes = std.math.maxInt(u64),
}); });
@ -515,8 +439,6 @@ fn compile_self_hosted(allocator: Allocator, args: struct {
.Unknown => error.unknown, .Unknown => error.unknown,
}; };
_ = compilation_result catch |err| { _ = compilation_result catch |err| {
std.debug.print("Compiling the self-hosted compiler failed!\n", .{}); std.debug.print("Compiling the self-hosted compiler failed!\n", .{});
if (compile_run.stdout.len > 0) { if (compile_run.stdout.len > 0) {
@ -527,47 +449,75 @@ fn compile_self_hosted(allocator: Allocator, args: struct {
} }
return err; return err;
}; };
return try std.mem.concat(allocator, u8, &.{"nat/", name});
} }
const Optimization = enum{
none,
debug_prefer_fast,
debug_prefer_size,
lightly_optimize_for_speed,
optimize_for_speed,
optimize_for_size,
aggressively_optimize_for_speed,
aggressively_optimize_for_size,
};
fn run_test_suite(allocator: Allocator, args: struct { fn run_test_suite(allocator: Allocator, args: struct {
self_hosted: bool, self_hosted: bool,
compiler_path: []const u8,
}) bool { }) bool {
const self_hosted = args.self_hosted; const self_hosted = args.self_hosted;
std.debug.print("TESTING {s} COMPILER...\n=================\n", .{if (self_hosted) "SELF-HOSTED" else "BOOTSTRAP"}); std.debug.print("TESTING {s} COMPILER: {s}...\n=================\n", .{if (self_hosted) "SELF-HOSTED" else "BOOTSTRAP", args.compiler_path});
var errors = false; var errors = false;
runStandalone(allocator, .{ runStandalone(allocator, .{
.directory_path = "test/standalone", .directory_path = "test/standalone",
.group_name = "STANDALONE", .group_name = "STANDALONE",
.is_test = false, .is_test = false,
.self_hosted = self_hosted, .self_hosted = self_hosted,
.compiler_path = args.compiler_path,
}) catch { }) catch {
errors = true; errors = true;
}; };
runBuildTests(allocator, .{ runBuildTests(allocator, .{
.self_hosted = self_hosted, .self_hosted = self_hosted,
.compiler_path = args.compiler_path,
}) catch { }) catch {
errors = true; errors = true;
}; };
runStandalone(allocator, .{ runStandalone(allocator, .{
.directory_path = "test/tests", .directory_path = "test/tests",
.group_name = "TEST EXECUTABLE", .group_name = "TEST EXECUTABLE",
.is_test = true, .is_test = true,
.self_hosted = self_hosted, .self_hosted = self_hosted,
.compiler_path = args.compiler_path,
}) catch { }) catch {
errors = true; errors = true;
}; };
//
runStdTests(allocator, .{ runStdTests(allocator, .{
.self_hosted = self_hosted, .self_hosted = self_hosted,
.compiler_path = args.compiler_path,
}) catch { }) catch {
errors = true; errors = true;
}; };
if (!self_hosted) { if (!self_hosted) {
runCmakeTests(allocator, "test/cc") catch { runCmakeTests(allocator, .{
.dir_path = "test/cc",
.compiler_path = args.compiler_path,
}) catch {
errors = true; errors = true;
}; };
runCmakeTests(allocator, "test/c++") catch {
runCmakeTests(allocator, .{
.dir_path = "test/c++",
.compiler_path = args.compiler_path,
}) catch {
errors = true; errors = true;
}; };
@ -575,7 +525,10 @@ fn run_test_suite(allocator: Allocator, args: struct {
.macos => {}, .macos => {},
.windows => {}, .windows => {},
.linux => switch (@import("builtin").abi) { .linux => switch (@import("builtin").abi) {
.gnu => runCmakeTests(allocator, "test/cc_linux") catch { .gnu => runCmakeTests(allocator, .{
.dir_path = "test/cc_linux",
.compiler_path = args.compiler_path,
}) catch {
errors = true; errors = true;
}, },
.musl => {}, .musl => {},
@ -594,20 +547,26 @@ pub fn main() !void {
var errors = run_test_suite(allocator, .{ var errors = run_test_suite(allocator, .{
.self_hosted = false, .self_hosted = false,
.compiler_path = bootstrap_relative_path,
}); });
if (!errors) { if (!errors) {
inline for (@typeInfo(Optimization).Enum.fields) |opt| {
const optimization = @field(Optimization, opt.name);
if (compile_self_hosted(allocator, .{ if (compile_self_hosted(allocator, .{
.is_test = false, .is_test = false,
})) |_| { .optimization = optimization,
})) |compiler_path| {
errors = errors or run_test_suite(allocator, .{ errors = errors or run_test_suite(allocator, .{
.self_hosted = true, .self_hosted = true,
.compiler_path = compiler_path,
}); });
} else |err| { } else |err| {
err catch {}; err catch {};
errors = true; errors = true;
} }
} }
}
if (errors) { if (errors) {
return error.fail; return error.fail;

View File

@ -3,14 +3,7 @@ const assert = std.assert;
const Allocator = std.Allocator; const Allocator = std.Allocator;
const Target = std.Target; const Target = std.Target;
const Optimization = enum{ const Optimization = std.builtin.Optimization;
none,
light,
prefer_size,
prefer_speed,
speed_aggressive,
size_aggressive,
};
const Executable = struct{ const Executable = struct{
target: Target, target: Target,

View File

@ -12,7 +12,8 @@ const Cpu = enum{
const Abi = enum{ const Abi = enum{
none, none,
gnu, msvc, gnu,
msvc,
}; };
const CallingConvention = enum{ const CallingConvention = enum{
@ -37,3 +38,14 @@ const TestFunction = struct{
const StructOptions = struct{ const StructOptions = struct{
sliceable: bool = false, sliceable: bool = false,
}; };
const Optimization = enum{
none,
debug_prefer_fast,
debug_prefer_size,
lightly_optimize_for_speed,
optimize_for_speed,
optimize_for_size,
aggressively_optimize_for_speed,
aggressively_optimize_for_size,
};

View File

@ -935,17 +935,18 @@ extern "C" void NativityLLVMModuleSetTargetTriple(Module& module, const char* ta
module.setTargetTriple(target_triple); module.setTargetTriple(target_triple);
} }
extern "C" void NativityLLVMRunOptimizationPipeline(Module& module, TargetMachine& target_machine) extern "C" void NativityLLVMRunOptimizationPipeline(Module& module, TargetMachine& target_machine, OptimizationLevel optimization_level)
{ {
// TODO: PGO // TODO: PGO
// TODO: CS profile // TODO: CS profile
bool loop_optimizations = !optimization_level.isOptimizingForSize() && optimization_level.getSpeedupLevel() >= 2;
llvm::PipelineTuningOptions pipeline_tuning_options; llvm::PipelineTuningOptions pipeline_tuning_options;
pipeline_tuning_options.LoopUnrolling = false; pipeline_tuning_options.LoopUnrolling = loop_optimizations;
pipeline_tuning_options.LoopInterleaving = false; pipeline_tuning_options.LoopInterleaving = loop_optimizations;
pipeline_tuning_options.LoopVectorization = false; pipeline_tuning_options.LoopVectorization = loop_optimizations;
pipeline_tuning_options.SLPVectorization = false; pipeline_tuning_options.SLPVectorization = loop_optimizations;
pipeline_tuning_options.MergeFunctions = false; pipeline_tuning_options.MergeFunctions = true;
pipeline_tuning_options.CallGraphProfile = false; pipeline_tuning_options.CallGraphProfile = false;
pipeline_tuning_options.UnifiedLTO = false; pipeline_tuning_options.UnifiedLTO = false;
@ -971,7 +972,6 @@ extern "C" void NativityLLVMRunOptimizationPipeline(Module& module, TargetMachin
ModulePassManager module_pass_manager; ModulePassManager module_pass_manager;
OptimizationLevel optimization_level = OptimizationLevel::O0;
bool thin_lto = false; bool thin_lto = false;
bool lto = false; bool lto = false;