From 09c92d666c24d673162b28211bac4f164aafeac2 Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Thu, 5 Jun 2025 19:57:05 -0600 Subject: [PATCH] Move self-hosted tests to self-hosted compiler --- src/compiler.bbb | 284 +++++++++++++++++++++++++++++++++++++++++++++-- src/compiler.cpp | 83 +++----------- src/compiler.hpp | 1 + src/emitter.cpp | 161 +++++++++++++++++++++------ src/parser.cpp | 4 + 5 files changed, 422 insertions(+), 111 deletions(-) diff --git a/src/compiler.bbb b/src/compiler.bbb index 86f1da5..531c6c5 100644 --- a/src/compiler.bbb +++ b/src/compiler.bbb @@ -77,10 +77,17 @@ O = bits u32 [extern] open = fn [cc(c)] (path: &u8, o: O, ...) int; [extern] close = fn [cc(c)] (fd: File) int; +[extern] dup2 = fn [cc(c)] (old_fd: s32, new_fd: s32) s32; +[extern] pipe = fn [cc(c)] (fd: &[2]s32) s32; + [extern] fstat = fn [cc(c)] (fd: File, s: &Stat) int; [extern] write = fn [cc(c)] (fd: File, pointer: &u8, byte_count: u64) ssize; [extern] read = fn [cc(c)] (fd: File, pointer: &u8, byte_count: u64) ssize; +[extern] fork = fn [cc(c)] () s32; +[extern] execve = fn [cc(c)] (name: &u8, arguments: &&u8, environment: &&u8) s32; +[extern] waitpid = fn [cc(c)] (pid: s32, wait_status: &u32, options: s32) s32; + assert = macro (ok: u1) void { if (!ok) @@ -517,6 +524,217 @@ arena_join_string = fn (arena: &Arena, pieces: [][]u8) []u8 return pointer[..size]; } +exit_status = fn (s: u32) u8 +{ + return #truncate((s & 0xff00) >> 8); +} + +term_sig = fn (s: u32) u32 +{ + return s & 0x7f; +} + +stop_sig = fn (s: u32) u32 +{ + return #extend(exit_status(s)); +} + +if_exited = fn (s: u32) u1 +{ + return term_sig(s) == 0; +} + +if_stopped = fn (s: u32) u1 +{ + >v: u16 = #truncate(((s & 0xffff) * 0x10001) >> 8); + return v > 0x7f00; +} + +if_signaled = fn (s: u32) u1 +{ + return (s & 0xffff) - 1 < 0xff; +} + +TerminationKind = enum +{ + unknown, + exit, + signal, + stop, +} + +Execution = struct +{ + streams: [2][]u8, + termination_kind: TerminationKind, + termination_code: u32, +} + +StreamPolicy = enum +{ + inherit, + pipe, + ignore, +} + +ExecutionOptions = struct +{ + policies: [2]StreamPolicy, + null_fd: s32, + null_fd_valid: u1, +} + +os_execute = fn (arena: &Arena, arguments: []&u8, environment: &&u8, options: ExecutionOptions) Execution +{ + assert(arguments.pointer[arguments.length] == zero); + + >result: Execution = zero; + + >null_file_descriptor: s32 = -1; + if (options.null_fd_valid) + { + null_file_descriptor = options.null_fd; + assert(null_file_descriptor >= 0); + } + else if (options.policies[0] == .ignore or options.policies[1] == .ignore) + { + null_file_descriptor = open("/dev/null", { .access_mode = .write_only, zero }); + assert(null_file_descriptor >= 0); + } + + >pipes: [2][2]s32 = undefined; + + for (i: 0..2) + { + if (options.policies[i] == .pipe) + { + if (pipe(&pipes[i]) == -1) + { + #trap(); + } + } + } + + >pid = fork(); + + switch (pid) + { + -1 => + { + #trap(); + }, + 0 => + { + for (i: 0..2) + { + >fd: s32 = #truncate(i + 1); + + switch (options.policies[i]) + { + .inherit => {}, + .pipe => + { + close(pipes[i][0]); + dup2(pipes[i][1], fd); + close(pipes[i][1]); + }, + .ignore => + { + dup2(null_file_descriptor, fd); + close(null_file_descriptor); + }, + } + } + + >result = execve(arguments[0], arguments.pointer, environment); + + if (result != -1) + { + unreachable; + } + + #trap(); + }, + else => + { + for (i: 0..2) + { + if (options.policies[i] == .pipe) + { + close(pipes[i][1]); + } + } + + // TODO: better allocation strategy + >allocation_size: u64 = 1024 * 1024; + >allocation: []u8 = zero; + >is_pipe0 = options.policies[0] == .pipe; + >is_pipe1 = options.policies[1] == .pipe; + if (is_pipe0 or is_pipe1) + { + allocation = arena_allocate_slice[u8](arena, allocation_size * (#extend(is_pipe0) + #extend(is_pipe1))); + } + + >offset: u64 = 0; + + for (i: 0..2) + { + if (options.policies[i] == .pipe) + { + >buffer = allocation[offset..offset + allocation_size]; + >byte_count = read(pipes[i][0], buffer.pointer, buffer.length); + assert(byte_count >= 0); + result.streams[i] = buffer[..#extend(byte_count)]; + close(pipes[i][0]); + + offset += allocation_size; + } + } + + >status: u32 = 0; + >waitpid_result = waitpid(pid, &status, 0); + + if (waitpid_result == pid) + { + if (if_exited(status)) + { + result.termination_kind = .exit; + result.termination_code = #extend(exit_status(status)); + } + else if (if_signaled(status)) + { + result.termination_kind = .signal; + result.termination_code = term_sig(status); + } + else if (if_stopped(status)) + { + result.termination_kind = .stop; + result.termination_code = stop_sig(status); + } + else + { + result.termination_kind = .unknown; + } + + if (!options.null_fd_valid and null_file_descriptor > 0) + { + close(null_file_descriptor); + } + } + else if (waitpid_result == -1) + { + #trap(); + } + else + { + #trap(); + } + }, + } + + return result; +} + file_read = fn (arena: &Arena, path: []u8) []u8 { >fd = os_file_open(path.pointer, { .read = 1, zero }, { .read = 1, zero }); @@ -5420,7 +5638,7 @@ compile = fn (arena: &Arena, options: CompileOptions) void emit(&module); } -compile_file = fn (arena: &Arena, compile_options: CompileFile) void +compile_file = fn (arena: &Arena, compile_options: CompileFile) []u8 { >relative_file_path = compile_options.relative_file_path; if (relative_file_path.length < 5) @@ -5514,19 +5732,26 @@ compile_file = fn (arena: &Arena, compile_options: CompileFile) void }; compile(arena, options); + + return output_executable_path; } -[export] main = fn [cc(c)] (argument_count: u32, argv: &&u8) s32 +names: [_][]u8 = [ + "minimal", +]; + +[export] main = fn [cc(c)] (argument_count: u32, argv: &&u8, envp: &&u8) s32 { global_state_initialize(); >arena = global_state.arena; - if (argument_count < 2) + >arguments = argv[..#extend(argument_count)]; + if (arguments.length < 2) { return 1; } - >command_string = c_string_to_slice(argv[1]); + >command_string = c_string_to_slice(arguments[1]); >command_string_to_enum = #string_to_enum(CompilerCommand, command_string); if (!command_string_to_enum.is_valid) @@ -5539,7 +5764,7 @@ compile_file = fn (arena: &Arena, compile_options: CompileFile) void { .compile => { - if (argument_count < 3) + if (arguments.length < 3) { return 1; } @@ -5549,7 +5774,7 @@ compile_file = fn (arena: &Arena, compile_options: CompileFile) void if (argument_count >= 4) { - >build_mode_string_to_enum = #string_to_enum(BuildMode, c_string_to_slice(argv[3])); + >build_mode_string_to_enum = #string_to_enum(BuildMode, c_string_to_slice(arguments[3])); if (!build_mode_string_to_enum.is_valid) { return 1; @@ -5559,7 +5784,7 @@ compile_file = fn (arena: &Arena, compile_options: CompileFile) void if (argument_count >= 5) { - >has_debug_info_string = c_string_to_slice(argv[4]); + >has_debug_info_string = c_string_to_slice(arguments[4]); if (string_equal(has_debug_info_string, "true")) { has_debug_info = 1; @@ -5574,7 +5799,7 @@ compile_file = fn (arena: &Arena, compile_options: CompileFile) void } } - >relative_file_path_pointer = argv[2]; + >relative_file_path_pointer = arguments[2]; if (!relative_file_path_pointer) { return 1; @@ -5591,8 +5816,47 @@ compile_file = fn (arena: &Arena, compile_options: CompileFile) void }, .test => { - // TODO - #trap(); + if (arguments.length != 2) + { + report_error(); + } + + >debug_info_array: [_]u1 = [1, 0]; + for (name: names) + { + for (build_mode: #enum_values(BuildMode)) + { + for (has_debug_info: debug_info_array) + { + >position = arena.position; + + >relative_file_path = arena_join_string(arena, [ "tests/", name, ".bbb" ][..]); + >compiler_path = compile_file(arena, { + .relative_file_path = relative_file_path, + .build_mode = build_mode, + .has_debug_info = has_debug_info, + .silent = 1, + }); + + >arguments: [_]&u8 = [ + compiler_path.pointer, + "test", + zero, + ]; + >args = arguments[..arguments.length - 1]; + >execution = os_execute(arena, args, envp, zero); + + >success = execution.termination_kind == .exit and execution.termination_code == 0; + + if (!success) + { + #trap(); + } + + arena.position = position; + } + } + } }, } diff --git a/src/compiler.cpp b/src/compiler.cpp index c495f85..3615b92 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -599,75 +599,22 @@ void entry_point(Slice arguments, Slice envp) .silent = true, }); - Slice name_slice = array_to_slice(names); - for (auto name: name_slice(0, 1)) + char* const compiler_arguments[] = { - for (BuildMode build_mode = BuildMode::debug_none; build_mode < BuildMode::count; build_mode = (BuildMode)((backing_type(BuildMode))build_mode + 1)) - { - for (bool has_debug_info : has_debug_info_array) - { - // COMPILATION START - - String relative_file_path_parts[] = { string_literal("tests/"), name, string_literal(".bbb") }; - auto relative_file_path = arena_join_string(arena, array_to_slice(relative_file_path_parts)); - char* const compiler_arguments[] = - { - (char*)compiler.pointer, - (char*)"compile", - (char*)relative_file_path.pointer, - (char*)build_mode_to_string(build_mode).pointer, - (char*)(has_debug_info ? "true" : "false"), - 0, - }; - Slice arg_slice = array_to_slice(compiler_arguments); - arg_slice.length -= 1; - auto execution = os_execute(arena, arg_slice, environment, {}); - auto success = execution.termination_kind == TerminationKind::exit && execution.termination_code == 0; - if (!success) - { - print(string_literal("Self-hosted test failed to compile: ")); - print(name); - print(string_literal("\n")); - bb_fail(); - } - - // COMPILATION END - - // RUN START - - String exe_parts[] = { - string_literal("self-hosted-bb-cache/"), - build_mode_to_string(compiler_build_mode), - string_literal("_"), - compiler_has_debug_info ? string_literal("di") : string_literal("nodi"), - string_literal("/"), - build_mode_to_string(build_mode), - string_literal("_"), - has_debug_info ? string_literal("di") : string_literal("nodi"), - string_literal("/"), - name, - }; - String exe_path = arena_join_string(arena, array_to_slice(exe_parts)); - char* const run_arguments[] = - { - (char*)exe_path.pointer, - 0, - }; - arg_slice = array_to_slice(run_arguments); - arg_slice.length -= 1; - execution = os_execute(arena, arg_slice, environment, {}); - success = execution.termination_kind == TerminationKind::exit && execution.termination_code == 0; - if (!success) - { - print(string_literal("Self-hosted test failed to run: ")); - print(name); - print(string_literal("\n")); - bb_fail(); - } - - // RUN END - } - } + (char*)compiler.pointer, + (char*)"test", + 0, + }; + Slice arg_slice = array_to_slice(compiler_arguments); + arg_slice.length -= 1; + auto execution = os_execute(arena, arg_slice, environment, {}); + auto success = execution.termination_kind == TerminationKind::exit && execution.termination_code == 0; + if (!success) + { + print(string_literal("Self-hosted tests failed to compile: ")); + print(build_mode_to_string(compiler_build_mode)); + print(compiler_has_debug_info ? string_literal(" with debug info\n") : string_literal(" with no debug info\n")); + bb_fail(); } } } diff --git a/src/compiler.hpp b/src/compiler.hpp index a530d94..3cefaa2 100644 --- a/src/compiler.hpp +++ b/src/compiler.hpp @@ -895,6 +895,7 @@ enum class UnaryTypeId { align_of, byte_size, + enum_values, integer_max, }; diff --git a/src/emitter.cpp b/src/emitter.cpp index 7604d89..6c4a60b 100644 --- a/src/emitter.cpp +++ b/src/emitter.cpp @@ -1215,6 +1215,7 @@ fn Type* resolve_type(Module* module, Type* type) } break; case TypeId::void_type: case TypeId::integer: + case TypeId::enumerator: { result = type; } break; @@ -3054,43 +3055,79 @@ fn void analyze_type(Module* module, Value* value, Type* expected_type, TypeAnal value->unary_type.type = unary_type; auto unary_type_id = value->unary_type.id; - if (expected_type) + if (unary_type_id == UnaryTypeId::enum_values) { - value_type = expected_type; + auto element_type = unary_type; + + if (element_type->id != TypeId::enumerator) + { + report_error(); + } + + auto fields = unary_type->enumerator.fields; + auto element_count = fields.length; + if (element_count == 0) + { + report_error(); + } + + auto array_type = get_array_type(module, element_type, element_count); + switch (value->kind) + { + case ValueKind::right: + { + value_type = array_type; + } break; + case ValueKind::left: + { + value_type = get_pointer_type(module, array_type); + } break; + } } else { - value_type = unary_type; - } + if (expected_type) + { + value_type = expected_type; + } + else + { + value_type = unary_type; + } - assert(value_type); - if (value_type->id != TypeId::integer) - { - report_error(); - } + assert(value_type); + if (value_type->id != TypeId::integer) + { + report_error(); + } - u64 value; - auto max_value = integer_max_value(value_type->integer.bit_count, value_type->integer.is_signed); - switch (unary_type_id) - { - case UnaryTypeId::align_of: - { - value = get_byte_alignment(unary_type); - } break; - case UnaryTypeId::byte_size: - { + u64 value; + auto max_value = integer_max_value(value_type->integer.bit_count, value_type->integer.is_signed); + switch (unary_type_id) + { + case UnaryTypeId::align_of: + { + value = get_byte_alignment(unary_type); + } break; + case UnaryTypeId::byte_size: + { - value = get_byte_size(unary_type); - } break; - case UnaryTypeId::integer_max: - { - value = integer_max_value(unary_type->integer.bit_count, unary_type->integer.is_signed); - } break; - } + value = get_byte_size(unary_type); + } break; + case UnaryTypeId::integer_max: + { + value = integer_max_value(unary_type->integer.bit_count, unary_type->integer.is_signed); + } break; + case UnaryTypeId::enum_values: + { + unreachable(); + } break; + } - if (value > max_value) - { - report_error(); + if (value > max_value) + { + report_error(); + } } typecheck(module, expected_type, value_type); @@ -4827,6 +4864,7 @@ fn void invalidate_analysis(Module* module, Value* value) { case ValueId::variable_reference: case ValueId::constant_integer: + case ValueId::unary_type: break; case ValueId::aggregate_initialization: { @@ -6953,6 +6991,43 @@ fn void emit_value(Module* module, Value* value, TypeKind type_kind, bool expect auto constant_integer = LLVMConstInt(resolved_value_type->llvm.abi, max_value, is_signed); llvm_value = constant_integer; } break; + case UnaryTypeId::enum_values: + { + LLVMValueRef buffer[64]; + assert(type_kind == TypeKind::memory); + assert(unary_type->id == TypeId::enumerator); + auto fields = unary_type->enumerator.fields; + auto llvm_enum_type = unary_type->llvm.memory; + u64 i = 0; + for (auto& field : fields) + { + auto v = field.value; + buffer[i] = LLVMConstInt(llvm_enum_type, v, false); + i += 1; + } + auto array_value = LLVMConstArray2(llvm_enum_type, buffer, i); + + switch (value->kind) + { + case ValueKind::right: + { + llvm_value = array_value; + } break; + case ValueKind::left: + { + auto is_constant = true; + assert(resolved_value_type->id == TypeId::pointer); + auto array_type = resolved_value_type->pointer.element_type; + assert(array_type->id == TypeId::array); + resolve_type_in_place(module, array_type); + auto value_array_variable = llvm_module_create_global_variable(module->llvm.module, array_type->llvm.memory, is_constant, LLVMInternalLinkage, array_value, string_literal("enum.values"), 0, LLVMNotThreadLocal, 0, 0); + auto alignment = get_byte_alignment(resolved_value_type); + LLVMSetAlignment(value_array_variable, alignment); + LLVMSetUnnamedAddress(value_array_variable, LLVMGlobalUnnamedAddr); + llvm_value = value_array_variable; + } break; + } + } break; } } break; case ValueId::binary: @@ -7542,6 +7617,21 @@ fn void emit_value(Module* module, Value* value, TypeKind type_kind, bool expect { field_value = value->constant_integer.value; } break; + case ValueId::enum_literal: + { + auto enum_name = value->enum_literal; + auto value_type = value->type; + assert(value_type->id == TypeId::enumerator); + + for (auto& field: value_type->enumerator.fields) + { + if (enum_name.equal(field.name)) + { + field_value = field.value; + break; + } + } + } break; default: unreachable(); } @@ -8228,6 +8318,14 @@ fn void analyze_statement(Module* module, Scope* scope, Statement* statement, u3 analyze_type(module, right, 0, {}); + if (right->kind == ValueKind::right) + { + if (!is_slice(right->type)) + { + reanalyze_type_as_left_value(module, right); + } + } + Type* aggregate_type = 0; switch (right->kind) @@ -8235,10 +8333,7 @@ fn void analyze_statement(Module* module, Scope* scope, Statement* statement, u3 case ValueKind::right: { aggregate_type = right->type; - if (!is_slice(aggregate_type)) - { - report_error(); - } + assert(is_slice(aggregate_type)); } break; case ValueKind::left: { diff --git a/src/parser.cpp b/src/parser.cpp index 69cfd2d..36181cb 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -7,6 +7,7 @@ enum class ValueIntrinsic byte_size, enum_from_int, enum_name, + enum_values, extend, has_debug_info, integer_max, @@ -1159,6 +1160,7 @@ fn Token tokenize(Module* module) string_literal("byte_size"), string_literal("enum_from_int"), string_literal("enum_name"), + string_literal("enum_values"), string_literal("extend"), string_literal("has_debug_info"), string_literal("integer_max"), @@ -1711,6 +1713,7 @@ fn Value* parse_left(Module* module, Scope* scope, ValueBuilder builder) } break; case ValueIntrinsic::align_of: case ValueIntrinsic::byte_size: + case ValueIntrinsic::enum_values: case ValueIntrinsic::integer_max: { skip_space(module); @@ -1726,6 +1729,7 @@ fn Value* parse_left(Module* module, Scope* scope, ValueBuilder builder) { case ValueIntrinsic::align_of: id = UnaryTypeId::align_of; break; case ValueIntrinsic::byte_size: id = UnaryTypeId::byte_size; break; + case ValueIntrinsic::enum_values: id = UnaryTypeId::enum_values; break; case ValueIntrinsic::integer_max: id = UnaryTypeId::integer_max; break; default: unreachable(); }