From 1696e943acd67119104f303467c0e26eecb94544 Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Tue, 28 Apr 2020 11:14:47 -0600 Subject: [PATCH 001/295] Implement @typeInfo for @Frame() Closes https://github.com/ziglang/zig/issues/3066 --- src/analyze.cpp | 13 +++++++++++++ src/analyze.hpp | 3 +++ src/ir.cpp | 15 ++++++++++++--- test/stage1/behavior/type_info.zig | 15 ++++++++++++++- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index d1702738083c687c402034a562e995e7d2cf0c22..5eb0205dfb88eb610de5b64cb982eb3d9b32ee8c 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -6021,6 +6021,19 @@ ZigValue *create_const_null(CodeGen *g, ZigType *type) { return const_val; } +void init_const_fn(ZigValue *const_val, ZigFn *fn) { + const_val->special = ConstValSpecialStatic; + const_val->type = fn->type_entry; + const_val->data.x_ptr.special = ConstPtrSpecialFunction; + const_val->data.x_ptr.data.fn.fn_entry = fn; +} + +ZigValue *create_const_fn(CodeGen *g, ZigFn *fn) { + ZigValue *const_val = g->pass1_arena->create(); + init_const_fn(const_val, fn); + return const_val; +} + void init_const_float(ZigValue *const_val, ZigType *type, double value) { const_val->special = ConstValSpecialStatic; const_val->type = type; diff --git a/src/analyze.hpp b/src/analyze.hpp index ae010c87e1f04508f827b0ec3b94f75ddc3010ed..a92d7ec1de6b1005d52cca1cd04e553b08bda2f5 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -180,6 +180,9 @@ ZigValue *create_const_slice(CodeGen *g, ZigValue *array_val, size_t start, size void init_const_null(ZigValue *const_val, ZigType *type); ZigValue *create_const_null(CodeGen *g, ZigType *type); +void init_const_fn(ZigValue *const_val, ZigFn *fn); +ZigValue *create_const_fn(CodeGen *g, ZigFn *fn); + ZigValue **alloc_const_vals_ptrs(CodeGen *g, size_t count); ZigValue **realloc_const_vals_ptrs(CodeGen *g, ZigValue **ptr, size_t old_count, size_t new_count); diff --git a/src/ir.cpp b/src/ir.cpp index f37f91088a2ef2dc0be409f10a3c1b690b8a5353..0111a33f34b83d2eb340516013ce6d246b943532 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -25166,9 +25166,18 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy break; } case ZigTypeIdFnFrame: - ir_add_error(ira, source_instr, - buf_sprintf("compiler bug: TODO @typeInfo for async function frames. https://github.com/ziglang/zig/issues/3066")); - return ErrorSemanticAnalyzeFail; + { + result = ira->codegen->pass1_arena->create(); + result->special = ConstValSpecialStatic; + result->type = ir_type_info_get_type(ira, "Frame", nullptr); + ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 1); + result->data.x_struct.fields = fields; + ZigFn *fn = type_entry->data.frame.fn; + // function: var + ensure_field_index(result->type, "function", 0); + fields[0] = create_const_fn(ira->codegen, fn); + break; + } } assert(result != nullptr); diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index c897276f8392dbc7ff730131a17592b8cbf27cb2..41301f290d9e5bccdff9813a480ccba47b4ca498 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -202,7 +202,7 @@ fn testUnion() void { expect(typeinfo_info.Union.fields[4].enum_field != null); expect(typeinfo_info.Union.fields[4].enum_field.?.value == 4); expect(typeinfo_info.Union.fields[4].field_type == @TypeOf(@typeInfo(u8).Int)); - expect(typeinfo_info.Union.decls.len == 20); + expect(typeinfo_info.Union.decls.len == 21); const TestNoTagUnion = union { Foo: void, @@ -389,3 +389,16 @@ test "defaut value for a var-typed field" { const S = struct { x: var }; expect(@typeInfo(S).Struct.fields[0].default_value == null); } + +fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "type info for async frames" { + switch (@typeInfo(@Frame(add))) { + .Frame => |frame| { + expect(frame.function == add); + }, + else => unreachable, + } +} -- 2.54.0 From ca6db2d008cf3e0e3700e84400bd3d6e259e3c0f Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Tue, 28 Apr 2020 11:16:11 -0600 Subject: [PATCH 002/295] Implement @Type() for EnumLiteral and FnFrame --- lib/std/builtin.zig | 8 +++++++- src/ir.cpp | 12 ++++++++++-- test/stage1/behavior/type.zig | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index af8033ae91852f5cea5a4e77afecfdd7e991f380..9d419f9af2ebd3243b483700826e01efd2bf17be 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -157,7 +157,7 @@ pub const TypeInfo = union(enum) { Fn: Fn, BoundFn: Fn, Opaque: void, - Frame: void, + Frame: Frame, AnyFrame: AnyFrame, Vector: Vector, EnumLiteral: void, @@ -315,6 +315,12 @@ pub const TypeInfo = union(enum) { args: []FnArg, }; + /// This data structure is used by the Zig language code generation and + /// therefore must be kept in sync with the compiler implementation. + pub const Frame = struct { + function: var, + }; + /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. pub const AnyFrame = struct { diff --git a/src/ir.cpp b/src/ir.cpp index 0111a33f34b83d2eb340516013ce6d246b943532..1a7c0c352798d7124fcda9d9b9c79945d2c5e963 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -25446,10 +25446,18 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI ZigType *child_type = get_const_field_meta_type_optional(ira, source_instr->source_node, payload, "child", 0); return get_any_frame_type(ira->codegen, child_type); } + case ZigTypeIdEnumLiteral: + return ira->codegen->builtin_types.entry_enum_literal; + case ZigTypeIdFnFrame: { + assert(payload->special == ConstValSpecialStatic); + assert(payload->type == ir_type_info_get_type(ira, "Frame", nullptr)); + ZigValue *function = get_const_field(ira, source_instr->source_node, payload, "function", 0); + assert(function->type->id == ZigTypeIdFn); + ZigFn *fn = function->data.x_ptr.data.fn.fn_entry; + return get_fn_frame_type(ira->codegen, fn); + } case ZigTypeIdErrorSet: case ZigTypeIdEnum: - case ZigTypeIdFnFrame: - case ZigTypeIdEnumLiteral: ir_add_error(ira, source_instr, buf_sprintf( "TODO implement @Type for 'TypeInfo.%s': see https://github.com/ziglang/zig/issues/2907", type_id_name(tagTypeId))); return ira->codegen->invalid_inst_gen->value->type; diff --git a/test/stage1/behavior/type.zig b/test/stage1/behavior/type.zig index 2860229cb8c4bfe329a0e0d4d9c2cefefff8d5ef..d8ed6338875b7e0ea6c9c2325c199135696bc95e 100644 --- a/test/stage1/behavior/type.zig +++ b/test/stage1/behavior/type.zig @@ -213,3 +213,19 @@ test "Type.AnyFrame" { anyframe->anyframe->u8, }); } + +test "Type.EnumLiteral" { + testTypes(&[_]type{ + @TypeOf(.Dummy), + }); +} + +fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "Type.Frame" { + testTypes(&[_]type{ + @Frame(add), + }); +} -- 2.54.0 From 647901b4a82dbb89656c620d4d4a89869fdf1fa0 Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Thu, 30 Apr 2020 04:30:01 -0600 Subject: [PATCH 003/295] Constify TypeInfo --- lib/std/builtin.zig | 18 +++++++++--------- lib/std/meta.zig | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 9d419f9af2ebd3243b483700826e01efd2bf17be..9b0293228bae80d1a994c18559e09b6d2d0566c0 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -235,8 +235,8 @@ pub const TypeInfo = union(enum) { /// therefore must be kept in sync with the compiler implementation. pub const Struct = struct { layout: ContainerLayout, - fields: []StructField, - decls: []Declaration, + fields: []const StructField, + decls: []const Declaration, }; /// This data structure is used by the Zig language code generation and @@ -261,7 +261,7 @@ pub const TypeInfo = union(enum) { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. - pub const ErrorSet = ?[]Error; + pub const ErrorSet = ?[]const Error; /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. @@ -275,8 +275,8 @@ pub const TypeInfo = union(enum) { pub const Enum = struct { layout: ContainerLayout, tag_type: type, - fields: []EnumField, - decls: []Declaration, + fields: []const EnumField, + decls: []const Declaration, is_exhaustive: bool, }; @@ -293,8 +293,8 @@ pub const TypeInfo = union(enum) { pub const Union = struct { layout: ContainerLayout, tag_type: ?type, - fields: []UnionField, - decls: []Declaration, + fields: []const UnionField, + decls: []const Declaration, }; /// This data structure is used by the Zig language code generation and @@ -312,7 +312,7 @@ pub const TypeInfo = union(enum) { is_generic: bool, is_var_args: bool, return_type: ?type, - args: []FnArg, + args: []const FnArg, }; /// This data structure is used by the Zig language code generation and @@ -358,7 +358,7 @@ pub const TypeInfo = union(enum) { is_export: bool, lib_name: ?[]const u8, return_type: type, - arg_names: [][]const u8, + arg_names: []const []const u8, /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. diff --git a/lib/std/meta.zig b/lib/std/meta.zig index e38712af2b12930fe5d1fee322ca1605bdfaaf14..4fc74282a56008fa156c71c8063fb78494fd0e97 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -219,7 +219,7 @@ test "std.meta.containerLayout" { testing.expect(containerLayout(U3) == .Extern); } -pub fn declarations(comptime T: type) []TypeInfo.Declaration { +pub fn declarations(comptime T: type) []const TypeInfo.Declaration { return switch (@typeInfo(T)) { .Struct => |info| info.decls, .Enum => |info| info.decls, @@ -243,7 +243,7 @@ test "std.meta.declarations" { fn a() void {} }; - const decls = comptime [_][]TypeInfo.Declaration{ + const decls = comptime [_][]const TypeInfo.Declaration{ declarations(E1), declarations(S1), declarations(U1), @@ -292,10 +292,10 @@ test "std.meta.declarationInfo" { } pub fn fields(comptime T: type) switch (@typeInfo(T)) { - .Struct => []TypeInfo.StructField, - .Union => []TypeInfo.UnionField, - .ErrorSet => []TypeInfo.Error, - .Enum => []TypeInfo.EnumField, + .Struct => []const TypeInfo.StructField, + .Union => []const TypeInfo.UnionField, + .ErrorSet => []const TypeInfo.Error, + .Enum => []const TypeInfo.EnumField, else => @compileError("Expected struct, union, error set or enum type, found '" ++ @typeName(T) ++ "'"), } { return switch (@typeInfo(T)) { -- 2.54.0 From a62e9bc8e50296e2d5b201614a78b0e658887aa9 Mon Sep 17 00:00:00 2001 From: Tadeo Kondrak Date: Thu, 30 Apr 2020 05:50:17 -0600 Subject: [PATCH 004/295] Implement @Type for ErrorSet --- lib/std/builtin.zig | 1 + src/ir.cpp | 74 ++++++++++++++++++++++++++++++++++- test/stage1/behavior/type.zig | 7 ++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 9b0293228bae80d1a994c18559e09b6d2d0566c0..47fba9d09598eb164224755aca24f8c6f80bfe52 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -256,6 +256,7 @@ pub const TypeInfo = union(enum) { /// therefore must be kept in sync with the compiler implementation. pub const Error = struct { name: []const u8, + /// This field is ignored when using @Type(). value: comptime_int, }; diff --git a/src/ir.cpp b/src/ir.cpp index 1a7c0c352798d7124fcda9d9b9c79945d2c5e963..5ba92be3c0b986555549598bbb3aa9ace95cedd1 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -25456,7 +25456,79 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI ZigFn *fn = function->data.x_ptr.data.fn.fn_entry; return get_fn_frame_type(ira->codegen, fn); } - case ZigTypeIdErrorSet: + case ZigTypeIdErrorSet: { + assert(payload->special == ConstValSpecialStatic); + assert(payload->type->id == ZigTypeIdOptional); + ZigValue *slice = payload->data.x_optional; + if (slice == nullptr) + return ira->codegen->builtin_types.entry_global_error_set; + assert(slice->special == ConstValSpecialStatic); + assert(is_slice(slice->type)); + ZigType *err_set_type = new_type_table_entry(ZigTypeIdErrorSet); + Buf bare_name = BUF_INIT; + buf_init_from_buf(&err_set_type->name, get_anon_type_name(ira->codegen, ira->old_irb.exec, "error", source_instr->scope, source_instr->source_node, &bare_name)); + err_set_type->size_in_bits = ira->codegen->builtin_types.entry_global_error_set->size_in_bits; + err_set_type->abi_align = ira->codegen->builtin_types.entry_global_error_set->abi_align; + err_set_type->abi_size = ira->codegen->builtin_types.entry_global_error_set->abi_size; + ZigValue *ptr = slice->data.x_struct.fields[slice_ptr_index]; + assert(ptr->data.x_ptr.special == ConstPtrSpecialBaseArray);; + assert(ptr->data.x_ptr.data.base_array.elem_index == 0); + ZigValue *arr = ptr->data.x_ptr.data.base_array.array_val; + assert(arr->special == ConstValSpecialStatic); + assert(arr->data.x_array.special == ConstArraySpecialNone); + ZigValue *len = slice->data.x_struct.fields[slice_len_index]; + size_t count = bigint_as_usize(&len->data.x_bigint); + err_set_type->data.error_set.err_count = count; + err_set_type->data.error_set.errors = heap::c_allocator.allocate(count); + bool *already_set = heap::c_allocator.allocate(ira->codegen->errors_by_index.length + count); + for (size_t i = 0; i < count; i++) { + ZigValue *error = &arr->data.x_array.data.s_none.elements[i]; + assert(error->type == ir_type_info_get_type(ira, "Error", nullptr)); + ErrorTableEntry *err_entry = heap::c_allocator.create(); + err_entry->decl_node = source_instr->source_node; + ZigValue *name_slice = get_const_field(ira, source_instr->source_node, error, "name", 0); + ZigValue *name_ptr = name_slice->data.x_struct.fields[slice_ptr_index]; + ZigValue *name_len = name_slice->data.x_struct.fields[slice_len_index]; + assert(name_ptr->data.x_ptr.special == ConstPtrSpecialBaseArray); + assert(name_ptr->data.x_ptr.data.base_array.elem_index == 0); + ZigValue *name_arr = name_ptr->data.x_ptr.data.base_array.array_val; + assert(name_arr->special == ConstValSpecialStatic); + switch (name_arr->data.x_array.special) { + case ConstArraySpecialUndef: + return ira->codegen->invalid_inst_gen->value->type; + case ConstArraySpecialNone: { + buf_resize(&err_entry->name, 0); + size_t name_count = bigint_as_usize(&name_len->data.x_bigint); + for (size_t j = 0; j < name_count; j++) { + ZigValue *ch_val = &name_arr->data.x_array.data.s_none.elements[j]; + unsigned ch = bigint_as_u32(&ch_val->data.x_bigint); + buf_append_char(&err_entry->name, ch); + } + break; + } + case ConstArraySpecialBuf: + buf_init_from_buf(&err_entry->name, name_arr->data.x_array.data.s_buf); + break; + } + auto existing_entry = ira->codegen->error_table.put_unique(&err_entry->name, err_entry); + if (existing_entry) { + err_entry->value = existing_entry->value->value; + } else { + size_t error_value_count = ira->codegen->errors_by_index.length; + assert((uint32_t)error_value_count < (((uint32_t)1) << (uint32_t)ira->codegen->err_tag_type->data.integral.bit_count)); + err_entry->value = error_value_count; + ira->codegen->errors_by_index.append(err_entry); + } + if (already_set[err_entry->value]) { + ir_add_error(ira, source_instr, buf_sprintf("duplicate error: %s", buf_ptr(&err_entry->name))); + return ira->codegen->invalid_inst_gen->value->type; + } else { + already_set[err_entry->value] = true; + } + err_set_type->data.error_set.errors[i] = err_entry; + } + return err_set_type; + } case ZigTypeIdEnum: ir_add_error(ira, source_instr, buf_sprintf( "TODO implement @Type for 'TypeInfo.%s': see https://github.com/ziglang/zig/issues/2907", type_id_name(tagTypeId))); diff --git a/test/stage1/behavior/type.zig b/test/stage1/behavior/type.zig index d8ed6338875b7e0ea6c9c2325c199135696bc95e..f21e9f1ce994e1ebff58c4be3ac200897d02d26e 100644 --- a/test/stage1/behavior/type.zig +++ b/test/stage1/behavior/type.zig @@ -229,3 +229,10 @@ test "Type.Frame" { @Frame(add), }); } + +test "Type.ErrorSet" { + // error sets don't compare equal so just check if they compile + _ = @Type(@typeInfo(error{})); + _ = @Type(@typeInfo(error{A})); + _ = @Type(@typeInfo(error{ A, B, C })); +} -- 2.54.0 From 037c72fe6781df317a6c8cc04739d277cb96979f Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 15 Jun 2020 15:52:59 +0300 Subject: [PATCH 005/295] Convert paths to UTF-16 before calling CreateDirectory on windows --- src/os.cpp | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/os.hpp | 6 ++ src/util.hpp | 2 +- 3 files changed, 160 insertions(+), 2 deletions(-) diff --git a/src/os.cpp b/src/os.cpp index 26ed0dc4e171aba4878ff199b13193eb6970e5b7..1c61239e3bac70cbabbfae7ffa287ce40c554a8b 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -6,8 +6,11 @@ */ #include "os.hpp" +#include "buffer.hpp" #include "util.hpp" #include "error.hpp" +#include "util_base.hpp" +#include #if defined(_WIN32) @@ -1426,7 +1429,8 @@ Error os_make_path(Buf *path) { Error os_make_dir(Buf *path) { #if defined(ZIG_OS_WINDOWS) - if (!CreateDirectory(buf_ptr(path), NULL)) { + PathSpace path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(path), buf_len(path) }); + if (!CreateDirectoryW(&path_space.data.items[0], NULL)) { if (GetLastError() == ERROR_ALREADY_EXISTS) return ErrorPathAlreadyExists; if (GetLastError() == ERROR_PATH_NOT_FOUND) @@ -1719,6 +1723,26 @@ static uint8_t utf8CodepointSequenceLength(uint32_t c) { zig_unreachable(); } +// Ported from std.unicode.utf8ByteSequenceLength +static uint8_t utf8ByteSequenceLength(uint8_t first_byte) { + switch (clzll(~first_byte)) { + case 0: + return 1; + break; + case 2: + return 2; + break; + case 3: + return 3; + break; + case 4: + return 4; + break; + default: + zig_unreachable(); + } +} + // Ported from std/unicode.zig static size_t utf8Encode(uint32_t c, Slice out) { size_t length = utf8CodepointSequenceLength(c); @@ -1753,6 +1777,80 @@ static size_t utf8Encode(uint32_t c, Slice out) { return length; } +// Ported from std.unicode.utf8Decode2 +static uint32_t utf8Decode2(Slice bytes) { + assert(bytes.len == 2); + assert((bytes.at(0) & 0b11100000) == 0b11000000); + + uint32_t value = bytes.at(0) & 0b00011111; + assert((bytes.at(1) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(1) & 0b00111111; + + assert(value >= 0x80); + return value; +} + +// Ported from std.unicode.utf8Decode3 +static uint32_t utf8Decode3(Slice bytes) { + assert(bytes.len == 3); + assert((bytes.at(0) & 0b11110000) == 0b11100000); + + uint32_t value = bytes.at(0) & 0b00001111; + assert((bytes.at(1) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(1) & 0b00111111; + + assert((bytes.at(2) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(2) & 0b00111111; + + assert(value >= 0x80); + assert(value < 0xd800 || value > 0xdfff); + return value; +} + +// Ported from std.unicode.utf8Decode4 +static uint32_t utf8Decode4(Slice bytes) { + assert(bytes.len == 4); + assert((bytes.at(0) & 0b11111000) == 0b11110000); + + uint32_t value = bytes.at(0) & 0b00000111; + assert((bytes.at(1) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(1) & 0b00111111; + + assert((bytes.at(2) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(2) & 0b00111111; + + assert((bytes.at(3) & 0b11000000) == 0b10000000); + value <<= 6; + value |= bytes.at(3) & 0b00111111; + + assert(value >= 0x10000 && value <= 0x10FFFF); + return value; +} + +// Ported from std.unicode.utf8Decode +static uint32_t utf8Decode(Slice bytes) { + switch (bytes.len) { + case 1: + return bytes.at(0); + break; + case 2: + return utf8Decode2(bytes); + break; + case 3: + return utf8Decode3(bytes); + break; + case 4: + return utf8Decode4(bytes); + break; + default: + zig_unreachable(); + } +} // Ported from std.unicode.utf16leToUtf8Alloc static void utf16le_ptr_to_utf8(Buf *out, WCHAR *utf16le) { // optimistically guess that it will all be ascii. @@ -1770,6 +1868,60 @@ static void utf16le_ptr_to_utf8(Buf *out, WCHAR *utf16le) { out_index += utf8_len; } } + +// Ported from std.unicode.utf8ToUtf16Le +static size_t utf8_to_utf16le(WCHAR *utf16_le, Slice utf8) { + size_t dest_i = 0; + size_t src_i = 0; + while (src_i < utf8.len) { + uint8_t n = utf8ByteSequenceLength(utf8.at(src_i)); + size_t next_src_i = src_i + n; + uint32_t codepoint = utf8Decode(utf8.slice(src_i, next_src_i)); + if (codepoint < 0x10000) { + utf16_le[dest_i] = codepoint; + dest_i += 1; + } else { + WCHAR high = ((codepoint - 0x10000) >> 10) + 0xD800; + WCHAR low = (codepoint & 0x3FF) + 0xDC00; + utf16_le[dest_i] = high; + utf16_le[dest_i + 1] = low; + dest_i += 2; + } + src_i = next_src_i; + } + return dest_i; +} + +// Ported from std.os.windows.sliceToPrefixedFileW +PathSpace slice_to_prefixed_file_w(Slice path) { + PathSpace path_space; + for (size_t idx = 0; idx < path.len; idx++) { + assert(path.ptr[idx] != '*' && path.ptr[idx] != '?' && path.ptr[idx] != '"' && + path.ptr[idx] != '<' && path.ptr[idx] != '>' && path.ptr[idx] != '|'); + } + + size_t start_index; + if (memStartsWith(path, str("\\?")) || !isAbsoluteWindows(path)) { + start_index = 0; + } else { + static WCHAR prefix[4] = { u'\\', u'?', u'?', u'\\' }; + memCopy(path_space.data.slice(), Slice { prefix, 4 }); + start_index = 4; + } + + path_space.len = start_index + utf8_to_utf16le(path_space.data.slice().sliceFrom(start_index).ptr, path); + assert(path_space.len <= path_space.data.len); + + Slice path_slice = path_space.data.slice().slice(0, path_space.len); + for (size_t elem_idx = 0; elem_idx < path_slice.len; elem_idx += 1) { + if (path_slice.at(elem_idx) == '/') { + path_slice.at(elem_idx) = '\\'; + } + } + + path_space.data.items[path_space.len] = 0; + return path_space; +} #endif // Ported from std.os.getAppDataDir diff --git a/src/os.hpp b/src/os.hpp index e73e5e3aaa3a02910af2e2a296873945863f7f5d..12d0298ab7ce75231fb4a4d50d3a5a8adebeb5e7 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -155,4 +155,10 @@ Error ATTRIBUTE_MUST_USE os_get_app_data_dir(Buf *out_path, const char *appname) Error ATTRIBUTE_MUST_USE os_self_exe_shared_libs(ZigList &paths); +struct PathSpace { + Array data; + size_t len; +}; + +PathSpace slice_to_prefixed_file_w(Slice path); #endif diff --git a/src/util.hpp b/src/util.hpp index bd1a5b1e4c2081af1fb929e0b034aef59a45e983..ab8f4c8e70507bcc1c99298dc77d752e149ea37a 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -159,7 +159,7 @@ struct Slice { inline T &at(size_t i) { assert(i < len); - return &ptr[i]; + return ptr[i]; } inline Slice slice(size_t start, size_t end) { -- 2.54.0 From 3b0b56b81a26d9ba4d42812843cf2e9407d6d2bc Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 15 Jun 2020 16:15:10 +0300 Subject: [PATCH 006/295] Switched more Windows FS calls to their wide versions --- src/os.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/os.cpp b/src/os.cpp index 1c61239e3bac70cbabbfae7ffa287ce40c554a8b..b88e0510e3aea3d222b9f36ae37691745f8f30e8 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -776,7 +776,8 @@ Error os_fetch_file(FILE *f, Buf *out_buf) { Error os_file_exists(Buf *full_path, bool *result) { #if defined(ZIG_OS_WINDOWS) - *result = GetFileAttributes(buf_ptr(full_path)) != INVALID_FILE_ATTRIBUTES; + PathSpace path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(full_path), buf_len(full_path) }); + *result = GetFileAttributesW(&path_space.data.items[0]) != INVALID_FILE_ATTRIBUTES; return ErrorNone; #else *result = access(buf_ptr(full_path), F_OK) != -1; @@ -1333,7 +1334,9 @@ Error os_rename(Buf *src_path, Buf *dest_path) { return ErrorNone; } #if defined(ZIG_OS_WINDOWS) - if (!MoveFileExA(buf_ptr(src_path), buf_ptr(dest_path), MOVEFILE_REPLACE_EXISTING)) { + PathSpace src_path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(src_path), buf_len(src_path) }); + PathSpace dest_path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(dest_path), buf_len(dest_path) }); + if (!MoveFileExW(&src_path_space.data.items[0], &dest_path_space.data.items[0], MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { return ErrorFileSystem; } #else @@ -2014,8 +2017,8 @@ Error os_self_exe_shared_libs(ZigList &paths) { Error os_file_open_rw(Buf *full_path, OsFile *out_file, OsFileAttr *attr, bool need_write, uint32_t mode) { #if defined(ZIG_OS_WINDOWS) - // TODO use CreateFileW - HANDLE result = CreateFileA(buf_ptr(full_path), + PathSpace path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(full_path), buf_len(full_path) }); + HANDLE result = CreateFileW(&path_space.data.items[0], need_write ? (GENERIC_READ|GENERIC_WRITE) : GENERIC_READ, need_write ? 0 : FILE_SHARE_READ, nullptr, @@ -2119,8 +2122,9 @@ Error os_file_open_w(Buf *full_path, OsFile *out_file, OsFileAttr *attr, uint32_ Error os_file_open_lock_rw(Buf *full_path, OsFile *out_file) { #if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(full_path), buf_len(full_path) }); for (;;) { - HANDLE result = CreateFileA(buf_ptr(full_path), GENERIC_READ | GENERIC_WRITE, + HANDLE result = CreateFileW(&path_space.data.items[0], GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (result == INVALID_HANDLE_VALUE) { -- 2.54.0 From ce30357532395f46229052b5bcdc5f0a49d2b20d Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 15 Jun 2020 22:58:59 +1000 Subject: [PATCH 007/295] std: clean up debug stderr variables - stderr_file_writer was unused - stderr_stream was a pointer to a stream, rather than a stream - other functions assumed that getStderrStream has already been called --- lib/std/debug.zig | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index f339aa639bcaccf753b2cc1ef6610346dd67e518..916fb7ff2ddfbe7238ba3fc68e5facc6a16e2f4a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -50,14 +50,10 @@ pub const LineInfo = struct { } }; +var stderr_mutex = std.Mutex.init(); + /// Tries to write to stderr, unbuffered, and ignores any error returned. /// Does not append a newline. -var stderr_file: File = undefined; -var stderr_file_writer: File.Writer = undefined; - -var stderr_stream: ?*File.OutStream = null; -var stderr_mutex = std.Mutex.init(); - pub fn warn(comptime fmt: []const u8, args: var) void { const held = stderr_mutex.acquire(); defer held.release(); @@ -65,16 +61,8 @@ pub fn warn(comptime fmt: []const u8, args: var) void { nosuspend stderr.print(fmt, args) catch return; } -pub fn getStderrStream() *File.OutStream { - if (stderr_stream) |st| { - return st; - } else { - stderr_file = io.getStdErr(); - stderr_file_writer = stderr_file.outStream(); - const st = &stderr_file_writer; - stderr_stream = st; - return st; - } +pub fn getStderrStream() File.OutStream { + return io.getStdErr().outStream(); } pub fn getStderrMutex() *std.Mutex { @@ -99,6 +87,7 @@ pub fn detectTTYConfig() TTY.Config { if (process.getEnvVarOwned(allocator, "ZIG_DEBUG_COLOR")) |_| { return .escape_codes; } else |_| { + const stderr_file = io.getStdErr(); if (stderr_file.supportsAnsiEscapeCodes()) { return .escape_codes; } else if (builtin.os.tag == .windows and stderr_file.isTty()) { @@ -458,6 +447,7 @@ pub const TTY = struct { .Reset => out_stream.writeAll(RESET) catch return, }, .windows_api => if (builtin.os.tag == .windows) { + const stderr_file = io.getStdErr(); const S = struct { var attrs: windows.WORD = undefined; var init_attrs = false; -- 2.54.0 From af592f0ddd4448e746cf288b674c0199325598d5 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 15 Jun 2020 23:51:25 +1000 Subject: [PATCH 008/295] std: remove std.debug.getStderrStream Rather than migrate to new 'writer' interface, just remove it --- lib/std/debug.zig | 16 ++++++---------- lib/std/json.zig | 2 +- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 916fb7ff2ddfbe7238ba3fc68e5facc6a16e2f4a..591f2d1a802a9380681aee243d9a7537bd941a56 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -57,14 +57,10 @@ var stderr_mutex = std.Mutex.init(); pub fn warn(comptime fmt: []const u8, args: var) void { const held = stderr_mutex.acquire(); defer held.release(); - const stderr = getStderrStream(); + const stderr = io.getStdErr().writer(); nosuspend stderr.print(fmt, args) catch return; } -pub fn getStderrStream() File.OutStream { - return io.getStdErr().outStream(); -} - pub fn getStderrMutex() *std.Mutex { return &stderr_mutex; } @@ -102,7 +98,7 @@ pub fn detectTTYConfig() TTY.Config { /// TODO multithreaded awareness pub fn dumpCurrentStackTrace(start_addr: ?usize) void { nosuspend { - const stderr = getStderrStream(); + const stderr = io.getStdErr().writer(); if (builtin.strip_debug_info) { stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; return; @@ -123,7 +119,7 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { /// TODO multithreaded awareness pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { nosuspend { - const stderr = getStderrStream(); + const stderr = io.getStdErr().writer(); if (builtin.strip_debug_info) { stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; return; @@ -193,7 +189,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace /// TODO multithreaded awareness pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { nosuspend { - const stderr = getStderrStream(); + const stderr = io.getStdErr().writer(); if (builtin.strip_debug_info) { stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; return; @@ -261,7 +257,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c const held = panic_mutex.acquire(); defer held.release(); - const stderr = getStderrStream(); + const stderr = io.getStdErr().writer(); stderr.print(format ++ "\n", args) catch os.abort(); if (trace) |t| { dumpStackTrace(t.*); @@ -286,7 +282,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c // A panic happened while trying to print a previous panic message, // we're still holding the mutex but that's fine as we're going to // call abort() - const stderr = getStderrStream(); + const stderr = io.getStdErr().writer(); stderr.print("Panicked during a panic. Aborting.\n", .{}) catch os.abort(); }, else => { diff --git a/lib/std/json.zig b/lib/std/json.zig index eeceeac8a7f06b3f0e69ed7b125cbb3c2cbe050f..4acdbc7d1ad35a4cadb4f46198ba0cfaa239e79a 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -1288,7 +1288,7 @@ pub const Value = union(enum) { var held = std.debug.getStderrMutex().acquire(); defer held.release(); - const stderr = std.debug.getStderrStream(); + const stderr = io.getStdErr().writer(); std.json.stringify(self, std.json.StringifyOptions{ .whitespace = null }, stderr) catch return; } }; -- 2.54.0 From c34bdff4bbba88feefeca6491730526b2cc9f72a Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 15 Jun 2020 18:38:41 +0300 Subject: [PATCH 009/295] Use more wide functions on windows --- src/os.cpp | 35 +++++++++++++++++------------------ src/os.hpp | 4 +++- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/os.cpp b/src/os.cpp index b88e0510e3aea3d222b9f36ae37691745f8f30e8..d59cff62a2066b197cbf0161abc8b6cf6f8ce588 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -11,6 +11,7 @@ #include "error.hpp" #include "util_base.hpp" #include +#include #if defined(_WIN32) @@ -76,6 +77,7 @@ typedef SSIZE_T ssize_t; #endif #if defined(ZIG_OS_WINDOWS) +static void utf16le_ptr_to_utf8(Buf *out, WCHAR *utf16le); static uint64_t windows_perf_freq; #elif defined(__MACH__) static clock_serv_t macos_calendar_clock; @@ -272,11 +274,13 @@ void os_path_join(Buf *dirname, Buf *basename, Buf *out_full_path) { Error os_path_real(Buf *rel_path, Buf *out_abs_path) { #if defined(ZIG_OS_WINDOWS) - buf_resize(out_abs_path, 4096); - if (_fullpath(buf_ptr(out_abs_path), buf_ptr(rel_path), buf_len(out_abs_path)) == nullptr) { - zig_panic("_fullpath failed"); + PathSpace rel_path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(rel_path), buf_len(rel_path) }); + PathSpace out_abs_path_space; + + if (_wfullpath(&out_abs_path_space.data.items[0], &rel_path_space.data.items[0], PATH_MAX_WIDE) == nullptr) { + zig_panic("_wfullpath failed"); } - buf_resize(out_abs_path, strlen(buf_ptr(out_abs_path))); + utf16le_ptr_to_utf8(out_abs_path, &out_abs_path_space.data.items[0]); return ErrorNone; #elif defined(ZIG_OS_POSIX) buf_resize(out_abs_path, PATH_MAX + 1); @@ -1234,11 +1238,11 @@ Error os_fetch_file_path(Buf *full_path, Buf *out_contents) { Error os_get_cwd(Buf *out_cwd) { #if defined(ZIG_OS_WINDOWS) - char buf[4096]; - if (GetCurrentDirectory(4096, buf) == 0) { + PathSpace path_space; + if (GetCurrentDirectoryW(PATH_MAX_WIDE, &path_space.data.items[0]) == 0) { zig_panic("GetCurrentDirectory failed"); } - buf_init_from_str(out_cwd, buf); + utf16le_ptr_to_utf8(out_cwd, &path_space.data.items[0]); return ErrorNone; #elif defined(ZIG_OS_POSIX) char buf[PATH_MAX]; @@ -1538,18 +1542,13 @@ int os_init(void) { Error os_self_exe_path(Buf *out_path) { #if defined(ZIG_OS_WINDOWS) - buf_resize(out_path, 256); - for (;;) { - DWORD copied_amt = GetModuleFileName(nullptr, buf_ptr(out_path), buf_len(out_path)); - if (copied_amt <= 0) { - return ErrorFileNotFound; - } - if (copied_amt < buf_len(out_path)) { - buf_resize(out_path, copied_amt); - return ErrorNone; - } - buf_resize(out_path, buf_len(out_path) * 2); + PathSpace path_space; + DWORD copied_amt = GetModuleFileNameW(nullptr, &path_space.data.items[0], PATH_MAX_WIDE); + if (copied_amt <= 0) { + return ErrorFileNotFound; } + utf16le_ptr_to_utf8(out_path, &path_space.data.items[0]); + return ErrorNone; #elif defined(ZIG_OS_DARWIN) // How long is the executable's path? diff --git a/src/os.hpp b/src/os.hpp index 12d0298ab7ce75231fb4a4d50d3a5a8adebeb5e7..9792a42c453754adc60326e97903f66fe1bd34b1 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -155,8 +155,10 @@ Error ATTRIBUTE_MUST_USE os_get_app_data_dir(Buf *out_path, const char *appname) Error ATTRIBUTE_MUST_USE os_self_exe_shared_libs(ZigList &paths); +const size_t PATH_MAX_WIDE = 32767; + struct PathSpace { - Array data; + Array data; size_t len; }; -- 2.54.0 From 242246f79312f3106290b9b1aea6dfd5a18368b0 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 15 Jun 2020 21:51:53 +0300 Subject: [PATCH 010/295] UTF16 create process, utf8->utf16 fix --- src/os.cpp | 60 +++++++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/os.cpp b/src/os.cpp index d59cff62a2066b197cbf0161abc8b6cf6f8ce588..e363168579d1cd1cf12a3aebb0702ef63d849719 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -7,6 +7,7 @@ #include "os.hpp" #include "buffer.hpp" +#include "heap.hpp" #include "util.hpp" #include "error.hpp" #include "util_base.hpp" @@ -78,6 +79,7 @@ typedef SSIZE_T ssize_t; #if defined(ZIG_OS_WINDOWS) static void utf16le_ptr_to_utf8(Buf *out, WCHAR *utf16le); +static size_t utf8_to_utf16le(WCHAR *utf16_le, Slice utf8); static uint64_t windows_perf_freq; #elif defined(__MACH__) static clock_serv_t macos_calendar_clock; @@ -153,15 +155,21 @@ static void os_spawn_process_windows(ZigList &args, Termination *t os_windows_create_command_line(&command_line, args); PROCESS_INFORMATION piProcInfo = {0}; - STARTUPINFO siStartInfo = {0}; - siStartInfo.cb = sizeof(STARTUPINFO); + STARTUPINFOW siStartInfo = {0}; + siStartInfo.cb = sizeof(STARTUPINFOW); - const char *exe = args.at(0); - BOOL success = CreateProcessA(exe, buf_ptr(&command_line), nullptr, nullptr, TRUE, 0, nullptr, nullptr, + Slice exe_slice = str(args.at(0)); + auto exe_utf16_slice = Slice::alloc(exe_slice.len + 1); + exe_utf16_slice.ptr[utf8_to_utf16le(exe_utf16_slice.ptr, exe_slice)] = 0; + + auto command_line_utf16 = Slice::alloc(buf_len(&command_line) + 1); + command_line_utf16.ptr[utf8_to_utf16le(command_line_utf16.ptr, buf_to_slice(&command_line))] = 0; + + BOOL success = CreateProcessW(exe_utf16_slice.ptr, command_line_utf16.ptr, nullptr, nullptr, TRUE, CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, &siStartInfo, &piProcInfo); if (!success) { - zig_panic("CreateProcess failed. exe: %s command_line: %s", exe, buf_ptr(&command_line)); + zig_panic("CreateProcess failed. exe: %s command_line: %s", args.at(0), buf_ptr(&command_line)); } WaitForSingleObject(piProcInfo.hProcess, INFINITE); @@ -274,7 +282,7 @@ void os_path_join(Buf *dirname, Buf *basename, Buf *out_full_path) { Error os_path_real(Buf *rel_path, Buf *out_abs_path) { #if defined(ZIG_OS_WINDOWS) - PathSpace rel_path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(rel_path), buf_len(rel_path) }); + PathSpace rel_path_space = slice_to_prefixed_file_w(buf_to_slice(rel_path)); PathSpace out_abs_path_space; if (_wfullpath(&out_abs_path_space.data.items[0], &rel_path_space.data.items[0], PATH_MAX_WIDE) == nullptr) { @@ -780,7 +788,7 @@ Error os_fetch_file(FILE *f, Buf *out_buf) { Error os_file_exists(Buf *full_path, bool *result) { #if defined(ZIG_OS_WINDOWS) - PathSpace path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(full_path), buf_len(full_path) }); + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); *result = GetFileAttributesW(&path_space.data.items[0]) != INVALID_FILE_ATTRIBUTES; return ErrorNone; #else @@ -1338,8 +1346,8 @@ Error os_rename(Buf *src_path, Buf *dest_path) { return ErrorNone; } #if defined(ZIG_OS_WINDOWS) - PathSpace src_path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(src_path), buf_len(src_path) }); - PathSpace dest_path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(dest_path), buf_len(dest_path) }); + PathSpace src_path_space = slice_to_prefixed_file_w(buf_to_slice(src_path)); + PathSpace dest_path_space = slice_to_prefixed_file_w(buf_to_slice(dest_path)); if (!MoveFileExW(&src_path_space.data.items[0], &dest_path_space.data.items[0], MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { return ErrorFileSystem; } @@ -1436,7 +1444,14 @@ Error os_make_path(Buf *path) { Error os_make_dir(Buf *path) { #if defined(ZIG_OS_WINDOWS) - PathSpace path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(path), buf_len(path) }); + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(path)); + if (memEql(buf_to_slice(path), str("C:\\dev\\tést"))) { + for (size_t i = 0; i < path_space.len; i++) { + fprintf(stderr, "%d ", path_space.data.items[i]); + } + fprintf(stderr, "\n"); + } + if (!CreateDirectoryW(&path_space.data.items[0], NULL)) { if (GetLastError() == ERROR_ALREADY_EXISTS) return ErrorPathAlreadyExists; @@ -1727,22 +1742,11 @@ static uint8_t utf8CodepointSequenceLength(uint32_t c) { // Ported from std.unicode.utf8ByteSequenceLength static uint8_t utf8ByteSequenceLength(uint8_t first_byte) { - switch (clzll(~first_byte)) { - case 0: - return 1; - break; - case 2: - return 2; - break; - case 3: - return 3; - break; - case 4: - return 4; - break; - default: - zig_unreachable(); - } + if (first_byte < 0b10000000) return 1; + if ((first_byte & 0b11100000) == 0b11000000) return 2; + if ((first_byte & 0b11110000) == 0b11100000) return 3; + if ((first_byte & 0b11111000) == 0b11110000) return 4; + zig_unreachable(); } // Ported from std/unicode.zig @@ -2016,7 +2020,7 @@ Error os_self_exe_shared_libs(ZigList &paths) { Error os_file_open_rw(Buf *full_path, OsFile *out_file, OsFileAttr *attr, bool need_write, uint32_t mode) { #if defined(ZIG_OS_WINDOWS) - PathSpace path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(full_path), buf_len(full_path) }); + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); HANDLE result = CreateFileW(&path_space.data.items[0], need_write ? (GENERIC_READ|GENERIC_WRITE) : GENERIC_READ, need_write ? 0 : FILE_SHARE_READ, @@ -2121,7 +2125,7 @@ Error os_file_open_w(Buf *full_path, OsFile *out_file, OsFileAttr *attr, uint32_ Error os_file_open_lock_rw(Buf *full_path, OsFile *out_file) { #if defined(ZIG_OS_WINDOWS) - PathSpace path_space = slice_to_prefixed_file_w({ (uint8_t*)buf_ptr(full_path), buf_len(full_path) }); + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); for (;;) { HANDLE result = CreateFileW(&path_space.data.items[0], GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); -- 2.54.0 From 2c8a3aaf855475cabef4e8275581a12b06417a02 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 15 Jun 2020 22:21:01 +0300 Subject: [PATCH 011/295] Use _wfopen instead of fopen on windows --- src/os.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/os.cpp b/src/os.cpp index e363168579d1cd1cf12a3aebb0702ef63d849719..33d98fd41679c4f5cdd30e5e4b1941bb84e053f0 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -1037,7 +1037,12 @@ Error os_exec_process(ZigList &args, } Error os_write_file(Buf *full_path, Buf *contents) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); + FILE *f = _wfopen(&path_space.data.items[0], L"wb"); +#else FILE *f = fopen(buf_ptr(full_path), "wb"); +#endif if (!f) { zig_panic("os_write_file failed for %s", buf_ptr(full_path)); } @@ -1072,7 +1077,12 @@ static Error copy_open_files(FILE *src_f, FILE *dest_f) { Error os_dump_file(Buf *src_path, FILE *dest_file) { Error err; +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(src_path)); + FILE *src_f = _wfopen(&path_space.data.items[0], L"rb"); +#else FILE *src_f = fopen(buf_ptr(src_path), "rb"); +#endif if (!src_f) { int err = errno; if (err == ENOENT) { @@ -1189,7 +1199,12 @@ Error os_update_file(Buf *src_path, Buf *dst_path) { } Error os_copy_file(Buf *src_path, Buf *dest_path) { +#if defined(ZIG_OS_WINDOWS) + PathSpace src_path_space = slice_to_prefixed_file_w(buf_to_slice(src_path)); + FILE *src_f = _wfopen(&src_path_space.data.items[0], L"rb"); +#else FILE *src_f = fopen(buf_ptr(src_path), "rb"); +#endif if (!src_f) { int err = errno; if (err == ENOENT) { @@ -1200,7 +1215,12 @@ Error os_copy_file(Buf *src_path, Buf *dest_path) { return ErrorFileSystem; } } +#if defined(ZIG_OS_WINDOWS) + PathSpace dest_path_space = slice_to_prefixed_file_w(buf_to_slice(dest_path)); + FILE *dest_f = _wfopen(&dest_path_space.data.items[0], L"wb"); +#else FILE *dest_f = fopen(buf_ptr(dest_path), "wb"); +#endif if (!dest_f) { int err = errno; if (err == ENOENT) { @@ -1221,7 +1241,12 @@ Error os_copy_file(Buf *src_path, Buf *dest_path) { } Error os_fetch_file_path(Buf *full_path, Buf *out_contents) { +#if defined(ZIG_OS_WINDOWS) + PathSpace path_space = slice_to_prefixed_file_w(buf_to_slice(full_path)); + FILE *f = _wfopen(&path_space.data.items[0], L"rb"); +#else FILE *f = fopen(buf_ptr(full_path), "rb"); +#endif if (!f) { switch (errno) { case EACCES: -- 2.54.0 From 2bb3e1aff4976b2d04fb08a46d9221c77da0b204 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 15 Jun 2020 16:48:19 -0400 Subject: [PATCH 012/295] stage1: implement type coercion of anon struct literal to struct closes #3672 --- src/ir.cpp | 146 ++++++++++++++++++++++++++++++-- test/stage1/behavior/struct.zig | 33 ++++++++ 2 files changed, 171 insertions(+), 8 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 887f0a2f9b5fef8b50a451cf217fda4e5b304c26..d28648e128ba386e206afb2aac38cd3f21016423 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -286,6 +286,7 @@ static IrInstGen *ir_analyze_struct_value_field_value(IrAnalyze *ira, IrInst* so IrInstGen *struct_operand, TypeStructField *field); static bool value_cmp_numeric_val_any(ZigValue *left, Cmp predicate, ZigValue *right); static bool value_cmp_numeric_val_all(ZigValue *left, Cmp predicate, ZigValue *right); +static void memoize_field_init_val(CodeGen *codegen, ZigType *container_type, TypeStructField *field); #define ir_assert(OK, SOURCE_INSTRUCTION) ir_assert_impl((OK), (SOURCE_INSTRUCTION), __FILE__, __LINE__) #define ir_assert_gen(OK, SOURCE_INSTRUCTION) ir_assert_gen_impl((OK), (SOURCE_INSTRUCTION), __FILE__, __LINE__) @@ -14703,10 +14704,139 @@ static IrInstGen *ir_analyze_struct_literal_to_array(IrAnalyze *ira, IrInst* sou } static IrInstGen *ir_analyze_struct_literal_to_struct(IrAnalyze *ira, IrInst* source_instr, - IrInstGen *value, ZigType *wanted_type) + IrInstGen *struct_operand, ZigType *wanted_type) { - ir_add_error(ira, source_instr, buf_sprintf("TODO: type coercion of anon struct literal to struct")); - return ira->codegen->invalid_inst_gen; + Error err; + + IrInstGen *struct_ptr = ir_get_ref(ira, source_instr, struct_operand, true, false); + if (type_is_invalid(struct_ptr->value->type)) + return ira->codegen->invalid_inst_gen; + + if (wanted_type->data.structure.resolve_status == ResolveStatusBeingInferred) { + ir_add_error(ira, source_instr, buf_sprintf("type coercion of anon struct literal to inferred struct")); + return ira->codegen->invalid_inst_gen; + } + + if ((err = type_resolve(ira->codegen, wanted_type, ResolveStatusSizeKnown))) + return ira->codegen->invalid_inst_gen; + + size_t actual_field_count = wanted_type->data.structure.src_field_count; + size_t instr_field_count = struct_operand->value->type->data.structure.src_field_count; + + bool need_comptime = ir_should_inline(ira->old_irb.exec, source_instr->scope) + || type_requires_comptime(ira->codegen, wanted_type) == ReqCompTimeYes; + bool is_comptime = true; + + // Determine if the struct_operand will be comptime. + // Also emit compile errors for missing fields and duplicate fields. + AstNode **field_assign_nodes = heap::c_allocator.allocate(actual_field_count); + ZigValue **field_values = heap::c_allocator.allocate(actual_field_count); + IrInstGen **casted_fields = heap::c_allocator.allocate(actual_field_count); + IrInstGen *const_result = ir_const(ira, source_instr, wanted_type); + + for (size_t i = 0; i < instr_field_count; i += 1) { + TypeStructField *src_field = struct_operand->value->type->data.structure.fields[i]; + TypeStructField *dst_field = find_struct_type_field(wanted_type, src_field->name); + if (dst_field == nullptr) { + ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("no field named '%s' in struct '%s'", + buf_ptr(src_field->name), buf_ptr(&wanted_type->name))); + if (wanted_type->data.structure.decl_node) { + add_error_note(ira->codegen, msg, wanted_type->data.structure.decl_node, + buf_sprintf("struct '%s' declared here", buf_ptr(&wanted_type->name))); + } + add_error_note(ira->codegen, msg, src_field->decl_node, + buf_sprintf("field '%s' declared here", buf_ptr(src_field->name))); + return ira->codegen->invalid_inst_gen; + } + + ir_assert(src_field->decl_node != nullptr, source_instr); + AstNode *existing_assign_node = field_assign_nodes[dst_field->src_index]; + if (existing_assign_node != nullptr) { + ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("duplicate field")); + add_error_note(ira->codegen, msg, existing_assign_node, buf_sprintf("other field here")); + return ira->codegen->invalid_inst_gen; + } + field_assign_nodes[dst_field->src_index] = src_field->decl_node; + + IrInstGen *field_ptr = ir_analyze_struct_field_ptr(ira, source_instr, src_field, struct_ptr, + struct_operand->value->type, false); + if (type_is_invalid(field_ptr->value->type)) + return ira->codegen->invalid_inst_gen; + IrInstGen *field_value = ir_get_deref(ira, source_instr, field_ptr, nullptr); + if (type_is_invalid(field_value->value->type)) + return ira->codegen->invalid_inst_gen; + IrInstGen *casted_value = ir_implicit_cast(ira, field_value, dst_field->type_entry); + if (type_is_invalid(casted_value->value->type)) + return ira->codegen->invalid_inst_gen; + + casted_fields[dst_field->src_index] = casted_value; + if (need_comptime || instr_is_comptime(casted_value)) { + ZigValue *field_val = ir_resolve_const(ira, casted_value, UndefOk); + if (field_val == nullptr) + return ira->codegen->invalid_inst_gen; + field_val->parent.id = ConstParentIdStruct; + field_val->parent.data.p_struct.struct_val = const_result->value; + field_val->parent.data.p_struct.field_index = dst_field->src_index; + field_values[dst_field->src_index] = field_val; + } else { + is_comptime = false; + } + } + + bool any_missing = false; + for (size_t i = 0; i < actual_field_count; i += 1) { + if (field_assign_nodes[i] != nullptr) continue; + + // look for a default field value + TypeStructField *field = wanted_type->data.structure.fields[i]; + memoize_field_init_val(ira->codegen, wanted_type, field); + if (field->init_val == nullptr) { + ir_add_error(ira, source_instr, + buf_sprintf("missing field: '%s'", buf_ptr(field->name))); + any_missing = true; + continue; + } + if (type_is_invalid(field->init_val->type)) + return ira->codegen->invalid_inst_gen; + ZigValue *init_val_copy = ira->codegen->pass1_arena->create(); + copy_const_val(ira->codegen, init_val_copy, field->init_val); + init_val_copy->parent.id = ConstParentIdStruct; + init_val_copy->parent.data.p_struct.struct_val = const_result->value; + init_val_copy->parent.data.p_struct.field_index = i; + field_values[i] = init_val_copy; + casted_fields[i] = ir_const_move(ira, source_instr, init_val_copy); + } + if (any_missing) + return ira->codegen->invalid_inst_gen; + + if (is_comptime) { + heap::c_allocator.deallocate(field_assign_nodes, actual_field_count); + IrInstGen *const_result = ir_const(ira, source_instr, wanted_type); + const_result->value->data.x_struct.fields = field_values; + return const_result; + } + + IrInstGen *result_loc_inst = ir_resolve_result(ira, source_instr, no_result_loc(), + wanted_type, nullptr, true, true); + if (type_is_invalid(result_loc_inst->value->type) || result_loc_inst->value->type->id == ZigTypeIdUnreachable) { + return ira->codegen->invalid_inst_gen; + } + + for (size_t i = 0; i < actual_field_count; i += 1) { + TypeStructField *field = wanted_type->data.structure.fields[i]; + IrInstGen *field_ptr = ir_analyze_struct_field_ptr(ira, source_instr, field, result_loc_inst, wanted_type, true); + if (type_is_invalid(field_ptr->value->type)) + return ira->codegen->invalid_inst_gen; + IrInstGen *store_ptr_inst = ir_analyze_store_ptr(ira, source_instr, field_ptr, casted_fields[i], true); + if (type_is_invalid(store_ptr_inst->value->type)) + return ira->codegen->invalid_inst_gen; + } + + heap::c_allocator.deallocate(field_assign_nodes, actual_field_count); + heap::c_allocator.deallocate(field_values, actual_field_count); + heap::c_allocator.deallocate(casted_fields, actual_field_count); + + return ir_get_deref(ira, source_instr, result_loc_inst, nullptr); } static IrInstGen *ir_analyze_struct_literal_to_union(IrAnalyze *ira, IrInst* source_instr, @@ -14727,7 +14857,7 @@ static IrInstGen *ir_analyze_struct_literal_to_union(IrAnalyze *ira, IrInst* sou TypeUnionField *union_field = find_union_type_field(union_type, only_field->name); if (union_field == nullptr) { ir_add_error_node(ira, only_field->decl_node, - buf_sprintf("no member named '%s' in union '%s'", + buf_sprintf("no field named '%s' in union '%s'", buf_ptr(only_field->name), buf_ptr(&union_type->name))); return ira->codegen->invalid_inst_gen; } @@ -22407,7 +22537,7 @@ static IrInstGen *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstSrcFiel usize, ConstPtrMutComptimeConst, ptr_is_const, ptr_is_volatile, 0); } else { ir_add_error_node(ira, source_node, - buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), + buf_sprintf("no field named '%s' in '%s'", buf_ptr(field_name), buf_ptr(&container_type->name))); return ira->codegen->invalid_inst_gen; } @@ -23834,7 +23964,7 @@ static IrInstGen *ir_analyze_union_init(IrAnalyze *ira, IrInst* source_instructi TypeUnionField *type_field = find_union_type_field(union_type, field_name); if (type_field == nullptr) { ir_add_error_node(ira, field_source_node, - buf_sprintf("no member named '%s' in union '%s'", + buf_sprintf("no field named '%s' in union '%s'", buf_ptr(field_name), buf_ptr(&union_type->name))); return ira->codegen->invalid_inst_gen; } @@ -23930,7 +24060,7 @@ static IrInstGen *ir_analyze_container_init_fields(IrAnalyze *ira, IrInst *sourc TypeStructField *type_field = find_struct_type_field(container_type, field->name); if (!type_field) { ir_add_error_node(ira, field->source_node, - buf_sprintf("no member named '%s' in struct '%s'", + buf_sprintf("no field named '%s' in struct '%s'", buf_ptr(field->name), buf_ptr(&container_type->name))); return ira->codegen->invalid_inst_gen; } @@ -23965,7 +24095,7 @@ static IrInstGen *ir_analyze_container_init_fields(IrAnalyze *ira, IrInst *sourc memoize_field_init_val(ira->codegen, container_type, field); if (field->init_val == nullptr) { ir_add_error(ira, source_instr, - buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i]->name))); + buf_sprintf("missing field: '%s'", buf_ptr(field->name))); any_missing = true; continue; } diff --git a/test/stage1/behavior/struct.zig b/test/stage1/behavior/struct.zig index 3a4e0a4d9380e003030b933ba9bf4bab90a51e10..7b8690d604edc4e9f3492e0d33861bad41f74dd7 100644 --- a/test/stage1/behavior/struct.zig +++ b/test/stage1/behavior/struct.zig @@ -851,3 +851,36 @@ test "struct with union field" { expectEqual(@as(u32, 2), True.ref); expectEqual(true, True.kind.Bool); } + +test "type coercion of anon struct literal to struct" { + const S = struct { + const S2 = struct { + A: u32, + B: []const u8, + C: void, + D: Foo = .{}, + }; + + const Foo = struct { + field: i32 = 1234, + }; + + fn doTheTest() void { + var y: u32 = 42; + const t0 = .{ .A = 123, .B = "foo", .C = {} }; + const t1 = .{ .A = y, .B = "foo", .C = {} }; + const y0: S2 = t0; + var y1: S2 = t1; + expect(y0.A == 123); + expect(std.mem.eql(u8, y0.B, "foo")); + expect(y0.C == {}); + expect(y0.D.field == 1234); + expect(y1.A == y); + expect(std.mem.eql(u8, y1.B, "foo")); + expect(y1.C == {}); + expect(y1.D.field == 1234); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} -- 2.54.0 From c92816fbefe9d789d3f3c13f319c949c8b97ad01 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 17:51:29 -0400 Subject: [PATCH 013/295] [Stage2/Testing] ZIR tests for expected errors --- src-self-hosted/test.zig | 142 ++++++++++++++++++++++++++++++--- test/stage2/compile_errors.zig | 21 +++++ 2 files changed, 154 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 605c973bb906bd8f25193f4f954620190ccb33b1..21d35720891b6a05b2f280fe416178427c3353ae 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -1,6 +1,7 @@ const std = @import("std"); const link = @import("link.zig"); const Module = @import("Module.zig"); +const ErrorMsg = Module.ErrorMsg; const Allocator = std.mem.Allocator; const zir = @import("zir.zig"); const Package = @import("Package.zig"); @@ -18,6 +19,7 @@ test "self-hosted" { pub const TestContext = struct { zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), zir_transform_cases: std.ArrayList(ZIRTransformCase), + zir_error_cases: std.ArrayList(ZIRErrorCase), pub const ZIRCompareOutputCase = struct { name: []const u8, @@ -55,6 +57,15 @@ pub const TestContext = struct { } }; + pub const ZIRErrorCase = struct { + name: []const u8, + src: [:0]const u8, + expected_file_errors: []const ErrorMsg, + expected_decl_errors: []const ErrorMsg, + expected_export_errors: []const ErrorMsg, + cross_target: std.zig.CrossTarget, + }; + pub fn addZIRCompareOutput( ctx: *TestContext, name: []const u8, @@ -87,30 +98,38 @@ pub const TestContext = struct { }) catch unreachable; } - pub fn addZIRMulti( + pub fn addZIRError( ctx: *TestContext, name: []const u8, cross_target: std.zig.CrossTarget, - ) *ZIRTransformCase { - const case = ctx.zir_transform_cases.addOne() catch unreachable; - case.* = .{ + src: [:0]const u8, + expected_file_errors: []const ErrorMsg, + expected_decl_errors: []const ErrorMsg, + expected_export_errors: []const ErrorMsg, + ) void { + ctx.zir_error_cases.append(.{ .name = name, + .src = src, + .expected_file_errors = expected_file_errors, + .expected_decl_errors = expected_decl_errors, + .expected_export_errors = expected_export_errors, .cross_target = cross_target, - .updates = std.ArrayList(ZIRTransformCase.Update).init(std.heap.page_allocator), - }; - return case; + }) catch unreachable; } fn init(self: *TestContext) !void { + const allocator = std.heap.page_allocator; self.* = .{ - .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(std.heap.page_allocator), - .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(std.heap.page_allocator), + .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), + .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(allocator), + .zir_error_cases = std.ArrayList(ZIRErrorCase).init(allocator), }; } fn deinit(self: *TestContext) void { self.zir_cmp_output_cases.deinit(); self.zir_transform_cases.deinit(); + self.zir_error_cases.deinit(); self.* = undefined; } @@ -133,6 +152,12 @@ pub const TestContext = struct { try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, info.target); try std.testing.allocator_instance.validate(); } + for (self.zir_error_cases.items) |case| { + std.testing.base_allocator_instance.reset(); + const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); + try self.runOneZIRErrorCase(std.testing.allocator, root_node, case, info.target); + try std.testing.allocator_instance.validate(); + } } fn runOneZIRCmpOutputCase( @@ -300,6 +325,105 @@ pub const TestContext = struct { } } } + + fn runOneZIRErrorCase( + self: *TestContext, + allocator: *Allocator, + root_node: *std.Progress.Node, + case: ZIRErrorCase, + target: std.Target, + ) !void { + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + var prg_node = root_node.start(case.name, 1); + prg_node.activate(); + defer prg_node.end(); + + const tmp_src_path = "test-case.zir"; + try tmp.dir.writeFile(tmp_src_path, case.src); + + const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); + defer root_pkg.destroy(); + + var module = try Module.init(allocator, .{ + .target = target, + .output_mode = .Obj, + .optimize_mode = .Debug, + .bin_file_dir = tmp.dir, + .bin_file_path = "test-case.o", + .root_pkg = root_pkg, + }); + defer module.deinit(); + + var module_node = prg_node.start("parse/analysis/codegen", null); + module_node.activate(); + const failed = f: { + module.update() catch break :f true; + break :f false; + }; + if (!failed) { + return error.DidNotFail; + } + module_node.end(); + { + var i = module.failed_files.iterator(); + var index: usize = 0; + while (i.next()) |pair| : (index += 1) { + if (index == case.expected_file_errors.len) { + return error.UnexpectedError; + } + const v1 = pair.value.*; + const v2 = case.expected_file_errors[index]; + if (v1.byte_offset != v2.byte_offset) { + std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); + return error.ExpectedErrorElsewhere; + } + if (!std.mem.eql(u8, v1.msg, v2.msg)) { + std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); + return error.ExpectedOtherError; + } + } + } + { + var i = module.failed_decls.iterator(); + var index: usize = 0; + while (i.next()) |pair| : (index += 1) { + if (index == case.expected_decl_errors.len) { + return error.UnexpectedError; + } + const v1 = pair.value.*; + const v2 = case.expected_decl_errors[index]; + if (v1.byte_offset != v2.byte_offset) { + std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); + return error.ExpectedErrorElsewhere; + } + if (!std.mem.eql(u8, v1.msg, v2.msg)) { + std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); + return error.ExpectedOtherError; + } + } + } + { + var i = module.failed_exports.iterator(); + var index: usize = 0; + while (i.next()) |pair| : (index += 1) { + if (index == case.expected_export_errors.len) { + return error.UnexpectedError; + } + const v1 = pair.value.*; + const v2 = case.expected_export_errors[index]; + if (v1.byte_offset != v2.byte_offset) { + std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); + return error.ExpectedErrorElsewhere; + } + if (!std.mem.eql(u8, v1.msg, v2.msg)) { + std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); + return error.ExpectedOtherError; + } + } + } + } }; fn debugPrintErrors(src: []const u8, errors: var) void { diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 9b8dcd91c47195cd26b817f91865b0f8bb55ac7f..e3c009eee6165ea034eac9403cd7fea2fa1481d0 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -1,8 +1,29 @@ const TestContext = @import("../../src-self-hosted/test.zig").TestContext; +const std = @import("std"); + +const ErrorMsg = @import("../../src-self-hosted/Module.zig").ErrorMsg; + +const linux_x64 = std.zig.CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .linux, +}; pub fn addCases(ctx: *TestContext) !void { // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 + ctx.addZIRError("test", linux_x64, + \\@noreturn = primitive(noreturn) + \\@void = primitive(void) + \\@usize = primitive(usize) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %0 = call(%test, []) + \\}) + , &[_]ErrorMsg{.{ + .byte_offset = 168, + .msg = "unrecognized identifier: %test", + }}, &[_]ErrorMsg{}, &[_]ErrorMsg{}); //try ctx.testCompileError( // \\export fn entry() void {} -- 2.54.0 From 67414be86b1e33f8eda4e3c33a60a7e59098a55f Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sun, 24 May 2020 09:30:52 -0400 Subject: [PATCH 014/295] [Stage2/Testing] Print name of failed test --- src-self-hosted/test.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 21d35720891b6a05b2f280fe416178427c3353ae..9aea61281be1bad9169600dec511f52efd97a63c 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -363,6 +363,7 @@ pub const TestContext = struct { break :f false; }; if (!failed) { + std.debug.warn("Test '{}' compilation succeded, error expected.\n", .{case.name}); return error.DidNotFail; } module_node.end(); -- 2.54.0 From f2399db3ef3cc3951210f7047ddd688f17f7f59c Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sun, 24 May 2020 12:30:04 -0400 Subject: [PATCH 015/295] [Stage2/Testing] Don't rely on update erroring --- src-self-hosted/test.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 9aea61281be1bad9169600dec511f52efd97a63c..102e6caeff249d5537a9ea68dc7e59abfae0ea97 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -362,16 +362,13 @@ pub const TestContext = struct { module.update() catch break :f true; break :f false; }; - if (!failed) { - std.debug.warn("Test '{}' compilation succeded, error expected.\n", .{case.name}); - return error.DidNotFail; - } module_node.end(); { var i = module.failed_files.iterator(); var index: usize = 0; while (i.next()) |pair| : (index += 1) { if (index == case.expected_file_errors.len) { + std.debug.warn("Unexpected file error: {}\n", .{pair.value}); return error.UnexpectedError; } const v1 = pair.value.*; @@ -391,6 +388,7 @@ pub const TestContext = struct { var index: usize = 0; while (i.next()) |pair| : (index += 1) { if (index == case.expected_decl_errors.len) { + std.debug.warn("Unexpected decl error: {}\n", .{pair.value}); return error.UnexpectedError; } const v1 = pair.value.*; @@ -410,6 +408,7 @@ pub const TestContext = struct { var index: usize = 0; while (i.next()) |pair| : (index += 1) { if (index == case.expected_export_errors.len) { + std.debug.warn("Unexpected export error: {}\n", .{pair.value}); return error.UnexpectedError; } const v1 = pair.value.*; -- 2.54.0 From e030414c163c6f9a3c2fc8291b2d5917e78688de Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 25 May 2020 16:15:39 -0400 Subject: [PATCH 016/295] [Stage2/Testing] Always finish case, note all errs --- src-self-hosted/test.zig | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 102e6caeff249d5537a9ea68dc7e59abfae0ea97..b787d371ed3369f604cf0b54426750b753e3254e 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -363,25 +363,30 @@ pub const TestContext = struct { break :f false; }; module_node.end(); + var err: ?anyerror = null; { var i = module.failed_files.iterator(); var index: usize = 0; while (i.next()) |pair| : (index += 1) { if (index == case.expected_file_errors.len) { std.debug.warn("Unexpected file error: {}\n", .{pair.value}); - return error.UnexpectedError; + err = error.UnexpectedError; } const v1 = pair.value.*; const v2 = case.expected_file_errors[index]; if (v1.byte_offset != v2.byte_offset) { std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - return error.ExpectedErrorElsewhere; + err = error.ExpectedErrorElsewhere; } if (!std.mem.eql(u8, v1.msg, v2.msg)) { std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - return error.ExpectedOtherError; + err = error.ExpectedOtherError; } } + if (index != case.expected_file_errors.len) { + std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_file_errors[index]}); + err = error.MissingError; + } } { var i = module.failed_decls.iterator(); @@ -389,19 +394,23 @@ pub const TestContext = struct { while (i.next()) |pair| : (index += 1) { if (index == case.expected_decl_errors.len) { std.debug.warn("Unexpected decl error: {}\n", .{pair.value}); - return error.UnexpectedError; + err = error.UnexpectedError; } const v1 = pair.value.*; const v2 = case.expected_decl_errors[index]; if (v1.byte_offset != v2.byte_offset) { std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - return error.ExpectedErrorElsewhere; + err = error.ExpectedErrorElsewhere; } if (!std.mem.eql(u8, v1.msg, v2.msg)) { std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - return error.ExpectedOtherError; + err = error.ExpectedOtherError; } } + if (index != case.expected_decl_errors.len) { + std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_decl_errors[index]}); + err = error.MissingError; + } } { var i = module.failed_exports.iterator(); @@ -409,19 +418,26 @@ pub const TestContext = struct { while (i.next()) |pair| : (index += 1) { if (index == case.expected_export_errors.len) { std.debug.warn("Unexpected export error: {}\n", .{pair.value}); - return error.UnexpectedError; + err = error.UnexpectedError; } const v1 = pair.value.*; const v2 = case.expected_export_errors[index]; if (v1.byte_offset != v2.byte_offset) { std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - return error.ExpectedErrorElsewhere; + err = error.ExpectedErrorElsewhere; } if (!std.mem.eql(u8, v1.msg, v2.msg)) { std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - return error.ExpectedOtherError; + err = error.ExpectedOtherError; } } + if (index != case.expected_export_errors.len) { + std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_export_errors[index]}); + err = error.MissingError; + } + } + if (err) |e| { + return e; } } }; -- 2.54.0 From 2d1d012f1194e1e26f123891e12921d59ffe9e10 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 25 May 2020 16:26:51 -0400 Subject: [PATCH 017/295] [Stage2/Testing] Reduce test --- test/stage2/compile_errors.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index e3c009eee6165ea034eac9403cd7fea2fa1481d0..0133e5547725041d5d4fa083645ee7841fa5e426 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -13,15 +13,13 @@ pub fn addCases(ctx: *TestContext) !void { // https://github.com/ziglang/zig/issues/1364 ctx.addZIRError("test", linux_x64, \\@noreturn = primitive(noreturn) - \\@void = primitive(void) - \\@usize = primitive(usize) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) , &[_]ErrorMsg{.{ - .byte_offset = 168, + .byte_offset = 118, .msg = "unrecognized identifier: %test", }}, &[_]ErrorMsg{}, &[_]ErrorMsg{}); -- 2.54.0 From bebc1f49cf2e480fbd5dc2a409ee1bc0fe3de331 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 27 May 2020 09:20:06 -0400 Subject: [PATCH 018/295] [Stage2/Testing] Add (failing) test --- test/stage2/compile_errors.zig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 0133e5547725041d5d4fa083645ee7841fa5e426..89761736d97f0547acb4fcba85db81d3ca17cb42 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -23,6 +23,20 @@ pub fn addCases(ctx: *TestContext) !void { .msg = "unrecognized identifier: %test", }}, &[_]ErrorMsg{}, &[_]ErrorMsg{}); + ctx.addZIRError("call with non-existent target", linux_x64, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %0 = call(@notafunc, []) + \\}) + , &[_]ErrorMsg{ + .{ + .byte_offset = 118, + .msg = "unrecognized identifier: @notafunc", + }, + }, &[_]ErrorMsg{}, &[_]ErrorMsg{}); + //try ctx.testCompileError( // \\export fn entry() void {} // \\export fn entry() void {} -- 2.54.0 From 68cc068a3aa5df8bd1995d10a9133af1fa1ea1d3 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 27 May 2020 11:51:48 -0400 Subject: [PATCH 019/295] [Stage2/Testing] Make API more friendly --- src-self-hosted/test.zig | 161 ++++++++++++++++++--------------- test/stage2/compile_errors.zig | 26 ++---- 2 files changed, 98 insertions(+), 89 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index b787d371ed3369f604cf0b54426750b753e3254e..a2fcddb7e1d04d508e942ed08f803f6d4a84c88f 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -6,6 +6,24 @@ const Allocator = std.mem.Allocator; const zir = @import("zir.zig"); const Package = @import("Package.zig"); +test "find-offset" { + std.testing.expectEqual(findOffset("hello123", 1, 8), 7); + const testmsg = + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %0 = call(@notafunc, []) + \\}) + ; + std.testing.expectEqual(findOffset(testmsg, 2, 1), 32); + std.testing.expectEqual(findOffset(testmsg, 3, 1), 33); + std.testing.expectEqual(findOffset(testmsg, 3, 10), 42); + std.testing.expectEqual(findOffset(testmsg, 4, 1), 79); + std.testing.expectEqual(findOffset(testmsg, 5, 1), 106); + std.testing.expectEqual(findOffset(testmsg, 5, 13), 118); +} + test "self-hosted" { var ctx: TestContext = undefined; try ctx.init(); @@ -16,6 +34,27 @@ test "self-hosted" { try ctx.run(); } +/// Finds the raw byte offset of line:column in src. This is not a performant implementation, +/// as it should only ever be called rarely and it is better to focus on readability. +fn findOffset(src: []const u8, line: usize, column: usize) ?usize { + // "0000000001" + // 1:10 + // + var current_line: usize = 1; + var current_column: usize = 1; + for (src) |char, index| { + if (current_line == line and current_column == column) { + return index; + } + if (char == '\n') { + current_line += 1; + current_column = 0; + } + current_column += 1; + } + return null; +} + pub const TestContext = struct { zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), zir_transform_cases: std.ArrayList(ZIRTransformCase), @@ -60,9 +99,7 @@ pub const TestContext = struct { pub const ZIRErrorCase = struct { name: []const u8, src: [:0]const u8, - expected_file_errors: []const ErrorMsg, - expected_decl_errors: []const ErrorMsg, - expected_export_errors: []const ErrorMsg, + expected_errors: []const ErrorMsg, cross_target: std.zig.CrossTarget, }; @@ -103,16 +140,31 @@ pub const TestContext = struct { name: []const u8, cross_target: std.zig.CrossTarget, src: [:0]const u8, - expected_file_errors: []const ErrorMsg, - expected_decl_errors: []const ErrorMsg, - expected_export_errors: []const ErrorMsg, + expected_errors: []const []const u8, ) void { + var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator); + for (expected_errors) |e| { + const line_index = std.mem.indexOf(u8, e, ":"); + if (line_index == null) { + std.debug.panic("Invalid test: error must be specified as 'line:column:msg', found '{}'", .{e}); + } + const column_index = std.mem.indexOf(u8, e[line_index.? + 1 ..], ":"); + if (column_index == null) { + std.debug.panic("Invalid test: error must be specified as 'line:column:msg', found '{}'", .{e}); + } + const line = std.fmt.parseInt(usize, e[0..line_index.?], 10) catch @panic("Unable to parse line number"); + const column = std.fmt.parseInt(usize, e[line_index.? + 1 ..][0..column_index.?], 10) catch @panic("Unable to parse column number"); + const msg = e[line_index.? + 1 ..][column_index.? + 1 ..]; + const offset = findOffset(src, line, column) orelse std.debug.panic("Unable to match {}:{} to byte offset!", .{ line, column }); + array.append(ErrorMsg{ + .byte_offset = offset, + .msg = msg, + }) catch unreachable; + } ctx.zir_error_cases.append(.{ .name = name, .src = src, - .expected_file_errors = expected_file_errors, - .expected_decl_errors = expected_decl_errors, - .expected_export_errors = expected_export_errors, + .expected_errors = array.toOwnedSlice(), .cross_target = cross_target, }) catch unreachable; } @@ -129,6 +181,9 @@ pub const TestContext = struct { fn deinit(self: *TestContext) void { self.zir_cmp_output_cases.deinit(); self.zir_transform_cases.deinit(); + for (self.zir_error_cases.items) |e| { + self.zir_error_cases.allocator.free(e.expected_errors); + } self.zir_error_cases.deinit(); self.* = undefined; } @@ -364,78 +419,40 @@ pub const TestContext = struct { }; module_node.end(); var err: ?anyerror = null; + + var handled_errors = allocator.alloc(bool, case.expected_errors.len) catch unreachable; + defer allocator.free(handled_errors); + for (handled_errors) |*e| { + e.* = false; + } + { var i = module.failed_files.iterator(); - var index: usize = 0; - while (i.next()) |pair| : (index += 1) { - if (index == case.expected_file_errors.len) { - std.debug.warn("Unexpected file error: {}\n", .{pair.value}); - err = error.UnexpectedError; - } + while (i.next()) |pair| { const v1 = pair.value.*; - const v2 = case.expected_file_errors[index]; - if (v1.byte_offset != v2.byte_offset) { - std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - err = error.ExpectedErrorElsewhere; - } - if (!std.mem.eql(u8, v1.msg, v2.msg)) { - std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - err = error.ExpectedOtherError; - } - } - if (index != case.expected_file_errors.len) { - std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_file_errors[index]}); - err = error.MissingError; - } - } - { - var i = module.failed_decls.iterator(); - var index: usize = 0; - while (i.next()) |pair| : (index += 1) { - if (index == case.expected_decl_errors.len) { - std.debug.warn("Unexpected decl error: {}\n", .{pair.value}); + var handled = false; + for (case.expected_errors) |e, index| { + if (!handled_errors[index]) { + if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { + handled_errors[index] = true; + handled = true; + break; + } + } + } + if (!handled) { err = error.UnexpectedError; + std.debug.warn("Unexpected file error: {}\n", .{v1}); } - const v1 = pair.value.*; - const v2 = case.expected_decl_errors[index]; - if (v1.byte_offset != v2.byte_offset) { - std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - err = error.ExpectedErrorElsewhere; - } - if (!std.mem.eql(u8, v1.msg, v2.msg)) { - std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - err = error.ExpectedOtherError; - } - } - if (index != case.expected_decl_errors.len) { - std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_decl_errors[index]}); - err = error.MissingError; } } - { - var i = module.failed_exports.iterator(); - var index: usize = 0; - while (i.next()) |pair| : (index += 1) { - if (index == case.expected_export_errors.len) { - std.debug.warn("Unexpected export error: {}\n", .{pair.value}); - err = error.UnexpectedError; - } - const v1 = pair.value.*; - const v2 = case.expected_export_errors[index]; - if (v1.byte_offset != v2.byte_offset) { - std.debug.warn("Expected error at {}, found it at {}\n", .{ v2.byte_offset, v1.byte_offset }); - err = error.ExpectedErrorElsewhere; - } - if (!std.mem.eql(u8, v1.msg, v2.msg)) { - std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - err = error.ExpectedOtherError; - } - } - if (index != case.expected_export_errors.len) { - std.debug.warn("Expected an error ('{}'), but did not receive it\n", .{case.expected_export_errors[index]}); - err = error.MissingError; + for (handled_errors) |e, i| { + if (!e) { + err = error.MissingExpectedError; + std.debug.warn("Did not receive error: {}\n", .{case.expected_errors[i].msg}); } } + if (err) |e| { return e; } diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 89761736d97f0547acb4fcba85db81d3ca17cb42..50bfea52105d1cc852a387f1fd172962aa39e989 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,24 +18,16 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_]ErrorMsg{.{ - .byte_offset = 118, - .msg = "unrecognized identifier: %test", - }}, &[_]ErrorMsg{}, &[_]ErrorMsg{}); + , &[_][]const u8{"5:13:unrecognized identifier: %test"}); - ctx.addZIRError("call with non-existent target", linux_x64, - \\@noreturn = primitive(noreturn) - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %0 = call(@notafunc, []) - \\}) - , &[_]ErrorMsg{ - .{ - .byte_offset = 118, - .msg = "unrecognized identifier: @notafunc", - }, - }, &[_]ErrorMsg{}, &[_]ErrorMsg{}); + // ctx.addZIRError("call with non-existent target", linux_x64, + // \\@noreturn = primitive(noreturn) + // \\ + // \\@start_fnty = fntype([], @noreturn, cc=Naked) + // \\@start = fn(@start_fnty, { + // \\ %0 = call(@notafunc, []) + // \\}) + // , &[_][]const u8{"5:13:unrecognized identifier: @notafunc"}); //try ctx.testCompileError( // \\export fn entry() void {} -- 2.54.0 From bf8b3a4394be6b1b512531cb5217147d8bead180 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 27 May 2020 11:54:04 -0400 Subject: [PATCH 020/295] [Stage2/Testing] Handle decl and export errors --- src-self-hosted/test.zig | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index a2fcddb7e1d04d508e942ed08f803f6d4a84c88f..2c47a4743dc796f74c8049b353ef2c5a57728f95 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -446,6 +446,46 @@ pub const TestContext = struct { } } } + { + var i = module.failed_decls.iterator(); + while (i.next()) |pair| { + const v1 = pair.value.*; + var handled = false; + for (case.expected_errors) |e, index| { + if (!handled_errors[index]) { + if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { + handled_errors[index] = true; + handled = true; + break; + } + } + } + if (!handled) { + err = error.UnexpectedError; + std.debug.warn("Unexpected decl error: {}\n", .{v1}); + } + } + } + { + var i = module.failed_exports.iterator(); + while (i.next()) |pair| { + const v1 = pair.value.*; + var handled = false; + for (case.expected_errors) |e, index| { + if (!handled_errors[index]) { + if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { + handled_errors[index] = true; + handled = true; + break; + } + } + } + if (!handled) { + err = error.UnexpectedError; + std.debug.warn("Unexpected export error: {}\n", .{v1}); + } + } + } for (handled_errors) |e, i| { if (!e) { err = error.MissingExpectedError; -- 2.54.0 From 2ed07a36f8b225d4abd531e80b73cf97e51ae0bb Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 27 May 2020 14:09:31 -0400 Subject: [PATCH 021/295] [Stage2/Testing] Attempt to call nakedcc function --- test/stage2/compile_errors.zig | 36 +++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 50bfea52105d1cc852a387f1fd172962aa39e989..865ececf18e8806ce6621aa451e5e5e64478d054 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -11,7 +11,7 @@ const linux_x64 = std.zig.CrossTarget{ pub fn addCases(ctx: *TestContext) !void { // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 - ctx.addZIRError("test", linux_x64, + ctx.addZIRError("call undefined local", linux_x64, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -20,14 +20,32 @@ pub fn addCases(ctx: *TestContext) !void { \\}) , &[_][]const u8{"5:13:unrecognized identifier: %test"}); - // ctx.addZIRError("call with non-existent target", linux_x64, - // \\@noreturn = primitive(noreturn) - // \\ - // \\@start_fnty = fntype([], @noreturn, cc=Naked) - // \\@start = fn(@start_fnty, { - // \\ %0 = call(@notafunc, []) - // \\}) - // , &[_][]const u8{"5:13:unrecognized identifier: @notafunc"}); + // TODO: fix this test + // ctx.addZIRError("call with non-existent target", linux_x64, + // \\@noreturn = primitive(noreturn) + // \\ + // \\@start_fnty = fntype([], @noreturn, cc=Naked) + // \\@start = fn(@start_fnty, { + // \\ %0 = call(@notafunc, []) + // \\}) + // \\@0 = str("_start") + // \\@1 = ref(@0) + // \\@2 = export(@1, @start) + // , &[_][]const u8{"5:13:unrecognized identifier: @notafunc"}); + + // TODO: this error should occur at the call site, not the fntype decl + ctx.addZIRError("call naked function", linux_x64, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@s = fn(@start_fnty, {}) + \\@start = fn(@start_fnty, { + \\ %0 = call(@s, []) + \\}) + \\@0 = str("_start") + \\@1 = ref(@0) + \\@2 = export(@1, @start) + , &[_][]const u8{"4:9:unable to call function with naked calling convention"}); //try ctx.testCompileError( // \\export fn entry() void {} -- 2.54.0 From d4fd7c6a014f4ba41f2231b58d090d72179a5bdb Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 2 Jun 2020 15:29:59 -0400 Subject: [PATCH 022/295] Stage2/Testing: Staged test harness draft design --- src-self-hosted/test.zig | 170 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 2c47a4743dc796f74c8049b353ef2c5a57728f95..bde4a804e5436a8f9d74a8af0d6a4a90848a19e7 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -56,16 +56,27 @@ fn findOffset(src: []const u8, line: usize, column: usize) ?usize { } pub const TestContext = struct { + // TODO: remove these. They are deprecated. zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), + // TODO: remove zir_transform_cases: std.ArrayList(ZIRTransformCase), + // TODO: remove zir_error_cases: std.ArrayList(ZIRErrorCase), + /// TODO: find a way to treat cases as individual tests as far as + /// `zig test` is concerned. If we have 100 tests, they should *not* be + /// considered as *one*. "ZIR" isn't really a *test*, it's a *category* of + /// tests. + zir_cases: std.ArrayList(ZIRCase), + + // TODO: remove pub const ZIRCompareOutputCase = struct { name: []const u8, src_list: []const []const u8, expected_stdout_list: []const []const u8, }; + // TODO: remove pub const ZIRTransformCase = struct { name: []const u8, cross_target: std.zig.CrossTarget, @@ -96,6 +107,7 @@ pub const TestContext = struct { } }; + // TODO: remove pub const ZIRErrorCase = struct { name: []const u8, src: [:0]const u8, @@ -103,6 +115,83 @@ pub const TestContext = struct { cross_target: std.zig.CrossTarget, }; + pub const ZIRStageType = enum { + /// A transformation stage transforms the input ZIR and tests against + /// the expected output + Transformation, + /// An error stage attempts to compile bad code, and ensures that it + /// fails to compile, and for the expected reasons + Error, + /// An execution stage compiles and runs the input ZIR, feeding in + /// provided input and ensuring that the outputs match what is expected + Execution, + /// A compilation stage checks that the ZIR compiles without any issues + Compiles, + }; + + pub const ZIRStage = struct { + /// The input to the current stage. We simulate an incremental update + /// with the file's contents changed to this value each stage. + /// + /// This value can change entirely between stages, which would be akin + /// to deleting the source file and creating a new one from scratch; or + /// you can keep it mostly consistent, with small changes, testing the + /// effects of the incremental compilation. + src: [:0]const u8, + case: union(ZIRStageType) { + /// The expected output ZIR + Transformation: []const u8, + /// A slice containing the expected errors *in sequential order*. + Error: []const ErrorMsg, + + /// Input to feed to the program, and expected outputs. + /// + /// If stdout, stderr, and exit_code are all null, addZIRCase will + /// discard the test. To test for successful compilation, use a + /// dedicated Compile stage instead. + Execution: struct { + stdin: ?[]const u8, + stdout: ?[]const u8, + stderr: ?[]const u8, + exit_code: ?u8, + }, + /// A Compiles test checks only that compilation of the given ZIR + /// succeeds. To test outputs, use an Execution test. It is good to + /// use a Compiles test before an Execution, as the overhead should + /// be low (due to incremental compilation) and TODO: provide a way + /// to check changed / new / etc decls in testing mode + /// (usingnamespace a debug info struct with a comptime flag?) + Compiles: void, + }, + }; + + /// A ZIRCase consists of a set of *stages*. A stage can transform ZIR, + /// compile it, ensure that compilation fails, and more. The same Module is + /// used for each stage, so each stage's source is treated as a single file + /// being updated by the test harness and incrementally compiled. + pub const ZIRCase = struct { + name: []const u8, + /// The platform the ZIR targets. For non-native platforms, an emulator + /// such as QEMU is required for tests to complete. + /// + target: std.zig.CrossTarget, + stages: []ZIRStage, + }; + + pub fn addZIRCase( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + stages: []ZIRStage, + ) !void { + const case = .{ + .name = name, + .target = target, + .stages = stages, + }; + try ctx.cases.append(case); + } + pub fn addZIRCompareOutput( ctx: *TestContext, name: []const u8, @@ -196,6 +285,14 @@ pub const TestContext = struct { const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{}); + for (self.zir_cases.items) |case| { + std.testing.base_allocator_instance.reset(); + const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target); + try self.runOneZIRCase(std.testing.allocator, root_node, case, info.target); + try std.testing.allocator_instance.validate(); + } + + // TODO: wipe the rest of this function for (self.zir_cmp_output_cases.items) |case| { std.testing.base_allocator_instance.reset(); try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target); @@ -215,6 +312,75 @@ pub const TestContext = struct { } } + fn runOneZIRCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: ZIRCase, target: std.Target) !void { + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + + const tmp_src_path = "test_case.zir"; + const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); + defer root_pkg.destroy(); + + var prg_node = root_node.start(case.name, case.stages.len); + prg_node.activate(); + defer prg_node.end(); + + var module = try Module.init(allocator, .{ + .target = target, + // This is an Executable, as opposed to e.g. a *library*. This does + // not mean no ZIR is generated. + // + // TODO: support tests for object file building, and library builds + // and linking. This will require a rework to support multi-file + // tests. + .output_mode = .Exe, + // TODO: support testing optimizations + .optimize_mode = .Debug, + .bin_file_dir = tmp.dir, + .bin_file_path = "test_case", + .root_pkg = root_pkg, + }); + defer module.deinit(); + + for (case.stages) |s| { + // TODO: remove before committing. This is for ZLS ;) + const stage: ZIRStage = s; + + var stage_node = prg_node.start("stage", 4); + stage_node.activate(); + defer stage_node.end(); + + var sync_node = stage_node.start("write", null); + sync_node.activate(); + try tmp.dir.writeFile(tmp_src_path, stage.src); + sync_node.end(); + + var module_node = stage_node.start("parse/analysis/codegen", null); + module_node.activate(); + try module.update(); + module_node.end(); + + switch (stage.case) { + .Transformation => |expected_output| { + var emit_node = stage_node.start("emit", null); + emit_node.activate(); + var new_zir_module = try zir.emit(allocator, module); + defer new_zir_module.deinit(allocator); + emit_node.end(); + + var write_node = stage_node.start("write", null); + write_node.activate(); + var out_zir = std.ArrayList(u8).init(allocator); + defer out_zir.deinit(); + try new_zir_module.writeToStream(allocator, out_zir.outStream()); + write_node.end(); + + std.testing.expectEqualSlices(u8, expected_output, out_zir.items); + }, + else => return error.unimplemented, + } + } + } + fn runOneZIRCmpOutputCase( self: *TestContext, allocator: *Allocator, @@ -426,6 +592,10 @@ pub const TestContext = struct { e.* = false; } + // TODO: check the input error list in sequential order, manually + // incrementing indices when needed. This would allow deduplicating the + // following three blocks into one, and the restriction it imposes on + // test writers is one that naturally flows anyways. { var i = module.failed_files.iterator(); while (i.next()) |pair| { -- 2.54.0 From 6dce317fe39443278ee744e66118c6e9b6023615 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 4 Jun 2020 15:59:33 -0400 Subject: [PATCH 023/295] Stage2/Testing: Fix error tests --- src-self-hosted/test.zig | 172 +++++++++++---------------------- test/stage2/compile_errors.zig | 4 +- 2 files changed, 58 insertions(+), 118 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index bde4a804e5436a8f9d74a8af0d6a4a90848a19e7..feba45575809b84ae8710a2c062896e086db8706 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -1,29 +1,10 @@ const std = @import("std"); const link = @import("link.zig"); const Module = @import("Module.zig"); -const ErrorMsg = Module.ErrorMsg; const Allocator = std.mem.Allocator; const zir = @import("zir.zig"); const Package = @import("Package.zig"); -test "find-offset" { - std.testing.expectEqual(findOffset("hello123", 1, 8), 7); - const testmsg = - \\@noreturn = primitive(noreturn) - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %0 = call(@notafunc, []) - \\}) - ; - std.testing.expectEqual(findOffset(testmsg, 2, 1), 32); - std.testing.expectEqual(findOffset(testmsg, 3, 1), 33); - std.testing.expectEqual(findOffset(testmsg, 3, 10), 42); - std.testing.expectEqual(findOffset(testmsg, 4, 1), 79); - std.testing.expectEqual(findOffset(testmsg, 5, 1), 106); - std.testing.expectEqual(findOffset(testmsg, 5, 13), 118); -} - test "self-hosted" { var ctx: TestContext = undefined; try ctx.init(); @@ -34,26 +15,11 @@ test "self-hosted" { try ctx.run(); } -/// Finds the raw byte offset of line:column in src. This is not a performant implementation, -/// as it should only ever be called rarely and it is better to focus on readability. -fn findOffset(src: []const u8, line: usize, column: usize) ?usize { - // "0000000001" - // 1:10 - // - var current_line: usize = 1; - var current_column: usize = 1; - for (src) |char, index| { - if (current_line == line and current_column == column) { - return index; - } - if (char == '\n') { - current_line += 1; - current_column = 0; - } - current_column += 1; - } - return null; -} +const ErrorMsg = struct { + msg: []const u8, + line: u32, + column: u32, +}; pub const TestContext = struct { // TODO: remove these. They are deprecated. @@ -115,7 +81,7 @@ pub const TestContext = struct { cross_target: std.zig.CrossTarget, }; - pub const ZIRStageType = enum { + pub const ZIRUpdateType = enum { /// A transformation stage transforms the input ZIR and tests against /// the expected output Transformation, @@ -129,7 +95,7 @@ pub const TestContext = struct { Compiles, }; - pub const ZIRStage = struct { + pub const ZIRUpdate = struct { /// The input to the current stage. We simulate an incremental update /// with the file's contents changed to this value each stage. /// @@ -138,7 +104,7 @@ pub const TestContext = struct { /// you can keep it mostly consistent, with small changes, testing the /// effects of the incremental compilation. src: [:0]const u8, - case: union(ZIRStageType) { + case: union(ZIRUpdateType) { /// The expected output ZIR Transformation: []const u8, /// A slice containing the expected errors *in sequential order*. @@ -175,14 +141,14 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. /// target: std.zig.CrossTarget, - stages: []ZIRStage, + stages: []ZIRUpdate, }; pub fn addZIRCase( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - stages: []ZIRStage, + stages: []ZIRUpdate, ) !void { const case = .{ .name = name, @@ -233,21 +199,34 @@ pub const TestContext = struct { ) void { var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator); for (expected_errors) |e| { - const line_index = std.mem.indexOf(u8, e, ":"); + var cur = e; + const err = cur[0..7]; + if (!std.mem.eql(u8, err, "error: ")) { + std.debug.panic("Only error messages are currently supported, received {}\n", .{e}); + } + cur = cur[7..]; + var line_index = std.mem.indexOf(u8, cur, ":"); if (line_index == null) { - std.debug.panic("Invalid test: error must be specified as 'line:column:msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as 'error: line:column: msg', found '{}'", .{e}); } - const column_index = std.mem.indexOf(u8, e[line_index.? + 1 ..], ":"); + const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); + cur = cur[line_index.? + 1 ..]; + const column_index = std.mem.indexOf(u8, cur, ":"); if (column_index == null) { - std.debug.panic("Invalid test: error must be specified as 'line:column:msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as 'error: line:column: msg', found '{}'", .{e}); } - const line = std.fmt.parseInt(usize, e[0..line_index.?], 10) catch @panic("Unable to parse line number"); - const column = std.fmt.parseInt(usize, e[line_index.? + 1 ..][0..column_index.?], 10) catch @panic("Unable to parse column number"); - const msg = e[line_index.? + 1 ..][column_index.? + 1 ..]; - const offset = findOffset(src, line, column) orelse std.debug.panic("Unable to match {}:{} to byte offset!", .{ line, column }); - array.append(ErrorMsg{ - .byte_offset = offset, + const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); + std.debug.assert(cur[column_index.? + 1] == ' '); + const msg = cur[column_index.? + 2 ..]; + + if (line == 0 or column == 0) { + @panic("Invalid test: error line and column must be specified starting at one!"); + } + + array.append(.{ .msg = msg, + .line = line - 1, + .column = column - 1, }) catch unreachable; } ctx.zir_error_cases.append(.{ @@ -264,6 +243,7 @@ pub const TestContext = struct { .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(allocator), .zir_error_cases = std.ArrayList(ZIRErrorCase).init(allocator), + .zir_cases = std.ArrayList(ZIRCase).init(allocator), }; } @@ -274,6 +254,7 @@ pub const TestContext = struct { self.zir_error_cases.allocator.free(e.expected_errors); } self.zir_error_cases.deinit(); + self.zir_cases.deinit(); self.* = undefined; } @@ -343,9 +324,9 @@ pub const TestContext = struct { for (case.stages) |s| { // TODO: remove before committing. This is for ZLS ;) - const stage: ZIRStage = s; + const stage: ZIRUpdate = s; - var stage_node = prg_node.start("stage", 4); + var stage_node = prg_node.start("update", 4); stage_node.activate(); defer stage_node.end(); @@ -592,74 +573,33 @@ pub const TestContext = struct { e.* = false; } - // TODO: check the input error list in sequential order, manually - // incrementing indices when needed. This would allow deduplicating the - // following three blocks into one, and the restriction it imposes on - // test writers is one that naturally flows anyways. - { - var i = module.failed_files.iterator(); - while (i.next()) |pair| { - const v1 = pair.value.*; - var handled = false; - for (case.expected_errors) |e, index| { - if (!handled_errors[index]) { - if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { - handled_errors[index] = true; - handled = true; - break; - } + var all_errors = try module.getAllErrorsAlloc(); + defer all_errors.deinit(allocator); + for (all_errors.list) |e| { + var handled = false; + for (case.expected_errors) |ex, i| { + if (e.line == ex.line and e.column == ex.column and std.mem.eql(u8, ex.msg, e.msg)) { + if (handled_errors[i]) { + err = error.ErrorReceivedMultipleTimes; + std.debug.warn("Received error multiple times: {}\n", .{e.msg}); + } else { + handled_errors[i] = true; + handled = true; } - } - if (!handled) { - err = error.UnexpectedError; - std.debug.warn("Unexpected file error: {}\n", .{v1}); - } - } - } - { - var i = module.failed_decls.iterator(); - while (i.next()) |pair| { - const v1 = pair.value.*; - var handled = false; - for (case.expected_errors) |e, index| { - if (!handled_errors[index]) { - if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { - handled_errors[index] = true; - handled = true; - break; - } - } - } - if (!handled) { - err = error.UnexpectedError; - std.debug.warn("Unexpected decl error: {}\n", .{v1}); + break; } } - } - { - var i = module.failed_exports.iterator(); - while (i.next()) |pair| { - const v1 = pair.value.*; - var handled = false; - for (case.expected_errors) |e, index| { - if (!handled_errors[index]) { - if (v1.byte_offset == e.byte_offset and std.mem.eql(u8, v1.msg, e.msg)) { - handled_errors[index] = true; - handled = true; - break; - } - } - } - if (!handled) { - err = error.UnexpectedError; - std.debug.warn("Unexpected export error: {}\n", .{v1}); - } + if (!handled) { + err = error.ErrorNotExpected; + std.debug.warn("Received an unexpected error: {}:{}: {}\n", .{ e.line, e.column, e.msg }); } } + for (handled_errors) |e, i| { if (!e) { err = error.MissingExpectedError; - std.debug.warn("Did not receive error: {}\n", .{case.expected_errors[i].msg}); + const er = case.expected_errors[i]; + std.debug.warn("Did not receive error: {}:{}: {}\n", .{ er.line, er.column, er.msg }); } } diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 865ececf18e8806ce6621aa451e5e5e64478d054..5c7a9e455402273984a8b9a59fe1c4987d1e070d 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,7 +18,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_][]const u8{"5:13:unrecognized identifier: %test"}); + , &[_][]const u8{"error: 5:13: unrecognized identifier: %test"}); // TODO: fix this test // ctx.addZIRError("call with non-existent target", linux_x64, @@ -45,7 +45,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@0 = str("_start") \\@1 = ref(@0) \\@2 = export(@1, @start) - , &[_][]const u8{"4:9:unable to call function with naked calling convention"}); + , &[_][]const u8{"error: 4:9: unable to call function with naked calling convention"}); //try ctx.testCompileError( // \\export fn entry() void {} -- 2.54.0 From e77fc7fe7e3d6e5e4dc89bbc8fa95e65d4134b9f Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 4 Jun 2020 16:12:09 -0400 Subject: [PATCH 024/295] Stage2/Testing: Fix error specification --- src-self-hosted/test.zig | 14 +++++--------- test/stage2/compile_errors.zig | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index feba45575809b84ae8710a2c062896e086db8706..c0fb52a74da442fbb1f6c42099ea8271f02b9cb9 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -200,24 +200,20 @@ pub const TestContext = struct { var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator); for (expected_errors) |e| { var cur = e; - const err = cur[0..7]; - if (!std.mem.eql(u8, err, "error: ")) { - std.debug.panic("Only error messages are currently supported, received {}\n", .{e}); - } - cur = cur[7..]; var line_index = std.mem.indexOf(u8, cur, ":"); if (line_index == null) { - std.debug.panic("Invalid test: error must be specified as 'error: line:column: msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as 'line:column: error: msg', found '{}'", .{e}); } const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); cur = cur[line_index.? + 1 ..]; const column_index = std.mem.indexOf(u8, cur, ":"); if (column_index == null) { - std.debug.panic("Invalid test: error must be specified as 'error: line:column: msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as 'line:column: error: msg', found '{}'", .{e}); } const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); - std.debug.assert(cur[column_index.? + 1] == ' '); - const msg = cur[column_index.? + 2 ..]; + cur = cur[column_index.? + 2 ..]; + std.debug.assert(std.mem.eql(u8, cur[0..7], "error: ")); + const msg = cur[7..]; if (line == 0 or column == 0) { @panic("Invalid test: error line and column must be specified starting at one!"); diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 5c7a9e455402273984a8b9a59fe1c4987d1e070d..78a00840a000ef6c877989a9e2f93a8bc8e53137 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,7 +18,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_][]const u8{"error: 5:13: unrecognized identifier: %test"}); + , &[_][]const u8{"5:13: error: unrecognized identifier: %test"}); // TODO: fix this test // ctx.addZIRError("call with non-existent target", linux_x64, @@ -45,7 +45,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@0 = str("_start") \\@1 = ref(@0) \\@2 = export(@1, @start) - , &[_][]const u8{"error: 4:9: unable to call function with naked calling convention"}); + , &[_][]const u8{"4:9: error: unable to call function with naked calling convention"}); //try ctx.testCompileError( // \\export fn entry() void {} -- 2.54.0 From b6bd51ed69f75930bbf9a80b5b8841d1517a6828 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 17:55:24 -0400 Subject: [PATCH 025/295] Stage2/Testing: Move Transformation case to ZIRCase --- src-self-hosted/test.zig | 10 +-- test/stage2/zir.zig | 132 +++++++++++++++------------------------ 2 files changed, 57 insertions(+), 85 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index c0fb52a74da442fbb1f6c42099ea8271f02b9cb9..a9a4c4fc4b633ed985cc2940ca9734a9e29ad959 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -141,21 +141,21 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. /// target: std.zig.CrossTarget, - stages: []ZIRUpdate, + stages: []const ZIRUpdate, }; pub fn addZIRCase( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - stages: []ZIRUpdate, - ) !void { - const case = .{ + stages: []const ZIRUpdate, + ) void { + const case = ZIRCase{ .name = name, .target = target, .stages = stages, }; - try ctx.cases.append(case); + ctx.zir_cases.append(case) catch |err| std.debug.panic("Error: {}", .{err}); } pub fn addZIRCompareOutput( diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index bf5d4b8eae101ec4531b0b07a1f46193dfa0b4f8..a8cf4ca96495543d46a19d0939c0a57d506d5380 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -1,5 +1,6 @@ const std = @import("std"); const TestContext = @import("../../src-self-hosted/test.zig").TestContext; +const ZIRUpdate = TestContext.ZIRUpdate; // self-hosted does not yet support PE executable files / COFF object files // or mach-o files. So we do the ZIR transform test cases cross compiling for // x86_64-linux. @@ -9,86 +10,57 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) void { - ctx.addZIRTransform("referencing decls which appear later in the file", linux_x64, - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\ - \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) - \\ - \\@entry = fn(@fnty, { - \\ %11 = return() - \\}) - , - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\@9 = str("entry") - \\@10 = ref(@9) - \\@unnamed$6 = str("entry") - \\@unnamed$7 = ref(@unnamed$6) - \\@unnamed$8 = export(@unnamed$7, @entry) - \\@unnamed$10 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$10, { - \\ %0 = return() - \\}) - \\ - ); - ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@fnty = fntype([], @void, cc=C) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@entry = fn(@fnty, { - \\ %a = str("\x32\x08\x01\x0a") - \\ %aref = ref(%a) - \\ %eptr0 = elemptr(%aref, @0) - \\ %eptr1 = elemptr(%aref, @1) - \\ %eptr2 = elemptr(%aref, @2) - \\ %eptr3 = elemptr(%aref, @3) - \\ %v0 = deref(%eptr0) - \\ %v1 = deref(%eptr1) - \\ %v2 = deref(%eptr2) - \\ %v3 = deref(%eptr3) - \\ %x0 = add(%v0, %v1) - \\ %x1 = add(%v2, %v3) - \\ %result = add(%x0, %x1) - \\ - \\ %expected = int(69) - \\ %ok = cmp(%result, eq, %expected) - \\ %10 = condbr(%ok, { - \\ %11 = return() - \\ }, { - \\ %12 = breakpoint() - \\ }) - \\}) - \\ - \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) - , - \\@void = primitive(void) - \\@fnty = fntype([], @void, cc=C) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\@unnamed$7 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$7, { - \\ %0 = return() - \\}) - \\@a = str("2\x08\x01\n") - \\@9 = str("entry") - \\@10 = ref(@9) - \\@unnamed$14 = str("entry") - \\@unnamed$15 = ref(@unnamed$14) - \\@unnamed$16 = export(@unnamed$15, @entry) - \\ - ); + ctx.addZIRCase("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, &[_]ZIRUpdate{ZIRUpdate{ + .src = + \\@void = primitive(void) + \\@usize = primitive(usize) + \\@fnty = fntype([], @void, cc=C) + \\@0 = int(0) + \\@1 = int(1) + \\@2 = int(2) + \\@3 = int(3) + \\ + \\@entry = fn(@fnty, { + \\ %a = str("\x32\x08\x01\x0a") + \\ %aref = ref(%a) + \\ %eptr0 = elemptr(%aref, @0) + \\ %eptr1 = elemptr(%aref, @1) + \\ %eptr2 = elemptr(%aref, @2) + \\ %eptr3 = elemptr(%aref, @3) + \\ %v0 = deref(%eptr0) + \\ %v1 = deref(%eptr1) + \\ %v2 = deref(%eptr2) + \\ %v3 = deref(%eptr3) + \\ %x0 = add(%v0, %v1) + \\ %x1 = add(%v2, %v3) + \\ %result = add(%x0, %x1) + \\ + \\ %expected = int(69) + \\ %ok = cmp(%result, eq, %expected) + \\ %10 = condbr(%ok, { + \\ %11 = return() + \\ }, { + \\ %12 = breakpoint() + \\ }) + \\}) + \\ + \\@9 = str("entry") + \\@10 = ref(@9) + \\@11 = export(@10, @entry) + , + .case = .{ + .Transformation = + \\@0 = primitive(void) + \\@1 = fntype([], @0, cc=C) + \\@2 = fn(@1, { + \\ %0 = return() + \\}) + \\@3 = str("entry") + \\@4 = ref(@3) + \\@5 = export(@4, @2) + \\ + }, + }}); { var case = ctx.addZIRMulti("reference cycle with compile error in the cycle", linux_x64); -- 2.54.0 From 71dca252a575a6a09e16dfc8faf8b704e847eb87 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sun, 14 Jun 2020 20:55:49 -0400 Subject: [PATCH 026/295] Stage2/Testing: Rename stage -> update --- src-self-hosted/test.zig | 50 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index a9a4c4fc4b633ed985cc2940ca9734a9e29ad959..59960d3ed83f385d890e3d960724aca4a8f6734c 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -82,24 +82,24 @@ pub const TestContext = struct { }; pub const ZIRUpdateType = enum { - /// A transformation stage transforms the input ZIR and tests against + /// A transformation update transforms the input ZIR and tests against /// the expected output Transformation, - /// An error stage attempts to compile bad code, and ensures that it + /// An error update attempts to compile bad code, and ensures that it /// fails to compile, and for the expected reasons Error, - /// An execution stage compiles and runs the input ZIR, feeding in + /// An execution update compiles and runs the input ZIR, feeding in /// provided input and ensuring that the outputs match what is expected Execution, - /// A compilation stage checks that the ZIR compiles without any issues + /// A compilation update checks that the ZIR compiles without any issues Compiles, }; pub const ZIRUpdate = struct { - /// The input to the current stage. We simulate an incremental update - /// with the file's contents changed to this value each stage. + /// The input to the current update. We simulate an incremental update + /// with the file's contents changed to this value each update. /// - /// This value can change entirely between stages, which would be akin + /// This value can change entirely between updates, which would be akin /// to deleting the source file and creating a new one from scratch; or /// you can keep it mostly consistent, with small changes, testing the /// effects of the incremental compilation. @@ -114,7 +114,7 @@ pub const TestContext = struct { /// /// If stdout, stderr, and exit_code are all null, addZIRCase will /// discard the test. To test for successful compilation, use a - /// dedicated Compile stage instead. + /// dedicated Compile update instead. Execution: struct { stdin: ?[]const u8, stdout: ?[]const u8, @@ -131,9 +131,9 @@ pub const TestContext = struct { }, }; - /// A ZIRCase consists of a set of *stages*. A stage can transform ZIR, + /// A ZIRCase consists of a set of *updates*. A update can transform ZIR, /// compile it, ensure that compilation fails, and more. The same Module is - /// used for each stage, so each stage's source is treated as a single file + /// used for each update, so each update's source is treated as a single file /// being updated by the test harness and incrementally compiled. pub const ZIRCase = struct { name: []const u8, @@ -141,19 +141,19 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. /// target: std.zig.CrossTarget, - stages: []const ZIRUpdate, + updates: []const ZIRUpdate, }; pub fn addZIRCase( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - stages: []const ZIRUpdate, + updates: []const ZIRUpdate, ) void { const case = ZIRCase{ .name = name, .target = target, - .stages = stages, + .updates = updates, }; ctx.zir_cases.append(case) catch |err| std.debug.panic("Error: {}", .{err}); } @@ -297,7 +297,7 @@ pub const TestContext = struct { const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); - var prg_node = root_node.start(case.name, case.stages.len); + var prg_node = root_node.start(case.name, case.updates.len); prg_node.activate(); defer prg_node.end(); @@ -318,33 +318,33 @@ pub const TestContext = struct { }); defer module.deinit(); - for (case.stages) |s| { + for (case.updates) |s| { // TODO: remove before committing. This is for ZLS ;) - const stage: ZIRUpdate = s; + const update: ZIRUpdate = s; - var stage_node = prg_node.start("update", 4); - stage_node.activate(); - defer stage_node.end(); + var update_node = prg_node.start("update", 4); + update_node.activate(); + defer update_node.end(); - var sync_node = stage_node.start("write", null); + var sync_node = update_node.start("write", null); sync_node.activate(); - try tmp.dir.writeFile(tmp_src_path, stage.src); + try tmp.dir.writeFile(tmp_src_path, update.src); sync_node.end(); - var module_node = stage_node.start("parse/analysis/codegen", null); + var module_node = update_node.start("parse/analysis/codegen", null); module_node.activate(); try module.update(); module_node.end(); - switch (stage.case) { + switch (update.case) { .Transformation => |expected_output| { - var emit_node = stage_node.start("emit", null); + var emit_node = update_node.start("emit", null); emit_node.activate(); var new_zir_module = try zir.emit(allocator, module); defer new_zir_module.deinit(allocator); emit_node.end(); - var write_node = stage_node.start("write", null); + var write_node = update_node.start("write", null); write_node.activate(); var out_zir = std.ArrayList(u8).init(allocator); defer out_zir.deinit(); -- 2.54.0 From 1e5945d0a926b74517b1bed3cdab00f244aa592c Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 17:56:28 -0400 Subject: [PATCH 027/295] Stage2/Testing: remove ZIRTransformCase --- src-self-hosted/test.zig | 159 +-------------------------------------- 1 file changed, 2 insertions(+), 157 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 59960d3ed83f385d890e3d960724aca4a8f6734c..569b88184dc7219048c1c38f8ba13cc5ecc74b23 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -25,14 +25,9 @@ pub const TestContext = struct { // TODO: remove these. They are deprecated. zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), // TODO: remove - zir_transform_cases: std.ArrayList(ZIRTransformCase), - // TODO: remove zir_error_cases: std.ArrayList(ZIRErrorCase), - /// TODO: find a way to treat cases as individual tests as far as - /// `zig test` is concerned. If we have 100 tests, they should *not* be - /// considered as *one*. "ZIR" isn't really a *test*, it's a *category* of - /// tests. + /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases) zir_cases: std.ArrayList(ZIRCase), // TODO: remove @@ -43,37 +38,7 @@ pub const TestContext = struct { }; // TODO: remove - pub const ZIRTransformCase = struct { - name: []const u8, - cross_target: std.zig.CrossTarget, - updates: std.ArrayList(Update), - pub const Update = struct { - expected: Expected, - src: [:0]const u8, - }; - - pub const Expected = union(enum) { - zir: []const u8, - errors: []const []const u8, - }; - - pub fn addZIR(case: *ZIRTransformCase, src: [:0]const u8, zir_text: []const u8) void { - case.updates.append(.{ - .src = src, - .expected = .{ .zir = zir_text }, - }) catch unreachable; - } - - pub fn addError(case: *ZIRTransformCase, src: [:0]const u8, errors: []const []const u8) void { - case.updates.append(.{ - .src = src, - .expected = .{ .errors = errors }, - }) catch unreachable; - } - }; - - // TODO: remove pub const ZIRErrorCase = struct { name: []const u8, src: [:0]const u8, @@ -171,25 +136,6 @@ pub const TestContext = struct { }) catch unreachable; } - pub fn addZIRTransform( - ctx: *TestContext, - name: []const u8, - cross_target: std.zig.CrossTarget, - src: [:0]const u8, - expected_zir: []const u8, - ) void { - const case = ctx.zir_transform_cases.addOne() catch unreachable; - case.* = .{ - .name = name, - .cross_target = cross_target, - .updates = std.ArrayList(ZIRTransformCase.Update).init(std.heap.page_allocator), - }; - case.updates.append(.{ - .src = src, - .expected = .{ .zir = expected_zir }, - }) catch unreachable; - } - pub fn addZIRError( ctx: *TestContext, name: []const u8, @@ -237,7 +183,6 @@ pub const TestContext = struct { const allocator = std.heap.page_allocator; self.* = .{ .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), - .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(allocator), .zir_error_cases = std.ArrayList(ZIRErrorCase).init(allocator), .zir_cases = std.ArrayList(ZIRCase).init(allocator), }; @@ -245,7 +190,6 @@ pub const TestContext = struct { fn deinit(self: *TestContext) void { self.zir_cmp_output_cases.deinit(); - self.zir_transform_cases.deinit(); for (self.zir_error_cases.items) |e| { self.zir_error_cases.allocator.free(e.expected_errors); } @@ -256,8 +200,7 @@ pub const TestContext = struct { fn run(self: *TestContext) !void { var progress = std.Progress{}; - const root_node = try progress.start("zir", self.zir_cmp_output_cases.items.len + - self.zir_transform_cases.items.len); + const root_node = try progress.start("zir", self.zir_cases.items.len); defer root_node.end(); const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{}); @@ -275,12 +218,6 @@ pub const TestContext = struct { try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target); try std.testing.allocator_instance.validate(); } - for (self.zir_transform_cases.items) |case| { - std.testing.base_allocator_instance.reset(); - const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); - try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, info.target); - try std.testing.allocator_instance.validate(); - } for (self.zir_error_cases.items) |case| { std.testing.base_allocator_instance.reset(); const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); @@ -432,98 +369,6 @@ pub const TestContext = struct { } } - fn runOneZIRTransformCase( - self: *TestContext, - allocator: *Allocator, - root_node: *std.Progress.Node, - case: ZIRTransformCase, - target: std.Target, - ) !void { - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - var update_node = root_node.start(case.name, case.updates.items.len); - update_node.activate(); - defer update_node.end(); - - const tmp_src_path = "test-case.zir"; - const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); - defer root_pkg.destroy(); - - var module = try Module.init(allocator, .{ - .target = target, - .output_mode = .Obj, - .optimize_mode = .Debug, - .bin_file_dir = tmp.dir, - .bin_file_path = "test-case.o", - .root_pkg = root_pkg, - }); - defer module.deinit(); - - for (case.updates.items) |update| { - var prg_node = update_node.start("", 3); - prg_node.activate(); - defer prg_node.end(); - - try tmp.dir.writeFile(tmp_src_path, update.src); - - var module_node = prg_node.start("parse/analysis/codegen", null); - module_node.activate(); - try module.update(); - module_node.end(); - - switch (update.expected) { - .zir => |expected_zir| { - var emit_node = prg_node.start("emit", null); - emit_node.activate(); - var new_zir_module = try zir.emit(allocator, module); - defer new_zir_module.deinit(allocator); - emit_node.end(); - - var write_node = prg_node.start("write", null); - write_node.activate(); - var out_zir = std.ArrayList(u8).init(allocator); - defer out_zir.deinit(); - try new_zir_module.writeToStream(allocator, out_zir.outStream()); - write_node.end(); - - std.testing.expectEqualSlices(u8, expected_zir, out_zir.items); - }, - .errors => |expected_errors| { - var all_errors = try module.getAllErrorsAlloc(); - defer all_errors.deinit(module.allocator); - for (expected_errors) |expected_error| { - for (all_errors.list) |full_err_msg| { - const text = try std.fmt.allocPrint(allocator, ":{}:{}: error: {}", .{ - full_err_msg.line + 1, - full_err_msg.column + 1, - full_err_msg.msg, - }); - defer allocator.free(text); - if (std.mem.eql(u8, text, expected_error)) { - break; - } - } else { - std.debug.warn( - "{}\nExpected this error:\n================\n{}\n================\nBut found these errors:\n================\n", - .{ case.name, expected_error }, - ); - for (all_errors.list) |full_err_msg| { - std.debug.warn(":{}:{}: error: {}\n", .{ - full_err_msg.line + 1, - full_err_msg.column + 1, - full_err_msg.msg, - }); - } - std.debug.warn("================\nTest failed\n", .{}); - std.process.exit(1); - } - } - }, - } - } - } - fn runOneZIRErrorCase( self: *TestContext, allocator: *Allocator, -- 2.54.0 From 7ee0462f5f8b4ec5ff824b38821d8f1b79e5ba63 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 17:59:38 -0400 Subject: [PATCH 028/295] Stage2/Testing: Fix transformation tests --- src-self-hosted/test.zig | 42 +++++++++--- test/stage2/zir.zig | 136 +++++++++++++++++++++++---------------- 2 files changed, 114 insertions(+), 64 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 569b88184dc7219048c1c38f8ba13cc5ecc74b23..42af19ddd9332f64338d110e2140806c8d9da714 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -38,7 +38,6 @@ pub const TestContext = struct { }; // TODO: remove - pub const ZIRErrorCase = struct { name: []const u8, src: [:0]const u8, @@ -71,7 +70,7 @@ pub const TestContext = struct { src: [:0]const u8, case: union(ZIRUpdateType) { /// The expected output ZIR - Transformation: []const u8, + Transformation: [:0]const u8, /// A slice containing the expected errors *in sequential order*. Error: []const ErrorMsg, @@ -106,21 +105,30 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. /// target: std.zig.CrossTarget, - updates: []const ZIRUpdate, + updates: std.ArrayList(ZIRUpdate), + + pub fn addTransform(self: *ZIRCase, src: [:0]const u8, result: [:0]const u8) void { + self.updates.append(.{ + .src = src, + .case = .{ .Transformation = result }, + }) catch unreachable; + } + + pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void {} }; - pub fn addZIRCase( + pub fn addZIRMulti( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - updates: []const ZIRUpdate, - ) void { + ) *ZIRCase { const case = ZIRCase{ .name = name, .target = target, - .updates = updates, + .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), }; - ctx.zir_cases.append(case) catch |err| std.debug.panic("Error: {}", .{err}); + ctx.zir_cases.append(case) catch unreachable; + return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; } pub fn addZIRCompareOutput( @@ -136,6 +144,17 @@ pub const TestContext = struct { }) catch unreachable; } + pub fn addZIRTransform( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + result: [:0]const u8, + ) void { + var c = ctx.addZIRMulti(name, target); + c.addTransform(src, result); + } + pub fn addZIRError( ctx: *TestContext, name: []const u8, @@ -194,6 +213,9 @@ pub const TestContext = struct { self.zir_error_cases.allocator.free(e.expected_errors); } self.zir_error_cases.deinit(); + for (self.zir_cases.items) |c| { + c.updates.deinit(); + } self.zir_cases.deinit(); self.* = undefined; } @@ -234,7 +256,7 @@ pub const TestContext = struct { const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); - var prg_node = root_node.start(case.name, case.updates.len); + var prg_node = root_node.start(case.name, case.updates.items.len); prg_node.activate(); defer prg_node.end(); @@ -255,7 +277,7 @@ pub const TestContext = struct { }); defer module.deinit(); - for (case.updates) |s| { + for (case.updates.items) |s| { // TODO: remove before committing. This is for ZLS ;) const update: ZIRUpdate = s; diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index a8cf4ca96495543d46a19d0939c0a57d506d5380..d58b30c29db5c17813321afed557b036b05e38f1 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -1,6 +1,5 @@ const std = @import("std"); const TestContext = @import("../../src-self-hosted/test.zig").TestContext; -const ZIRUpdate = TestContext.ZIRUpdate; // self-hosted does not yet support PE executable files / COFF object files // or mach-o files. So we do the ZIR transform test cases cross compiling for // x86_64-linux. @@ -10,61 +9,90 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) void { - ctx.addZIRCase("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, &[_]ZIRUpdate{ZIRUpdate{ - .src = - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@fnty = fntype([], @void, cc=C) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@entry = fn(@fnty, { - \\ %a = str("\x32\x08\x01\x0a") - \\ %aref = ref(%a) - \\ %eptr0 = elemptr(%aref, @0) - \\ %eptr1 = elemptr(%aref, @1) - \\ %eptr2 = elemptr(%aref, @2) - \\ %eptr3 = elemptr(%aref, @3) - \\ %v0 = deref(%eptr0) - \\ %v1 = deref(%eptr1) - \\ %v2 = deref(%eptr2) - \\ %v3 = deref(%eptr3) - \\ %x0 = add(%v0, %v1) - \\ %x1 = add(%v2, %v3) - \\ %result = add(%x0, %x1) - \\ - \\ %expected = int(69) - \\ %ok = cmp(%result, eq, %expected) - \\ %10 = condbr(%ok, { - \\ %11 = return() - \\ }, { - \\ %12 = breakpoint() - \\ }) - \\}) - \\ - \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) - , - .case = .{ - .Transformation = - \\@0 = primitive(void) - \\@1 = fntype([], @0, cc=C) - \\@2 = fn(@1, { - \\ %0 = return() - \\}) - \\@3 = str("entry") - \\@4 = ref(@3) - \\@5 = export(@4, @2) - \\ - }, - }}); + ctx.addZIRTransform("referencing decls which appear later in the file", linux_x64, + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\ + \\@9 = str("entry") + \\@10 = ref(@9) + \\@11 = export(@10, @entry) + \\ + \\@entry = fn(@fnty, { + \\ %11 = return() + \\}) + , + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\@9 = str("entry") + \\@10 = ref(@9) + \\@unnamed$6 = str("entry") + \\@unnamed$7 = ref(@unnamed$6) + \\@unnamed$8 = export(@unnamed$7, @entry) + \\@unnamed$10 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$10, { + \\ %0 = return() + \\}) + \\ + ); + ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, + \\@void = primitive(void) + \\@usize = primitive(usize) + \\@fnty = fntype([], @void, cc=C) + \\@0 = int(0) + \\@1 = int(1) + \\@2 = int(2) + \\@3 = int(3) + \\ + \\@entry = fn(@fnty, { + \\ %a = str("\x32\x08\x01\x0a") + \\ %aref = ref(%a) + \\ %eptr0 = elemptr(%aref, @0) + \\ %eptr1 = elemptr(%aref, @1) + \\ %eptr2 = elemptr(%aref, @2) + \\ %eptr3 = elemptr(%aref, @3) + \\ %v0 = deref(%eptr0) + \\ %v1 = deref(%eptr1) + \\ %v2 = deref(%eptr2) + \\ %v3 = deref(%eptr3) + \\ %x0 = add(%v0, %v1) + \\ %x1 = add(%v2, %v3) + \\ %result = add(%x0, %x1) + \\ + \\ %expected = int(69) + \\ %ok = cmp(%result, eq, %expected) + \\ %10 = condbr(%ok, { + \\ %11 = return() + \\ }, { + \\ %12 = breakpoint() + \\ }) + \\}) + \\ + \\@9 = str("entry") + \\@10 = ref(@9) + \\@11 = export(@10, @entry) + , + \\@void = primitive(void) + \\@fnty = fntype([], @void, cc=C) + \\@0 = int(0) + \\@1 = int(1) + \\@2 = int(2) + \\@3 = int(3) + \\@unnamed$7 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$7, { + \\ %0 = return() + \\}) + \\@a = str("2\x08\x01\n") + \\@9 = str("entry") + \\@10 = ref(@9) + \\@unnamed$14 = str("entry") + \\@unnamed$15 = ref(@unnamed$14) + \\@unnamed$16 = export(@unnamed$15, @entry) + \\ + ); { var case = ctx.addZIRMulti("reference cycle with compile error in the cycle", linux_x64); - case.addZIR( + case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -143,7 +171,7 @@ pub fn addCases(ctx: *TestContext) void { // Now we remove the call to `a`. `a` and `b` form a cycle, but no entry points are // referencing either of them. This tests that the cycle is detected, and the error // goes away. - case.addZIR( + case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ -- 2.54.0 From adb21f1cafb4cf5e2a64ff9d8262c2506d72208b Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 20:25:13 -0400 Subject: [PATCH 029/295] Stage2/Testing: Add error tests to ZIRCase --- src-self-hosted/test.zig | 112 ++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 43 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 42af19ddd9332f64338d110e2140806c8d9da714..852c000a296778854e859ed19e3cac53fdff8db9 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -114,7 +114,37 @@ pub const TestContext = struct { }) catch unreachable; } - pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void {} + pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void { + var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; + for (errors) |e, i| { + var cur = e[1..]; + var line_index = std.mem.indexOf(u8, cur, ":"); + if (line_index == null) { + std.debug.panic("Invalid test: error must be specified as ':line:column: error: msg', found '{}'", .{e}); + } + const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); + cur = cur[line_index.? + 1 ..]; + const column_index = std.mem.indexOf(u8, cur, ":"); + if (column_index == null) { + std.debug.panic("Invalid test: error must be specified as ':line:column: error: msg', found '{}'", .{e}); + } + const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); + cur = cur[column_index.? + 2 ..]; + std.debug.assert(std.mem.eql(u8, cur[0..7], "error: ")); + const msg = cur[7..]; + + if (line == 0 or column == 0) { + @panic("Invalid test: error line and column must be specified starting at one!"); + } + + array[i] = .{ + .msg = msg, + .line = line - 1, + .column = column - 1, + }; + } + self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable; + } }; pub fn addZIRMulti( @@ -161,42 +191,7 @@ pub const TestContext = struct { cross_target: std.zig.CrossTarget, src: [:0]const u8, expected_errors: []const []const u8, - ) void { - var array = std.ArrayList(ErrorMsg).init(ctx.zir_error_cases.allocator); - for (expected_errors) |e| { - var cur = e; - var line_index = std.mem.indexOf(u8, cur, ":"); - if (line_index == null) { - std.debug.panic("Invalid test: error must be specified as 'line:column: error: msg', found '{}'", .{e}); - } - const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); - cur = cur[line_index.? + 1 ..]; - const column_index = std.mem.indexOf(u8, cur, ":"); - if (column_index == null) { - std.debug.panic("Invalid test: error must be specified as 'line:column: error: msg', found '{}'", .{e}); - } - const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); - cur = cur[column_index.? + 2 ..]; - std.debug.assert(std.mem.eql(u8, cur[0..7], "error: ")); - const msg = cur[7..]; - - if (line == 0 or column == 0) { - @panic("Invalid test: error line and column must be specified starting at one!"); - } - - array.append(.{ - .msg = msg, - .line = line - 1, - .column = column - 1, - }) catch unreachable; - } - ctx.zir_error_cases.append(.{ - .name = name, - .src = src, - .expected_errors = array.toOwnedSlice(), - .cross_target = cross_target, - }) catch unreachable; - } + ) void {} fn init(self: *TestContext) !void { const allocator = std.heap.page_allocator; @@ -214,6 +209,11 @@ pub const TestContext = struct { } self.zir_error_cases.deinit(); for (self.zir_cases.items) |c| { + for (c.updates.items) |u| { + if (u.case == .Error) { + c.updates.allocator.free(u.case.Error); + } + } c.updates.deinit(); } self.zir_cases.deinit(); @@ -268,11 +268,11 @@ pub const TestContext = struct { // TODO: support tests for object file building, and library builds // and linking. This will require a rework to support multi-file // tests. - .output_mode = .Exe, + .output_mode = .Obj, // TODO: support testing optimizations .optimize_mode = .Debug, .bin_file_dir = tmp.dir, - .bin_file_path = "test_case", + .bin_file_path = "test_case.o", .root_pkg = root_pkg, }); defer module.deinit(); @@ -312,6 +312,35 @@ pub const TestContext = struct { std.testing.expectEqualSlices(u8, expected_output, out_zir.items); }, + .Error => |e| { + var handled_errors = try allocator.alloc(bool, e.len); + defer allocator.free(handled_errors); + for (handled_errors) |*h| { + h.* = false; + } + var all_errors = try module.getAllErrorsAlloc(); + defer all_errors.deinit(allocator); + for (all_errors.list) |a| { + for (e) |ex, i| { + if (a.line == ex.line and a.column == ex.column and std.mem.eql(u8, ex.msg, a.msg)) { + handled_errors[i] = true; + break; + } + } else { + std.debug.warn("{}\nUnexpected error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg }); + std.process.exit(1); + } + } + + for (handled_errors) |h, i| { + if (!h) { + const er = e[i]; + std.debug.warn("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg }); + std.process.exit(1); + } + } + }, + else => return error.unimplemented, } } @@ -423,10 +452,7 @@ pub const TestContext = struct { var module_node = prg_node.start("parse/analysis/codegen", null); module_node.activate(); - const failed = f: { - module.update() catch break :f true; - break :f false; - }; + try module.update(); module_node.end(); var err: ?anyerror = null; -- 2.54.0 From 7d1c9a69ccaf148c7264df2d197549a851478998 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 20:30:08 -0400 Subject: [PATCH 030/295] Stage2/Testing: Remove dead code --- src-self-hosted/test.zig | 130 ++------------------------------- test/stage2/compile_errors.zig | 4 +- 2 files changed, 10 insertions(+), 124 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 852c000a296778854e859ed19e3cac53fdff8db9..ad0bd4a640091d9890521fea0e4a2567160c1339 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -24,8 +24,6 @@ const ErrorMsg = struct { pub const TestContext = struct { // TODO: remove these. They are deprecated. zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), - // TODO: remove - zir_error_cases: std.ArrayList(ZIRErrorCase), /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases) zir_cases: std.ArrayList(ZIRCase), @@ -37,14 +35,6 @@ pub const TestContext = struct { expected_stdout_list: []const []const u8, }; - // TODO: remove - pub const ZIRErrorCase = struct { - name: []const u8, - src: [:0]const u8, - expected_errors: []const ErrorMsg, - cross_target: std.zig.CrossTarget, - }; - pub const ZIRUpdateType = enum { /// A transformation update transforms the input ZIR and tests against /// the expected output @@ -114,6 +104,9 @@ pub const TestContext = struct { }) catch unreachable; } + /// TODO: document + /// + /// Errors must be specified in sequential order pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { @@ -188,26 +181,24 @@ pub const TestContext = struct { pub fn addZIRError( ctx: *TestContext, name: []const u8, - cross_target: std.zig.CrossTarget, + target: std.zig.CrossTarget, src: [:0]const u8, expected_errors: []const []const u8, - ) void {} + ) void { + var c = ctx.addZIRMulti(name, target); + c.addError(src, expected_errors); + } fn init(self: *TestContext) !void { const allocator = std.heap.page_allocator; self.* = .{ .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), - .zir_error_cases = std.ArrayList(ZIRErrorCase).init(allocator), .zir_cases = std.ArrayList(ZIRCase).init(allocator), }; } fn deinit(self: *TestContext) void { self.zir_cmp_output_cases.deinit(); - for (self.zir_error_cases.items) |e| { - self.zir_error_cases.allocator.free(e.expected_errors); - } - self.zir_error_cases.deinit(); for (self.zir_cases.items) |c| { for (c.updates.items) |u| { if (u.case == .Error) { @@ -240,12 +231,6 @@ pub const TestContext = struct { try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target); try std.testing.allocator_instance.validate(); } - for (self.zir_error_cases.items) |case| { - std.testing.base_allocator_instance.reset(); - const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target); - try self.runOneZIRErrorCase(std.testing.allocator, root_node, case, info.target); - try std.testing.allocator_instance.validate(); - } } fn runOneZIRCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: ZIRCase, target: std.Target) !void { @@ -419,103 +404,4 @@ pub const TestContext = struct { } } } - - fn runOneZIRErrorCase( - self: *TestContext, - allocator: *Allocator, - root_node: *std.Progress.Node, - case: ZIRErrorCase, - target: std.Target, - ) !void { - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - var prg_node = root_node.start(case.name, 1); - prg_node.activate(); - defer prg_node.end(); - - const tmp_src_path = "test-case.zir"; - try tmp.dir.writeFile(tmp_src_path, case.src); - - const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); - defer root_pkg.destroy(); - - var module = try Module.init(allocator, .{ - .target = target, - .output_mode = .Obj, - .optimize_mode = .Debug, - .bin_file_dir = tmp.dir, - .bin_file_path = "test-case.o", - .root_pkg = root_pkg, - }); - defer module.deinit(); - - var module_node = prg_node.start("parse/analysis/codegen", null); - module_node.activate(); - try module.update(); - module_node.end(); - var err: ?anyerror = null; - - var handled_errors = allocator.alloc(bool, case.expected_errors.len) catch unreachable; - defer allocator.free(handled_errors); - for (handled_errors) |*e| { - e.* = false; - } - - var all_errors = try module.getAllErrorsAlloc(); - defer all_errors.deinit(allocator); - for (all_errors.list) |e| { - var handled = false; - for (case.expected_errors) |ex, i| { - if (e.line == ex.line and e.column == ex.column and std.mem.eql(u8, ex.msg, e.msg)) { - if (handled_errors[i]) { - err = error.ErrorReceivedMultipleTimes; - std.debug.warn("Received error multiple times: {}\n", .{e.msg}); - } else { - handled_errors[i] = true; - handled = true; - } - break; - } - } - if (!handled) { - err = error.ErrorNotExpected; - std.debug.warn("Received an unexpected error: {}:{}: {}\n", .{ e.line, e.column, e.msg }); - } - } - - for (handled_errors) |e, i| { - if (!e) { - err = error.MissingExpectedError; - const er = case.expected_errors[i]; - std.debug.warn("Did not receive error: {}:{}: {}\n", .{ er.line, er.column, er.msg }); - } - } - - if (err) |e| { - return e; - } - } }; - -fn debugPrintErrors(src: []const u8, errors: var) void { - std.debug.warn("\n", .{}); - var nl = true; - var line: usize = 1; - for (src) |byte| { - if (nl) { - std.debug.warn("{: >3}| ", .{line}); - nl = false; - } - if (byte == '\n') { - nl = true; - line += 1; - } - std.debug.warn("{c}", .{byte}); - } - std.debug.warn("\n", .{}); - for (errors) |err_msg| { - const loc = std.zig.findLineColumn(src, err_msg.byte_offset); - std.debug.warn("{}:{}: error: {}\n", .{ loc.line + 1, loc.column + 1, err_msg.msg }); - } -} diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 78a00840a000ef6c877989a9e2f93a8bc8e53137..72c41f1230bfa0dcf4824ac3371f5bff237c4193 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,7 +18,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_][]const u8{"5:13: error: unrecognized identifier: %test"}); + , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); // TODO: fix this test // ctx.addZIRError("call with non-existent target", linux_x64, @@ -45,7 +45,7 @@ pub fn addCases(ctx: *TestContext) !void { \\@0 = str("_start") \\@1 = ref(@0) \\@2 = export(@1, @start) - , &[_][]const u8{"4:9: error: unable to call function with naked calling convention"}); + , &[_][]const u8{":4:9: error: unable to call function with naked calling convention"}); //try ctx.testCompileError( // \\export fn entry() void {} -- 2.54.0 From afec3e72f438fae41e493e3fd18ca62e5ef1c89b Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 20:42:22 -0400 Subject: [PATCH 031/295] Stage2/Testing: Enable another test --- src-self-hosted/test.zig | 13 +++++++++---- test/stage2/compile_errors.zig | 26 +++++++++++++------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index ad0bd4a640091d9890521fea0e4a2567160c1339..78bdcddf7153a5bcf65a9e2270e3e05277f60b29 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -110,20 +110,25 @@ pub const TestContext = struct { pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { + if (e[0] != ':') { + std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + } var cur = e[1..]; var line_index = std.mem.indexOf(u8, cur, ":"); if (line_index == null) { - std.debug.panic("Invalid test: error must be specified as ':line:column: error: msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); } const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); cur = cur[line_index.? + 1 ..]; const column_index = std.mem.indexOf(u8, cur, ":"); if (column_index == null) { - std.debug.panic("Invalid test: error must be specified as ':line:column: error: msg', found '{}'", .{e}); + std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); } const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); cur = cur[column_index.? + 2 ..]; - std.debug.assert(std.mem.eql(u8, cur[0..7], "error: ")); + if (!std.mem.eql(u8, cur[0..7], "error: ")) { + std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + } const msg = cur[7..]; if (line == 0 or column == 0) { @@ -312,7 +317,7 @@ pub const TestContext = struct { break; } } else { - std.debug.warn("{}\nUnexpected error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg }); + std.debug.warn("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg }); std.process.exit(1); } } diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 72c41f1230bfa0dcf4824ac3371f5bff237c4193..5484b6459aad4babbfe7236098edb987a7bc6473 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -18,20 +18,20 @@ pub fn addCases(ctx: *TestContext) !void { \\@start = fn(@start_fnty, { \\ %0 = call(%test, []) \\}) - , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); + // TODO: address inconsistency in this message and the one in the next test + , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); - // TODO: fix this test - // ctx.addZIRError("call with non-existent target", linux_x64, - // \\@noreturn = primitive(noreturn) - // \\ - // \\@start_fnty = fntype([], @noreturn, cc=Naked) - // \\@start = fn(@start_fnty, { - // \\ %0 = call(@notafunc, []) - // \\}) - // \\@0 = str("_start") - // \\@1 = ref(@0) - // \\@2 = export(@1, @start) - // , &[_][]const u8{"5:13:unrecognized identifier: @notafunc"}); + ctx.addZIRError("call with non-existent target", linux_x64, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %0 = call(@notafunc, []) + \\}) + \\@0 = str("_start") + \\@1 = ref(@0) + \\@2 = export(@1, @start) + , &[_][]const u8{":5:13: error: use of undeclared identifier 'notafunc'"}); // TODO: this error should occur at the call site, not the fntype decl ctx.addZIRError("call naked function", linux_x64, -- 2.54.0 From a99e61ebaa71aa74dfa95869ea8d02131ef9f696 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 15 Jun 2020 21:47:42 -0400 Subject: [PATCH 032/295] Stage2/Testing: Code cleanup --- src-self-hosted/test.zig | 20 +++++++++----------- test/stage2/compile_errors.zig | 6 ++++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 78bdcddf7153a5bcf65a9e2270e3e05277f60b29..4cf72ce481bb59e24a36ea83a531f649a171f11c 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -6,8 +6,7 @@ const zir = @import("zir.zig"); const Package = @import("Package.zig"); test "self-hosted" { - var ctx: TestContext = undefined; - try ctx.init(); + var ctx = TestContext.init(); defer ctx.deinit(); try @import("stage2_tests").addCases(&ctx); @@ -93,10 +92,11 @@ pub const TestContext = struct { name: []const u8, /// The platform the ZIR targets. For non-native platforms, an emulator /// such as QEMU is required for tests to complete. - /// target: std.zig.CrossTarget, updates: std.ArrayList(ZIRUpdate), + /// Adds a subcase in which the module is updated with new ZIR, and the + /// resulting ZIR is validated. pub fn addTransform(self: *ZIRCase, src: [:0]const u8, result: [:0]const u8) void { self.updates.append(.{ .src = src, @@ -104,9 +104,10 @@ pub const TestContext = struct { }) catch unreachable; } - /// TODO: document + /// Adds a subcase in which the module is updated with invalid ZIR, and + /// ensures that compilation fails for the expected reasons. /// - /// Errors must be specified in sequential order + /// Errors must be specified in sequential order. pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { @@ -194,9 +195,9 @@ pub const TestContext = struct { c.addError(src, expected_errors); } - fn init(self: *TestContext) !void { + fn init() TestContext { const allocator = std.heap.page_allocator; - self.* = .{ + return .{ .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), .zir_cases = std.ArrayList(ZIRCase).init(allocator), }; @@ -267,10 +268,7 @@ pub const TestContext = struct { }); defer module.deinit(); - for (case.updates.items) |s| { - // TODO: remove before committing. This is for ZLS ;) - const update: ZIRUpdate = s; - + for (case.updates.items) |update| { var update_node = prg_node.start("update", 4); update_node.activate(); defer update_node.end(); diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 5484b6459aad4babbfe7236098edb987a7bc6473..43c41aa3647535f761bb5d412bfac865591ff603 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -9,8 +9,6 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - // TODO: re-enable these tests. - // https://github.com/ziglang/zig/issues/1364 ctx.addZIRError("call undefined local", linux_x64, \\@noreturn = primitive(noreturn) \\ @@ -47,6 +45,10 @@ pub fn addCases(ctx: *TestContext) !void { \\@2 = export(@1, @start) , &[_][]const u8{":4:9: error: unable to call function with naked calling convention"}); + // TODO: re-enable these tests. + // https://github.com/ziglang/zig/issues/1364 + // TODO: add Zig AST -> ZIR testing pipeline + //try ctx.testCompileError( // \\export fn entry() void {} // \\export fn entry() void {} -- 2.54.0 From 8978fe94cfe08d4140bd968b1910348136b4b77d Mon Sep 17 00:00:00 2001 From: tgschultz Date: Thu, 11 Jun 2020 18:42:56 +0000 Subject: [PATCH 033/295] Overhauled leb128: handles integers < 8 bits incorrect overflow bugs fixed simplified *mem implementations added wrte* functions added thurough write/read testing --- lib/std/debug/leb128.zig | 332 ++++++++++++++++++++++++++------------- 1 file changed, 222 insertions(+), 110 deletions(-) diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig index ac278c4b1fc5af8237ab7dce90f0b3aa0c868791..f9e25d9ee784aab4eeb01a64dc0d04d590070d02 100644 --- a/lib/std/debug/leb128.zig +++ b/lib/std/debug/leb128.zig @@ -1,171 +1,198 @@ const std = @import("std"); const testing = std.testing; -pub fn readULEB128(comptime T: type, in_stream: var) !T { - const ShiftT = std.meta.Int(false, std.math.log2(T.bit_count)); +//@TODO: you can take *slice and alter slice.ptr +// make sign bits check more efficient +// add wrapper readLEB128 and write LEB128 that infer from type? +// or use assertions? - var result: T = 0; - var shift: usize = 0; +pub fn readULEB128(comptime T: type, reader: var) !T { + const U = if (T.bit_count < 8) u8 else T; + const ShiftT = std.math.Log2Int(U); + + const max_group = (U.bit_count + 6) / 7; + + var value = @as(U, 0); + var group = @as(ShiftT, 0); + + while (group < max_group) : (group += 1) { + const byte = try reader.readByte(); + var temp = @as(U, byte & 0x7f); + + if (@shlWithOverflow(U, temp, group * 7, &temp)) return error.Overflow; + + value |= temp; + if (byte & 0x80 == 0) break; + } else { + return error.Overflow; + } + + //only applies in the case that we extended to u8 + if (value > std.math.maxInt(T)) return error.Overflow; + + return @truncate(T, value); +} + +pub fn writeULEB128(writer: var, uint_value: var) !void { + const T = @TypeOf(uint_value); + const U = if (T.bit_count < 8) u8 else T; + var value = @intCast(U, uint_value); while (true) { - const byte = try in_stream.readByte(); - - if (shift > T.bit_count) - return error.Overflow; - - var operand: T = undefined; - if (@shlWithOverflow(T, byte & 0x7f, @intCast(ShiftT, shift), &operand)) - return error.Overflow; - - result |= operand; - - if ((byte & 0x80) == 0) - return result; - - shift += 7; + const byte = @truncate(u8, value & 0x7f); + value >>= 7; + if (value == 0) { + try writer.writeByte(byte); + break; + } else { + try writer.writeByte(byte | 0x80); + } } } pub fn readULEB128Mem(comptime T: type, ptr: *[*]const u8) !T { - const ShiftT = std.meta.Int(false, std.math.log2(T.bit_count)); + const max_group = (T.bit_count + 6) / 7; + var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); + const value = try readULEB128(T, buf.reader()); + ptr.* += @intCast(usize, try buf.getPos()); + return value; +} - var result: T = 0; - var shift: usize = 0; - var i: usize = 0; +pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { + const T = @TypeOf(uint_value); + const max_group = (T.bit_count + 6) / 7; + var buf = std.io.fixedBufferStream(ptr); + try writeULEB128(buf.writer(), uint_value); + return try buf.getPos(); +} - while (true) : (i += 1) { - const byte = ptr.*[i]; +pub fn readILEB128(comptime T: type, reader: var) !T { + const S = if (T.bit_count < 8) i8 else T; + const U = std.meta.Int(false, S.bit_count); + const ShiftU = std.math.Log2Int(U); - if (shift > T.bit_count) - return error.Overflow; + const max_group = (U.bit_count + 6) / 7; - var operand: T = undefined; - if (@shlWithOverflow(T, byte & 0x7f, @intCast(ShiftT, shift), &operand)) - return error.Overflow; + var value = @as(U, 0); + var group = @as(ShiftU, 0); - result |= operand; + while (group < max_group) : (group += 1) { + const byte = try reader.readByte(); + var temp = @as(U, byte & 0x7f); - if ((byte & 0x80) == 0) { - ptr.* += i + 1; - return result; + if (@shlWithOverflow(U, temp, group * 7, &temp)) { + //Overflow is ok so long as the sign bit is set and this is the last byte + if (byte & 0x80 != 0) return error.Overflow; + if (@bitCast(S, temp) >= 0) return error.Overflow; + + //and all the overflowed bits are 1 + const check_bits_shift = @intCast(u3, U.bit_count - @as(u16, group * 7)); + const check_bits_remaining = 7 - check_bits_shift; + const check_bits = byte >> check_bits_shift; + const num_consecutive_ones = @ctz(u8, ~check_bits); + if (num_consecutive_ones < check_bits_remaining) return error.Overflow; } - shift += 7; + value |= temp; + if (byte & 0x80 == 0) { + if (byte & 0x40 != 0 and group + 1 < max_group) { + value |= @bitCast(U, @as(S, -1)) << ((group + 1) * 7); + } + break; + } + } else { + return error.Overflow; } + + //Only applies if we extended to i8 + if (@bitCast(S, value) > std.math.maxInt(T) or @bitCast(S, value) < std.math.minInt(T)) return error.Overflow; + + return @truncate(T, @bitCast(S, value)); } -pub fn readILEB128(comptime T: type, in_stream: var) !T { - const UT = std.meta.Int(false, T.bit_count); - const ShiftT = std.meta.Int(false, std.math.log2(T.bit_count)); +pub fn writeILEB128(writer: var, int_value: var) !void { + const T = @TypeOf(int_value); + const S = if (T.bit_count < 8) i8 else T; + const U = std.meta.Int(false, S.bit_count); - var result: UT = 0; - var shift: usize = 0; + var value = @intCast(S, int_value); while (true) { - const byte: u8 = try in_stream.readByte(); - - if (shift > T.bit_count) - return error.Overflow; - - var operand: UT = undefined; - if (@shlWithOverflow(UT, @as(UT, byte & 0x7f), @intCast(ShiftT, shift), &operand)) { - if (byte != 0x7f) - return error.Overflow; - } - - result |= operand; - - shift += 7; - - if ((byte & 0x80) == 0) { - if (shift < T.bit_count and (byte & 0x40) != 0) { - result |= @bitCast(UT, @intCast(T, -1)) << @intCast(ShiftT, shift); - } - return @bitCast(T, result); + const uvalue = @bitCast(U, value); + const byte = @truncate(u8, uvalue); + value >>= 6; + if (value == -1 or value == 0) { + try writer.writeByte(byte & 0x7F); + break; + } else { + value >>= 1; + try writer.writeByte(byte | 0x80); } } } pub fn readILEB128Mem(comptime T: type, ptr: *[*]const u8) !T { - const UT = std.meta.Int(false, T.bit_count); - const ShiftT = std.meta.Int(false, std.math.log2(T.bit_count)); - - var result: UT = 0; - var shift: usize = 0; - var i: usize = 0; - - while (true) : (i += 1) { - const byte = ptr.*[i]; - - if (shift > T.bit_count) - return error.Overflow; - - var operand: UT = undefined; - if (@shlWithOverflow(UT, @as(UT, byte & 0x7f), @intCast(ShiftT, shift), &operand)) { - if (byte != 0x7f) - return error.Overflow; - } - - result |= operand; - - shift += 7; + const max_group = (T.bit_count + 6) / 7; + var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); + const value = try readILEB128(T, buf.reader()); + ptr.* += @intCast(usize, try buf.getPos()); + return value; +} - if ((byte & 0x80) == 0) { - if (shift < T.bit_count and (byte & 0x40) != 0) { - result |= @bitCast(UT, @intCast(T, -1)) << @intCast(ShiftT, shift); - } - ptr.* += i + 1; - return @bitCast(T, result); - } - } +pub fn writeILEB128Mem(ptr: []u8, int_value: var) !usize { + const T = @TypeOf(int_value); + var buf = std.io.fixedBufferStream(ptr); + try writeILEB128(buf.writer(), int_value); + return try buf.getPos(); } +//tests fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T { - var in_stream = std.io.fixedBufferStream(encoded); - return try readILEB128(T, in_stream.inStream()); + var reader = std.io.fixedBufferStream(encoded); + return try readILEB128(T, reader.reader()); } fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T { - var in_stream = std.io.fixedBufferStream(encoded); - return try readULEB128(T, in_stream.inStream()); + var reader = std.io.fixedBufferStream(encoded); + return try readULEB128(T, reader.reader()); } fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { - var in_stream = std.io.fixedBufferStream(encoded); - const v1 = readILEB128(T, in_stream.inStream()); + var reader = std.io.fixedBufferStream(encoded); + const v1 = try readILEB128(T, reader.reader()); var in_ptr = encoded.ptr; - const v2 = readILEB128Mem(T, &in_ptr); + const v2 = try readILEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; } fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { - var in_stream = std.io.fixedBufferStream(encoded); - const v1 = readULEB128(T, in_stream.inStream()); + var reader = std.io.fixedBufferStream(encoded); + const v1 = try readULEB128(T, reader.reader()); var in_ptr = encoded.ptr; - const v2 = readULEB128Mem(T, &in_ptr); + const v2 = try readULEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; } -fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) void { - var in_stream = std.io.fixedBufferStream(encoded); +fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { + var reader = std.io.fixedBufferStream(encoded); var in_ptr = encoded.ptr; var i: usize = 0; while (i < N) : (i += 1) { - const v1 = readILEB128(T, in_stream.inStream()); - const v2 = readILEB128Mem(T, &in_ptr); + const v1 = try readILEB128(T, reader.reader()); + const v2 = try readILEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); } } -fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) void { - var in_stream = std.io.fixedBufferStream(encoded); +fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { + var reader = std.io.fixedBufferStream(encoded); var in_ptr = encoded.ptr; var i: usize = 0; while (i < N) : (i += 1) { - const v1 = readULEB128(T, in_stream.inStream()); - const v2 = readULEB128Mem(T, &in_ptr); + const v1 = try readULEB128(T, reader.reader()); + const v2 = try readULEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); } } @@ -212,7 +239,7 @@ test "deserialize signed LEB128" { testing.expect((try test_read_ileb128(i64, "\x80\x81\x80\x00")) == 0x80); // Decode sequence of SLEB128 values - test_read_ileb128_seq(i64, 4, "\x81\x01\x3f\x80\x7f\x80\x80\x80\x00"); + try test_read_ileb128_seq(i64, 4, "\x81\x01\x3f\x80\x7f\x80\x80\x80\x00"); } test "deserialize unsigned LEB128" { @@ -252,5 +279,90 @@ test "deserialize unsigned LEB128" { testing.expect((try test_read_uleb128(u64, "\x80\x81\x80\x00")) == 0x80); // Decode sequence of ULEB128 values - test_read_uleb128_seq(u64, 4, "\x81\x01\x3f\x80\x7f\x80\x80\x80\x00"); + try test_read_uleb128_seq(u64, 4, "\x81\x01\x3f\x80\x7f\x80\x80\x80\x00"); +} + +fn test_write_leb128(value: var) !void { + const T = @TypeOf(value); + + if (T.bit_count == 0) std.debug.warn("{}\n", .{@typeName(T)}); + + const writeStream = if (T.is_signed) writeILEB128 else writeULEB128; + const writeMem = if (T.is_signed) writeILEB128Mem else writeULEB128Mem; + const readStream = if (T.is_signed) readILEB128 else readULEB128; + const readMem = if (T.is_signed) readILEB128Mem else readULEB128Mem; + + //decode to a larger bit size too, to ensure sign extension + // is working as expected + const larger_type_bits = ((T.bit_count + 8) / 8) * 8; + const B = std.meta.Int(T.is_signed, larger_type_bits); + const max_groups = if (T.bit_count == 0) 1 else (T.bit_count + 6) / 7; + + var buf: [max_groups]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + + //stream write + try writeStream(fbs.writer(), value); + const w1_pos = fbs.pos; + testing.expect(w1_pos > 0); + + //stream read + fbs.pos = 0; + const sr = try readStream(T, fbs.reader()); + testing.expect(fbs.pos == w1_pos); + testing.expect(sr == value); + + //bigger type stream read + fbs.pos = 0; + const bsr = try readStream(B, fbs.reader()); + testing.expect(fbs.pos == w1_pos); + testing.expect(bsr == value); + + //mem write + const w2_pos = try writeMem(&buf, value); + testing.expect(w2_pos == w1_pos); + + //mem read + var buf_ref: []u8 = buf[0..]; + const mr = try readMem(T, &buf_ref.ptr); + testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); + testing.expect(mr == value); + + //bigger type mem read + buf_ref = buf[0..]; + const bmr = try readMem(T, &buf_ref.ptr); + testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); + testing.expect(bmr == value); +} + +test "serialize unsigned LEB128" { + const max_bits = 18; + + comptime var t = 0; + inline while (t <= max_bits) : (t += 1) { + const T = std.meta.Int(false, t); + const min = std.math.minInt(T); + const max = std.math.maxInt(T); + var i = @as(std.meta.Int(false, T.bit_count + 1), min); + + while (i <= max) : (i += 1) try test_write_leb128(@intCast(T, i)); + } +} + +test "serialize signed LEB128" { + //explicitly test i0 because starting `t` at 0 + // will break the while loop + try test_write_leb128(@as(i0, 0)); + + const max_bits = 18; + + comptime var t = 1; + inline while (t <= max_bits) : (t += 1) { + const T = std.meta.Int(true, t); + const min = std.math.minInt(T); + const max = std.math.maxInt(T); + var i = @as(std.meta.Int(true, T.bit_count + 1), min); + + while (i <= max) : (i += 1) try test_write_leb128(@intCast(T, i)); + } } -- 2.54.0 From 7f24860737fba2b40c375b7c9fb37fe694bfa539 Mon Sep 17 00:00:00 2001 From: tgschultz Date: Thu, 11 Jun 2020 20:53:25 +0000 Subject: [PATCH 034/295] Code cleanup, documentation added, read*Mem functions now take *[]const u8 --- lib/std/debug/leb128.zig | 79 +++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig index f9e25d9ee784aab4eeb01a64dc0d04d590070d02..76e76d8e4e65b3a46278c62b8077fada2e8ec2cf 100644 --- a/lib/std/debug/leb128.zig +++ b/lib/std/debug/leb128.zig @@ -1,11 +1,8 @@ const std = @import("std"); const testing = std.testing; -//@TODO: you can take *slice and alter slice.ptr -// make sign bits check more efficient -// add wrapper readLEB128 and write LEB128 that infer from type? -// or use assertions? - +///Read a single unsigned LEB128 value from the given reader as type T, +/// or error.Overflow if the value cannot fit. pub fn readULEB128(comptime T: type, reader: var) !T { const U = if (T.bit_count < 8) u8 else T; const ShiftT = std.math.Log2Int(U); @@ -28,11 +25,14 @@ pub fn readULEB128(comptime T: type, reader: var) !T { } //only applies in the case that we extended to u8 - if (value > std.math.maxInt(T)) return error.Overflow; + if (U != T) { + if (value > std.math.maxInt(T)) return error.Overflow; + } return @truncate(T, value); } +///Write a single unsigned integer as unsigned LEB128 to the given writer. pub fn writeULEB128(writer: var, uint_value: var) !void { const T = @TypeOf(uint_value); const U = if (T.bit_count < 8) u8 else T; @@ -50,22 +50,27 @@ pub fn writeULEB128(writer: var, uint_value: var) !void { } } -pub fn readULEB128Mem(comptime T: type, ptr: *[*]const u8) !T { - const max_group = (T.bit_count + 6) / 7; - var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); +///Read a single unsinged integer from the given memory as type T. +/// The provided slice reference will be updated to point to the byte after the last byte read. +pub fn readULEB128Mem(comptime T: type, ptr: *[]const u8) !T { + var buf = std.io.fixedBufferStream(ptr.*); const value = try readULEB128(T, buf.reader()); - ptr.* += @intCast(usize, try buf.getPos()); + ptr.*.ptr += buf.pos; return value; } +///Write a single unsigned LEB128 integer to the given memory as unsigned LEB128, +/// returning the number of bytes written. pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { const T = @TypeOf(uint_value); const max_group = (T.bit_count + 6) / 7; var buf = std.io.fixedBufferStream(ptr); try writeULEB128(buf.writer(), uint_value); - return try buf.getPos(); + return buf.pos; } +///Read a single signed LEB128 value from the given reader as type T, +/// or error.Overflow if the value cannot fit. pub fn readILEB128(comptime T: type, reader: var) !T { const S = if (T.bit_count < 8) i8 else T; const U = std.meta.Int(false, S.bit_count); @@ -80,23 +85,24 @@ pub fn readILEB128(comptime T: type, reader: var) !T { const byte = try reader.readByte(); var temp = @as(U, byte & 0x7f); - if (@shlWithOverflow(U, temp, group * 7, &temp)) { + const shift = group * 7; + if (@shlWithOverflow(U, temp, shift, &temp)) { //Overflow is ok so long as the sign bit is set and this is the last byte if (byte & 0x80 != 0) return error.Overflow; if (@bitCast(S, temp) >= 0) return error.Overflow; //and all the overflowed bits are 1 - const check_bits_shift = @intCast(u3, U.bit_count - @as(u16, group * 7)); - const check_bits_remaining = 7 - check_bits_shift; - const check_bits = byte >> check_bits_shift; - const num_consecutive_ones = @ctz(u8, ~check_bits); - if (num_consecutive_ones < check_bits_remaining) return error.Overflow; + const remaining_shift = @intCast(u3, U.bit_count - @as(u16, shift)); + const remaining_bits = @bitCast(i8, byte | 0x80) >> remaining_shift; + if (remaining_bits != -1) return error.Overflow; } value |= temp; if (byte & 0x80 == 0) { - if (byte & 0x40 != 0 and group + 1 < max_group) { - value |= @bitCast(U, @as(S, -1)) << ((group + 1) * 7); + const needs_sign_ext = group + 1 < max_group; + if (byte & 0x40 != 0 and needs_sign_ext) { + const ones = @as(S, -1); + value |= @bitCast(U, ones) << (shift + 7); } break; } @@ -104,12 +110,16 @@ pub fn readILEB128(comptime T: type, reader: var) !T { return error.Overflow; } + const result = @bitCast(S, value); //Only applies if we extended to i8 - if (@bitCast(S, value) > std.math.maxInt(T) or @bitCast(S, value) < std.math.minInt(T)) return error.Overflow; + if (S != T) { + if (result > std.math.maxInt(T) or result < std.math.minInt(T)) return error.Overflow; + } - return @truncate(T, @bitCast(S, value)); + return @truncate(T, result); } +///Write a single signed integer as signed LEB128 to the given writer. pub fn writeILEB128(writer: var, int_value: var) !void { const T = @TypeOf(int_value); const S = if (T.bit_count < 8) i8 else T; @@ -131,19 +141,22 @@ pub fn writeILEB128(writer: var, int_value: var) !void { } } -pub fn readILEB128Mem(comptime T: type, ptr: *[*]const u8) !T { - const max_group = (T.bit_count + 6) / 7; - var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); +///Read a single singed LEB128 integer from the given memory as type T. +/// The provided slice reference will be updated to point to the byte after the last byte read. +pub fn readILEB128Mem(comptime T: type, ptr: *[]const u8) !T { + var buf = std.io.fixedBufferStream(ptr.*); const value = try readILEB128(T, buf.reader()); - ptr.* += @intCast(usize, try buf.getPos()); + ptr.*.ptr += buf.pos; return value; } +///Write a single signed LEB128 integer to the given memory as unsigned LEB128, +/// returning the number of bytes written. pub fn writeILEB128Mem(ptr: []u8, int_value: var) !usize { const T = @TypeOf(int_value); var buf = std.io.fixedBufferStream(ptr); try writeILEB128(buf.writer(), int_value); - return try buf.getPos(); + return buf.pos; } //tests @@ -160,7 +173,7 @@ fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readILEB128(T, reader.reader()); - var in_ptr = encoded.ptr; + var in_ptr = encoded; const v2 = try readILEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -169,7 +182,7 @@ fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readULEB128(T, reader.reader()); - var in_ptr = encoded.ptr; + var in_ptr = encoded; const v2 = try readULEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -177,7 +190,7 @@ fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded.ptr; + var in_ptr = encoded; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readILEB128(T, reader.reader()); @@ -188,7 +201,7 @@ fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded.ptr; + var in_ptr = encoded; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readULEB128(T, reader.reader()); @@ -285,8 +298,6 @@ test "deserialize unsigned LEB128" { fn test_write_leb128(value: var) !void { const T = @TypeOf(value); - if (T.bit_count == 0) std.debug.warn("{}\n", .{@typeName(T)}); - const writeStream = if (T.is_signed) writeILEB128 else writeULEB128; const writeMem = if (T.is_signed) writeILEB128Mem else writeULEB128Mem; const readStream = if (T.is_signed) readILEB128 else readULEB128; @@ -324,13 +335,13 @@ fn test_write_leb128(value: var) !void { //mem read var buf_ref: []u8 = buf[0..]; - const mr = try readMem(T, &buf_ref.ptr); + const mr = try readMem(T, &buf_ref); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(mr == value); //bigger type mem read buf_ref = buf[0..]; - const bmr = try readMem(T, &buf_ref.ptr); + const bmr = try readMem(T, &buf_ref); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(bmr == value); } -- 2.54.0 From e94eba5df552abf63ee55846ac62b1e0f5a80098 Mon Sep 17 00:00:00 2001 From: tgschultz Date: Thu, 11 Jun 2020 18:42:56 +0000 Subject: [PATCH 035/295] Overhauled leb128: handles integers < 8 bits incorrect overflow bugs fixed simplified *mem implementations added wrte* functions added thurough write/read testing --- lib/std/debug/leb128.zig | 79 +++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig index 76e76d8e4e65b3a46278c62b8077fada2e8ec2cf..f9e25d9ee784aab4eeb01a64dc0d04d590070d02 100644 --- a/lib/std/debug/leb128.zig +++ b/lib/std/debug/leb128.zig @@ -1,8 +1,11 @@ const std = @import("std"); const testing = std.testing; -///Read a single unsigned LEB128 value from the given reader as type T, -/// or error.Overflow if the value cannot fit. +//@TODO: you can take *slice and alter slice.ptr +// make sign bits check more efficient +// add wrapper readLEB128 and write LEB128 that infer from type? +// or use assertions? + pub fn readULEB128(comptime T: type, reader: var) !T { const U = if (T.bit_count < 8) u8 else T; const ShiftT = std.math.Log2Int(U); @@ -25,14 +28,11 @@ pub fn readULEB128(comptime T: type, reader: var) !T { } //only applies in the case that we extended to u8 - if (U != T) { - if (value > std.math.maxInt(T)) return error.Overflow; - } + if (value > std.math.maxInt(T)) return error.Overflow; return @truncate(T, value); } -///Write a single unsigned integer as unsigned LEB128 to the given writer. pub fn writeULEB128(writer: var, uint_value: var) !void { const T = @TypeOf(uint_value); const U = if (T.bit_count < 8) u8 else T; @@ -50,27 +50,22 @@ pub fn writeULEB128(writer: var, uint_value: var) !void { } } -///Read a single unsinged integer from the given memory as type T. -/// The provided slice reference will be updated to point to the byte after the last byte read. -pub fn readULEB128Mem(comptime T: type, ptr: *[]const u8) !T { - var buf = std.io.fixedBufferStream(ptr.*); +pub fn readULEB128Mem(comptime T: type, ptr: *[*]const u8) !T { + const max_group = (T.bit_count + 6) / 7; + var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); const value = try readULEB128(T, buf.reader()); - ptr.*.ptr += buf.pos; + ptr.* += @intCast(usize, try buf.getPos()); return value; } -///Write a single unsigned LEB128 integer to the given memory as unsigned LEB128, -/// returning the number of bytes written. pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { const T = @TypeOf(uint_value); const max_group = (T.bit_count + 6) / 7; var buf = std.io.fixedBufferStream(ptr); try writeULEB128(buf.writer(), uint_value); - return buf.pos; + return try buf.getPos(); } -///Read a single signed LEB128 value from the given reader as type T, -/// or error.Overflow if the value cannot fit. pub fn readILEB128(comptime T: type, reader: var) !T { const S = if (T.bit_count < 8) i8 else T; const U = std.meta.Int(false, S.bit_count); @@ -85,24 +80,23 @@ pub fn readILEB128(comptime T: type, reader: var) !T { const byte = try reader.readByte(); var temp = @as(U, byte & 0x7f); - const shift = group * 7; - if (@shlWithOverflow(U, temp, shift, &temp)) { + if (@shlWithOverflow(U, temp, group * 7, &temp)) { //Overflow is ok so long as the sign bit is set and this is the last byte if (byte & 0x80 != 0) return error.Overflow; if (@bitCast(S, temp) >= 0) return error.Overflow; //and all the overflowed bits are 1 - const remaining_shift = @intCast(u3, U.bit_count - @as(u16, shift)); - const remaining_bits = @bitCast(i8, byte | 0x80) >> remaining_shift; - if (remaining_bits != -1) return error.Overflow; + const check_bits_shift = @intCast(u3, U.bit_count - @as(u16, group * 7)); + const check_bits_remaining = 7 - check_bits_shift; + const check_bits = byte >> check_bits_shift; + const num_consecutive_ones = @ctz(u8, ~check_bits); + if (num_consecutive_ones < check_bits_remaining) return error.Overflow; } value |= temp; if (byte & 0x80 == 0) { - const needs_sign_ext = group + 1 < max_group; - if (byte & 0x40 != 0 and needs_sign_ext) { - const ones = @as(S, -1); - value |= @bitCast(U, ones) << (shift + 7); + if (byte & 0x40 != 0 and group + 1 < max_group) { + value |= @bitCast(U, @as(S, -1)) << ((group + 1) * 7); } break; } @@ -110,16 +104,12 @@ pub fn readILEB128(comptime T: type, reader: var) !T { return error.Overflow; } - const result = @bitCast(S, value); //Only applies if we extended to i8 - if (S != T) { - if (result > std.math.maxInt(T) or result < std.math.minInt(T)) return error.Overflow; - } + if (@bitCast(S, value) > std.math.maxInt(T) or @bitCast(S, value) < std.math.minInt(T)) return error.Overflow; - return @truncate(T, result); + return @truncate(T, @bitCast(S, value)); } -///Write a single signed integer as signed LEB128 to the given writer. pub fn writeILEB128(writer: var, int_value: var) !void { const T = @TypeOf(int_value); const S = if (T.bit_count < 8) i8 else T; @@ -141,22 +131,19 @@ pub fn writeILEB128(writer: var, int_value: var) !void { } } -///Read a single singed LEB128 integer from the given memory as type T. -/// The provided slice reference will be updated to point to the byte after the last byte read. -pub fn readILEB128Mem(comptime T: type, ptr: *[]const u8) !T { - var buf = std.io.fixedBufferStream(ptr.*); +pub fn readILEB128Mem(comptime T: type, ptr: *[*]const u8) !T { + const max_group = (T.bit_count + 6) / 7; + var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); const value = try readILEB128(T, buf.reader()); - ptr.*.ptr += buf.pos; + ptr.* += @intCast(usize, try buf.getPos()); return value; } -///Write a single signed LEB128 integer to the given memory as unsigned LEB128, -/// returning the number of bytes written. pub fn writeILEB128Mem(ptr: []u8, int_value: var) !usize { const T = @TypeOf(int_value); var buf = std.io.fixedBufferStream(ptr); try writeILEB128(buf.writer(), int_value); - return buf.pos; + return try buf.getPos(); } //tests @@ -173,7 +160,7 @@ fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readILEB128(T, reader.reader()); - var in_ptr = encoded; + var in_ptr = encoded.ptr; const v2 = try readILEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -182,7 +169,7 @@ fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readULEB128(T, reader.reader()); - var in_ptr = encoded; + var in_ptr = encoded.ptr; const v2 = try readULEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -190,7 +177,7 @@ fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded; + var in_ptr = encoded.ptr; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readILEB128(T, reader.reader()); @@ -201,7 +188,7 @@ fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded; + var in_ptr = encoded.ptr; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readULEB128(T, reader.reader()); @@ -298,6 +285,8 @@ test "deserialize unsigned LEB128" { fn test_write_leb128(value: var) !void { const T = @TypeOf(value); + if (T.bit_count == 0) std.debug.warn("{}\n", .{@typeName(T)}); + const writeStream = if (T.is_signed) writeILEB128 else writeULEB128; const writeMem = if (T.is_signed) writeILEB128Mem else writeULEB128Mem; const readStream = if (T.is_signed) readILEB128 else readULEB128; @@ -335,13 +324,13 @@ fn test_write_leb128(value: var) !void { //mem read var buf_ref: []u8 = buf[0..]; - const mr = try readMem(T, &buf_ref); + const mr = try readMem(T, &buf_ref.ptr); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(mr == value); //bigger type mem read buf_ref = buf[0..]; - const bmr = try readMem(T, &buf_ref); + const bmr = try readMem(T, &buf_ref.ptr); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(bmr == value); } -- 2.54.0 From 928d3ee9ea7f1a7eb17b1ec3a8ec36559e1e9968 Mon Sep 17 00:00:00 2001 From: tgschultz Date: Thu, 11 Jun 2020 20:53:25 +0000 Subject: [PATCH 036/295] Code cleanup, documentation added, read*Mem functions now take *[]const u8 --- lib/std/debug/leb128.zig | 79 +++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig index f9e25d9ee784aab4eeb01a64dc0d04d590070d02..76e76d8e4e65b3a46278c62b8077fada2e8ec2cf 100644 --- a/lib/std/debug/leb128.zig +++ b/lib/std/debug/leb128.zig @@ -1,11 +1,8 @@ const std = @import("std"); const testing = std.testing; -//@TODO: you can take *slice and alter slice.ptr -// make sign bits check more efficient -// add wrapper readLEB128 and write LEB128 that infer from type? -// or use assertions? - +///Read a single unsigned LEB128 value from the given reader as type T, +/// or error.Overflow if the value cannot fit. pub fn readULEB128(comptime T: type, reader: var) !T { const U = if (T.bit_count < 8) u8 else T; const ShiftT = std.math.Log2Int(U); @@ -28,11 +25,14 @@ pub fn readULEB128(comptime T: type, reader: var) !T { } //only applies in the case that we extended to u8 - if (value > std.math.maxInt(T)) return error.Overflow; + if (U != T) { + if (value > std.math.maxInt(T)) return error.Overflow; + } return @truncate(T, value); } +///Write a single unsigned integer as unsigned LEB128 to the given writer. pub fn writeULEB128(writer: var, uint_value: var) !void { const T = @TypeOf(uint_value); const U = if (T.bit_count < 8) u8 else T; @@ -50,22 +50,27 @@ pub fn writeULEB128(writer: var, uint_value: var) !void { } } -pub fn readULEB128Mem(comptime T: type, ptr: *[*]const u8) !T { - const max_group = (T.bit_count + 6) / 7; - var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); +///Read a single unsinged integer from the given memory as type T. +/// The provided slice reference will be updated to point to the byte after the last byte read. +pub fn readULEB128Mem(comptime T: type, ptr: *[]const u8) !T { + var buf = std.io.fixedBufferStream(ptr.*); const value = try readULEB128(T, buf.reader()); - ptr.* += @intCast(usize, try buf.getPos()); + ptr.*.ptr += buf.pos; return value; } +///Write a single unsigned LEB128 integer to the given memory as unsigned LEB128, +/// returning the number of bytes written. pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { const T = @TypeOf(uint_value); const max_group = (T.bit_count + 6) / 7; var buf = std.io.fixedBufferStream(ptr); try writeULEB128(buf.writer(), uint_value); - return try buf.getPos(); + return buf.pos; } +///Read a single signed LEB128 value from the given reader as type T, +/// or error.Overflow if the value cannot fit. pub fn readILEB128(comptime T: type, reader: var) !T { const S = if (T.bit_count < 8) i8 else T; const U = std.meta.Int(false, S.bit_count); @@ -80,23 +85,24 @@ pub fn readILEB128(comptime T: type, reader: var) !T { const byte = try reader.readByte(); var temp = @as(U, byte & 0x7f); - if (@shlWithOverflow(U, temp, group * 7, &temp)) { + const shift = group * 7; + if (@shlWithOverflow(U, temp, shift, &temp)) { //Overflow is ok so long as the sign bit is set and this is the last byte if (byte & 0x80 != 0) return error.Overflow; if (@bitCast(S, temp) >= 0) return error.Overflow; //and all the overflowed bits are 1 - const check_bits_shift = @intCast(u3, U.bit_count - @as(u16, group * 7)); - const check_bits_remaining = 7 - check_bits_shift; - const check_bits = byte >> check_bits_shift; - const num_consecutive_ones = @ctz(u8, ~check_bits); - if (num_consecutive_ones < check_bits_remaining) return error.Overflow; + const remaining_shift = @intCast(u3, U.bit_count - @as(u16, shift)); + const remaining_bits = @bitCast(i8, byte | 0x80) >> remaining_shift; + if (remaining_bits != -1) return error.Overflow; } value |= temp; if (byte & 0x80 == 0) { - if (byte & 0x40 != 0 and group + 1 < max_group) { - value |= @bitCast(U, @as(S, -1)) << ((group + 1) * 7); + const needs_sign_ext = group + 1 < max_group; + if (byte & 0x40 != 0 and needs_sign_ext) { + const ones = @as(S, -1); + value |= @bitCast(U, ones) << (shift + 7); } break; } @@ -104,12 +110,16 @@ pub fn readILEB128(comptime T: type, reader: var) !T { return error.Overflow; } + const result = @bitCast(S, value); //Only applies if we extended to i8 - if (@bitCast(S, value) > std.math.maxInt(T) or @bitCast(S, value) < std.math.minInt(T)) return error.Overflow; + if (S != T) { + if (result > std.math.maxInt(T) or result < std.math.minInt(T)) return error.Overflow; + } - return @truncate(T, @bitCast(S, value)); + return @truncate(T, result); } +///Write a single signed integer as signed LEB128 to the given writer. pub fn writeILEB128(writer: var, int_value: var) !void { const T = @TypeOf(int_value); const S = if (T.bit_count < 8) i8 else T; @@ -131,19 +141,22 @@ pub fn writeILEB128(writer: var, int_value: var) !void { } } -pub fn readILEB128Mem(comptime T: type, ptr: *[*]const u8) !T { - const max_group = (T.bit_count + 6) / 7; - var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); +///Read a single singed LEB128 integer from the given memory as type T. +/// The provided slice reference will be updated to point to the byte after the last byte read. +pub fn readILEB128Mem(comptime T: type, ptr: *[]const u8) !T { + var buf = std.io.fixedBufferStream(ptr.*); const value = try readILEB128(T, buf.reader()); - ptr.* += @intCast(usize, try buf.getPos()); + ptr.*.ptr += buf.pos; return value; } +///Write a single signed LEB128 integer to the given memory as unsigned LEB128, +/// returning the number of bytes written. pub fn writeILEB128Mem(ptr: []u8, int_value: var) !usize { const T = @TypeOf(int_value); var buf = std.io.fixedBufferStream(ptr); try writeILEB128(buf.writer(), int_value); - return try buf.getPos(); + return buf.pos; } //tests @@ -160,7 +173,7 @@ fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readILEB128(T, reader.reader()); - var in_ptr = encoded.ptr; + var in_ptr = encoded; const v2 = try readILEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -169,7 +182,7 @@ fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readULEB128(T, reader.reader()); - var in_ptr = encoded.ptr; + var in_ptr = encoded; const v2 = try readULEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -177,7 +190,7 @@ fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded.ptr; + var in_ptr = encoded; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readILEB128(T, reader.reader()); @@ -188,7 +201,7 @@ fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded.ptr; + var in_ptr = encoded; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readULEB128(T, reader.reader()); @@ -285,8 +298,6 @@ test "deserialize unsigned LEB128" { fn test_write_leb128(value: var) !void { const T = @TypeOf(value); - if (T.bit_count == 0) std.debug.warn("{}\n", .{@typeName(T)}); - const writeStream = if (T.is_signed) writeILEB128 else writeULEB128; const writeMem = if (T.is_signed) writeILEB128Mem else writeULEB128Mem; const readStream = if (T.is_signed) readILEB128 else readULEB128; @@ -324,13 +335,13 @@ fn test_write_leb128(value: var) !void { //mem read var buf_ref: []u8 = buf[0..]; - const mr = try readMem(T, &buf_ref.ptr); + const mr = try readMem(T, &buf_ref); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(mr == value); //bigger type mem read buf_ref = buf[0..]; - const bmr = try readMem(T, &buf_ref.ptr); + const bmr = try readMem(T, &buf_ref); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(bmr == value); } -- 2.54.0 From 00ec81b0dc41e113624152cdb268d06f73e6f88c Mon Sep 17 00:00:00 2001 From: tgschultz Date: Thu, 11 Jun 2020 18:42:56 +0000 Subject: [PATCH 037/295] Overhauled leb128: handles integers < 8 bits incorrect overflow bugs fixed simplified *mem implementations added wrte* functions added thurough write/read testing --- lib/std/debug/leb128.zig | 79 +++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig index 76e76d8e4e65b3a46278c62b8077fada2e8ec2cf..f9e25d9ee784aab4eeb01a64dc0d04d590070d02 100644 --- a/lib/std/debug/leb128.zig +++ b/lib/std/debug/leb128.zig @@ -1,8 +1,11 @@ const std = @import("std"); const testing = std.testing; -///Read a single unsigned LEB128 value from the given reader as type T, -/// or error.Overflow if the value cannot fit. +//@TODO: you can take *slice and alter slice.ptr +// make sign bits check more efficient +// add wrapper readLEB128 and write LEB128 that infer from type? +// or use assertions? + pub fn readULEB128(comptime T: type, reader: var) !T { const U = if (T.bit_count < 8) u8 else T; const ShiftT = std.math.Log2Int(U); @@ -25,14 +28,11 @@ pub fn readULEB128(comptime T: type, reader: var) !T { } //only applies in the case that we extended to u8 - if (U != T) { - if (value > std.math.maxInt(T)) return error.Overflow; - } + if (value > std.math.maxInt(T)) return error.Overflow; return @truncate(T, value); } -///Write a single unsigned integer as unsigned LEB128 to the given writer. pub fn writeULEB128(writer: var, uint_value: var) !void { const T = @TypeOf(uint_value); const U = if (T.bit_count < 8) u8 else T; @@ -50,27 +50,22 @@ pub fn writeULEB128(writer: var, uint_value: var) !void { } } -///Read a single unsinged integer from the given memory as type T. -/// The provided slice reference will be updated to point to the byte after the last byte read. -pub fn readULEB128Mem(comptime T: type, ptr: *[]const u8) !T { - var buf = std.io.fixedBufferStream(ptr.*); +pub fn readULEB128Mem(comptime T: type, ptr: *[*]const u8) !T { + const max_group = (T.bit_count + 6) / 7; + var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); const value = try readULEB128(T, buf.reader()); - ptr.*.ptr += buf.pos; + ptr.* += @intCast(usize, try buf.getPos()); return value; } -///Write a single unsigned LEB128 integer to the given memory as unsigned LEB128, -/// returning the number of bytes written. pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { const T = @TypeOf(uint_value); const max_group = (T.bit_count + 6) / 7; var buf = std.io.fixedBufferStream(ptr); try writeULEB128(buf.writer(), uint_value); - return buf.pos; + return try buf.getPos(); } -///Read a single signed LEB128 value from the given reader as type T, -/// or error.Overflow if the value cannot fit. pub fn readILEB128(comptime T: type, reader: var) !T { const S = if (T.bit_count < 8) i8 else T; const U = std.meta.Int(false, S.bit_count); @@ -85,24 +80,23 @@ pub fn readILEB128(comptime T: type, reader: var) !T { const byte = try reader.readByte(); var temp = @as(U, byte & 0x7f); - const shift = group * 7; - if (@shlWithOverflow(U, temp, shift, &temp)) { + if (@shlWithOverflow(U, temp, group * 7, &temp)) { //Overflow is ok so long as the sign bit is set and this is the last byte if (byte & 0x80 != 0) return error.Overflow; if (@bitCast(S, temp) >= 0) return error.Overflow; //and all the overflowed bits are 1 - const remaining_shift = @intCast(u3, U.bit_count - @as(u16, shift)); - const remaining_bits = @bitCast(i8, byte | 0x80) >> remaining_shift; - if (remaining_bits != -1) return error.Overflow; + const check_bits_shift = @intCast(u3, U.bit_count - @as(u16, group * 7)); + const check_bits_remaining = 7 - check_bits_shift; + const check_bits = byte >> check_bits_shift; + const num_consecutive_ones = @ctz(u8, ~check_bits); + if (num_consecutive_ones < check_bits_remaining) return error.Overflow; } value |= temp; if (byte & 0x80 == 0) { - const needs_sign_ext = group + 1 < max_group; - if (byte & 0x40 != 0 and needs_sign_ext) { - const ones = @as(S, -1); - value |= @bitCast(U, ones) << (shift + 7); + if (byte & 0x40 != 0 and group + 1 < max_group) { + value |= @bitCast(U, @as(S, -1)) << ((group + 1) * 7); } break; } @@ -110,16 +104,12 @@ pub fn readILEB128(comptime T: type, reader: var) !T { return error.Overflow; } - const result = @bitCast(S, value); //Only applies if we extended to i8 - if (S != T) { - if (result > std.math.maxInt(T) or result < std.math.minInt(T)) return error.Overflow; - } + if (@bitCast(S, value) > std.math.maxInt(T) or @bitCast(S, value) < std.math.minInt(T)) return error.Overflow; - return @truncate(T, result); + return @truncate(T, @bitCast(S, value)); } -///Write a single signed integer as signed LEB128 to the given writer. pub fn writeILEB128(writer: var, int_value: var) !void { const T = @TypeOf(int_value); const S = if (T.bit_count < 8) i8 else T; @@ -141,22 +131,19 @@ pub fn writeILEB128(writer: var, int_value: var) !void { } } -///Read a single singed LEB128 integer from the given memory as type T. -/// The provided slice reference will be updated to point to the byte after the last byte read. -pub fn readILEB128Mem(comptime T: type, ptr: *[]const u8) !T { - var buf = std.io.fixedBufferStream(ptr.*); +pub fn readILEB128Mem(comptime T: type, ptr: *[*]const u8) !T { + const max_group = (T.bit_count + 6) / 7; + var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); const value = try readILEB128(T, buf.reader()); - ptr.*.ptr += buf.pos; + ptr.* += @intCast(usize, try buf.getPos()); return value; } -///Write a single signed LEB128 integer to the given memory as unsigned LEB128, -/// returning the number of bytes written. pub fn writeILEB128Mem(ptr: []u8, int_value: var) !usize { const T = @TypeOf(int_value); var buf = std.io.fixedBufferStream(ptr); try writeILEB128(buf.writer(), int_value); - return buf.pos; + return try buf.getPos(); } //tests @@ -173,7 +160,7 @@ fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readILEB128(T, reader.reader()); - var in_ptr = encoded; + var in_ptr = encoded.ptr; const v2 = try readILEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -182,7 +169,7 @@ fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readULEB128(T, reader.reader()); - var in_ptr = encoded; + var in_ptr = encoded.ptr; const v2 = try readULEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -190,7 +177,7 @@ fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded; + var in_ptr = encoded.ptr; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readILEB128(T, reader.reader()); @@ -201,7 +188,7 @@ fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded; + var in_ptr = encoded.ptr; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readULEB128(T, reader.reader()); @@ -298,6 +285,8 @@ test "deserialize unsigned LEB128" { fn test_write_leb128(value: var) !void { const T = @TypeOf(value); + if (T.bit_count == 0) std.debug.warn("{}\n", .{@typeName(T)}); + const writeStream = if (T.is_signed) writeILEB128 else writeULEB128; const writeMem = if (T.is_signed) writeILEB128Mem else writeULEB128Mem; const readStream = if (T.is_signed) readILEB128 else readULEB128; @@ -335,13 +324,13 @@ fn test_write_leb128(value: var) !void { //mem read var buf_ref: []u8 = buf[0..]; - const mr = try readMem(T, &buf_ref); + const mr = try readMem(T, &buf_ref.ptr); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(mr == value); //bigger type mem read buf_ref = buf[0..]; - const bmr = try readMem(T, &buf_ref); + const bmr = try readMem(T, &buf_ref.ptr); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(bmr == value); } -- 2.54.0 From a0160d776f30fab70c786cd1159203a593af51b4 Mon Sep 17 00:00:00 2001 From: tgschultz Date: Thu, 11 Jun 2020 20:53:25 +0000 Subject: [PATCH 038/295] Code cleanup, documentation added, read*Mem functions now take *[]const u8 --- lib/std/debug/leb128.zig | 79 +++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig index f9e25d9ee784aab4eeb01a64dc0d04d590070d02..76e76d8e4e65b3a46278c62b8077fada2e8ec2cf 100644 --- a/lib/std/debug/leb128.zig +++ b/lib/std/debug/leb128.zig @@ -1,11 +1,8 @@ const std = @import("std"); const testing = std.testing; -//@TODO: you can take *slice and alter slice.ptr -// make sign bits check more efficient -// add wrapper readLEB128 and write LEB128 that infer from type? -// or use assertions? - +///Read a single unsigned LEB128 value from the given reader as type T, +/// or error.Overflow if the value cannot fit. pub fn readULEB128(comptime T: type, reader: var) !T { const U = if (T.bit_count < 8) u8 else T; const ShiftT = std.math.Log2Int(U); @@ -28,11 +25,14 @@ pub fn readULEB128(comptime T: type, reader: var) !T { } //only applies in the case that we extended to u8 - if (value > std.math.maxInt(T)) return error.Overflow; + if (U != T) { + if (value > std.math.maxInt(T)) return error.Overflow; + } return @truncate(T, value); } +///Write a single unsigned integer as unsigned LEB128 to the given writer. pub fn writeULEB128(writer: var, uint_value: var) !void { const T = @TypeOf(uint_value); const U = if (T.bit_count < 8) u8 else T; @@ -50,22 +50,27 @@ pub fn writeULEB128(writer: var, uint_value: var) !void { } } -pub fn readULEB128Mem(comptime T: type, ptr: *[*]const u8) !T { - const max_group = (T.bit_count + 6) / 7; - var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); +///Read a single unsinged integer from the given memory as type T. +/// The provided slice reference will be updated to point to the byte after the last byte read. +pub fn readULEB128Mem(comptime T: type, ptr: *[]const u8) !T { + var buf = std.io.fixedBufferStream(ptr.*); const value = try readULEB128(T, buf.reader()); - ptr.* += @intCast(usize, try buf.getPos()); + ptr.*.ptr += buf.pos; return value; } +///Write a single unsigned LEB128 integer to the given memory as unsigned LEB128, +/// returning the number of bytes written. pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { const T = @TypeOf(uint_value); const max_group = (T.bit_count + 6) / 7; var buf = std.io.fixedBufferStream(ptr); try writeULEB128(buf.writer(), uint_value); - return try buf.getPos(); + return buf.pos; } +///Read a single signed LEB128 value from the given reader as type T, +/// or error.Overflow if the value cannot fit. pub fn readILEB128(comptime T: type, reader: var) !T { const S = if (T.bit_count < 8) i8 else T; const U = std.meta.Int(false, S.bit_count); @@ -80,23 +85,24 @@ pub fn readILEB128(comptime T: type, reader: var) !T { const byte = try reader.readByte(); var temp = @as(U, byte & 0x7f); - if (@shlWithOverflow(U, temp, group * 7, &temp)) { + const shift = group * 7; + if (@shlWithOverflow(U, temp, shift, &temp)) { //Overflow is ok so long as the sign bit is set and this is the last byte if (byte & 0x80 != 0) return error.Overflow; if (@bitCast(S, temp) >= 0) return error.Overflow; //and all the overflowed bits are 1 - const check_bits_shift = @intCast(u3, U.bit_count - @as(u16, group * 7)); - const check_bits_remaining = 7 - check_bits_shift; - const check_bits = byte >> check_bits_shift; - const num_consecutive_ones = @ctz(u8, ~check_bits); - if (num_consecutive_ones < check_bits_remaining) return error.Overflow; + const remaining_shift = @intCast(u3, U.bit_count - @as(u16, shift)); + const remaining_bits = @bitCast(i8, byte | 0x80) >> remaining_shift; + if (remaining_bits != -1) return error.Overflow; } value |= temp; if (byte & 0x80 == 0) { - if (byte & 0x40 != 0 and group + 1 < max_group) { - value |= @bitCast(U, @as(S, -1)) << ((group + 1) * 7); + const needs_sign_ext = group + 1 < max_group; + if (byte & 0x40 != 0 and needs_sign_ext) { + const ones = @as(S, -1); + value |= @bitCast(U, ones) << (shift + 7); } break; } @@ -104,12 +110,16 @@ pub fn readILEB128(comptime T: type, reader: var) !T { return error.Overflow; } + const result = @bitCast(S, value); //Only applies if we extended to i8 - if (@bitCast(S, value) > std.math.maxInt(T) or @bitCast(S, value) < std.math.minInt(T)) return error.Overflow; + if (S != T) { + if (result > std.math.maxInt(T) or result < std.math.minInt(T)) return error.Overflow; + } - return @truncate(T, @bitCast(S, value)); + return @truncate(T, result); } +///Write a single signed integer as signed LEB128 to the given writer. pub fn writeILEB128(writer: var, int_value: var) !void { const T = @TypeOf(int_value); const S = if (T.bit_count < 8) i8 else T; @@ -131,19 +141,22 @@ pub fn writeILEB128(writer: var, int_value: var) !void { } } -pub fn readILEB128Mem(comptime T: type, ptr: *[*]const u8) !T { - const max_group = (T.bit_count + 6) / 7; - var buf = std.io.fixedBufferStream(ptr.*[0 .. max_group + 1]); +///Read a single singed LEB128 integer from the given memory as type T. +/// The provided slice reference will be updated to point to the byte after the last byte read. +pub fn readILEB128Mem(comptime T: type, ptr: *[]const u8) !T { + var buf = std.io.fixedBufferStream(ptr.*); const value = try readILEB128(T, buf.reader()); - ptr.* += @intCast(usize, try buf.getPos()); + ptr.*.ptr += buf.pos; return value; } +///Write a single signed LEB128 integer to the given memory as unsigned LEB128, +/// returning the number of bytes written. pub fn writeILEB128Mem(ptr: []u8, int_value: var) !usize { const T = @TypeOf(int_value); var buf = std.io.fixedBufferStream(ptr); try writeILEB128(buf.writer(), int_value); - return try buf.getPos(); + return buf.pos; } //tests @@ -160,7 +173,7 @@ fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readILEB128(T, reader.reader()); - var in_ptr = encoded.ptr; + var in_ptr = encoded; const v2 = try readILEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -169,7 +182,7 @@ fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); const v1 = try readULEB128(T, reader.reader()); - var in_ptr = encoded.ptr; + var in_ptr = encoded; const v2 = try readULEB128Mem(T, &in_ptr); testing.expectEqual(v1, v2); return v1; @@ -177,7 +190,7 @@ fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded.ptr; + var in_ptr = encoded; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readILEB128(T, reader.reader()); @@ -188,7 +201,7 @@ fn test_read_ileb128_seq(comptime T: type, comptime N: usize, encoded: []const u fn test_read_uleb128_seq(comptime T: type, comptime N: usize, encoded: []const u8) !void { var reader = std.io.fixedBufferStream(encoded); - var in_ptr = encoded.ptr; + var in_ptr = encoded; var i: usize = 0; while (i < N) : (i += 1) { const v1 = try readULEB128(T, reader.reader()); @@ -285,8 +298,6 @@ test "deserialize unsigned LEB128" { fn test_write_leb128(value: var) !void { const T = @TypeOf(value); - if (T.bit_count == 0) std.debug.warn("{}\n", .{@typeName(T)}); - const writeStream = if (T.is_signed) writeILEB128 else writeULEB128; const writeMem = if (T.is_signed) writeILEB128Mem else writeULEB128Mem; const readStream = if (T.is_signed) readILEB128 else readULEB128; @@ -324,13 +335,13 @@ fn test_write_leb128(value: var) !void { //mem read var buf_ref: []u8 = buf[0..]; - const mr = try readMem(T, &buf_ref.ptr); + const mr = try readMem(T, &buf_ref); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(mr == value); //bigger type mem read buf_ref = buf[0..]; - const bmr = try readMem(T, &buf_ref.ptr); + const bmr = try readMem(T, &buf_ref); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(bmr == value); } -- 2.54.0 From 38e69a9e6a6d69a48d130382dcb61ba2516863d0 Mon Sep 17 00:00:00 2001 From: tgschultz Date: Tue, 16 Jun 2020 16:11:39 +0000 Subject: [PATCH 039/295] Added test to ensure minimum number of bytes is emitted for writes --- lib/std/debug/leb128.zig | 55 ++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig index 76e76d8e4e65b3a46278c62b8077fada2e8ec2cf..16a23da1232f750507628f5d2bc3e985cb79c345 100644 --- a/lib/std/debug/leb128.zig +++ b/lib/std/debug/leb128.zig @@ -1,7 +1,7 @@ const std = @import("std"); const testing = std.testing; -///Read a single unsigned LEB128 value from the given reader as type T, +/// Read a single unsigned LEB128 value from the given reader as type T, /// or error.Overflow if the value cannot fit. pub fn readULEB128(comptime T: type, reader: var) !T { const U = if (T.bit_count < 8) u8 else T; @@ -24,7 +24,7 @@ pub fn readULEB128(comptime T: type, reader: var) !T { return error.Overflow; } - //only applies in the case that we extended to u8 + // only applies in the case that we extended to u8 if (U != T) { if (value > std.math.maxInt(T)) return error.Overflow; } @@ -32,7 +32,7 @@ pub fn readULEB128(comptime T: type, reader: var) !T { return @truncate(T, value); } -///Write a single unsigned integer as unsigned LEB128 to the given writer. +/// Write a single unsigned integer as unsigned LEB128 to the given writer. pub fn writeULEB128(writer: var, uint_value: var) !void { const T = @TypeOf(uint_value); const U = if (T.bit_count < 8) u8 else T; @@ -50,7 +50,7 @@ pub fn writeULEB128(writer: var, uint_value: var) !void { } } -///Read a single unsinged integer from the given memory as type T. +/// Read a single unsinged integer from the given memory as type T. /// The provided slice reference will be updated to point to the byte after the last byte read. pub fn readULEB128Mem(comptime T: type, ptr: *[]const u8) !T { var buf = std.io.fixedBufferStream(ptr.*); @@ -59,7 +59,7 @@ pub fn readULEB128Mem(comptime T: type, ptr: *[]const u8) !T { return value; } -///Write a single unsigned LEB128 integer to the given memory as unsigned LEB128, +/// Write a single unsigned LEB128 integer to the given memory as unsigned LEB128, /// returning the number of bytes written. pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { const T = @TypeOf(uint_value); @@ -69,7 +69,7 @@ pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { return buf.pos; } -///Read a single signed LEB128 value from the given reader as type T, +/// Read a single signed LEB128 value from the given reader as type T, /// or error.Overflow if the value cannot fit. pub fn readILEB128(comptime T: type, reader: var) !T { const S = if (T.bit_count < 8) i8 else T; @@ -87,11 +87,11 @@ pub fn readILEB128(comptime T: type, reader: var) !T { const shift = group * 7; if (@shlWithOverflow(U, temp, shift, &temp)) { - //Overflow is ok so long as the sign bit is set and this is the last byte + // Overflow is ok so long as the sign bit is set and this is the last byte if (byte & 0x80 != 0) return error.Overflow; if (@bitCast(S, temp) >= 0) return error.Overflow; - //and all the overflowed bits are 1 + // and all the overflowed bits are 1 const remaining_shift = @intCast(u3, U.bit_count - @as(u16, shift)); const remaining_bits = @bitCast(i8, byte | 0x80) >> remaining_shift; if (remaining_bits != -1) return error.Overflow; @@ -111,7 +111,7 @@ pub fn readILEB128(comptime T: type, reader: var) !T { } const result = @bitCast(S, value); - //Only applies if we extended to i8 + // Only applies if we extended to i8 if (S != T) { if (result > std.math.maxInt(T) or result < std.math.minInt(T)) return error.Overflow; } @@ -119,7 +119,7 @@ pub fn readILEB128(comptime T: type, reader: var) !T { return @truncate(T, result); } -///Write a single signed integer as signed LEB128 to the given writer. +/// Write a single signed integer as signed LEB128 to the given writer. pub fn writeILEB128(writer: var, int_value: var) !void { const T = @TypeOf(int_value); const S = if (T.bit_count < 8) i8 else T; @@ -141,7 +141,7 @@ pub fn writeILEB128(writer: var, int_value: var) !void { } } -///Read a single singed LEB128 integer from the given memory as type T. +/// Read a single singed LEB128 integer from the given memory as type T. /// The provided slice reference will be updated to point to the byte after the last byte read. pub fn readILEB128Mem(comptime T: type, ptr: *[]const u8) !T { var buf = std.io.fixedBufferStream(ptr.*); @@ -150,7 +150,7 @@ pub fn readILEB128Mem(comptime T: type, ptr: *[]const u8) !T { return value; } -///Write a single signed LEB128 integer to the given memory as unsigned LEB128, +/// Write a single signed LEB128 integer to the given memory as unsigned LEB128, /// returning the number of bytes written. pub fn writeILEB128Mem(ptr: []u8, int_value: var) !usize { const T = @TypeOf(int_value); @@ -159,7 +159,7 @@ pub fn writeILEB128Mem(ptr: []u8, int_value: var) !usize { return buf.pos; } -//tests +// tests fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T { var reader = std.io.fixedBufferStream(encoded); return try readILEB128(T, reader.reader()); @@ -303,43 +303,54 @@ fn test_write_leb128(value: var) !void { const readStream = if (T.is_signed) readILEB128 else readULEB128; const readMem = if (T.is_signed) readILEB128Mem else readULEB128Mem; - //decode to a larger bit size too, to ensure sign extension + // decode to a larger bit size too, to ensure sign extension // is working as expected const larger_type_bits = ((T.bit_count + 8) / 8) * 8; const B = std.meta.Int(T.is_signed, larger_type_bits); + + const bytes_needed = bn: { + const S = std.meta.Int(T.is_signed, @sizeOf(T) * 8); + if (T.bit_count <= 7) break :bn @as(u16, 1); + + const unused_bits = if (value < 0) @clz(T, ~value) else @clz(T, value); + const used_bits: u16 = (T.bit_count - unused_bits) + @boolToInt(T.is_signed); + if (used_bits <= 7) break :bn @as(u16, 1); + break :bn ((used_bits + 6) / 7); + }; + const max_groups = if (T.bit_count == 0) 1 else (T.bit_count + 6) / 7; var buf: [max_groups]u8 = undefined; var fbs = std.io.fixedBufferStream(&buf); - //stream write + // stream write try writeStream(fbs.writer(), value); const w1_pos = fbs.pos; - testing.expect(w1_pos > 0); + testing.expect(w1_pos == bytes_needed); - //stream read + // stream read fbs.pos = 0; const sr = try readStream(T, fbs.reader()); testing.expect(fbs.pos == w1_pos); testing.expect(sr == value); - //bigger type stream read + // bigger type stream read fbs.pos = 0; const bsr = try readStream(B, fbs.reader()); testing.expect(fbs.pos == w1_pos); testing.expect(bsr == value); - //mem write + // mem write const w2_pos = try writeMem(&buf, value); testing.expect(w2_pos == w1_pos); - //mem read + // mem read var buf_ref: []u8 = buf[0..]; const mr = try readMem(T, &buf_ref); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); testing.expect(mr == value); - //bigger type mem read + // bigger type mem read buf_ref = buf[0..]; const bmr = try readMem(T, &buf_ref); testing.expect(@ptrToInt(buf_ref.ptr) - @ptrToInt(&buf) == w2_pos); @@ -361,7 +372,7 @@ test "serialize unsigned LEB128" { } test "serialize signed LEB128" { - //explicitly test i0 because starting `t` at 0 + // explicitly test i0 because starting `t` at 0 // will break the while loop try test_write_leb128(@as(i0, 0)); -- 2.54.0 From 04c3fae720b57e72dedbaf28982e7e9bfed47db6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 15 Jun 2020 17:04:32 +0200 Subject: [PATCH 040/295] Remove obsolete branch in ir_analyze_cast Branch handling `*[N]T` to `E![]T` is already handled in a more complete branch handling `*[N]T` to `[]T` *and* `*[N]T` to `E![]T` so it seems safe to remove this one. --- src/ir.cpp | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index d28648e128ba386e206afb2aac38cd3f21016423..71e3b473b2537a5594bbe76acf678417fbadcbae 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -15257,46 +15257,6 @@ static IrInstGen *ir_analyze_cast(IrAnalyze *ira, IrInst *source_instr, } } - // *[N]T to E![]T - if (wanted_type->id == ZigTypeIdErrorUnion && - is_slice(wanted_type->data.error_union.payload_type) && - actual_type->id == ZigTypeIdPointer && - actual_type->data.pointer.ptr_len == PtrLenSingle && - actual_type->data.pointer.child_type->id == ZigTypeIdArray) - { - ZigType *slice_type = wanted_type->data.error_union.payload_type; - ZigType *slice_ptr_type = slice_type->data.structure.fields[slice_ptr_index]->type_entry; - assert(slice_ptr_type->id == ZigTypeIdPointer); - ZigType *array_type = actual_type->data.pointer.child_type; - bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0 - || !actual_type->data.pointer.is_const); - if (const_ok && types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, - array_type->data.array.child_type, source_node, - !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk) - { - // If the pointers both have ABI align, it works. - bool ok_align = slice_ptr_type->data.pointer.explicit_alignment == 0 && - actual_type->data.pointer.explicit_alignment == 0; - if (!ok_align) { - // If either one has non ABI align, we have to resolve them both - if ((err = type_resolve(ira->codegen, actual_type->data.pointer.child_type, - ResolveStatusAlignmentKnown))) - { - return ira->codegen->invalid_inst_gen; - } - if ((err = type_resolve(ira->codegen, slice_ptr_type->data.pointer.child_type, - ResolveStatusAlignmentKnown))) - { - return ira->codegen->invalid_inst_gen; - } - ok_align = get_ptr_align(ira->codegen, actual_type) >= get_ptr_align(ira->codegen, slice_ptr_type); - } - if (ok_align) { - return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, slice_type, nullptr); - } - } - } - // @Vector(N,T1) to @Vector(N,T2) if (actual_type->id == ZigTypeIdVector && wanted_type->id == ZigTypeIdVector) { if (actual_type->data.vector.len == wanted_type->data.vector.len && -- 2.54.0 From f0b8791da75d2cb9c73424b8270da81e7d0ec333 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sat, 13 Jun 2020 11:35:18 -0600 Subject: [PATCH 041/295] ArrayList(u8) support writer interface --- lib/std/array_list.zig | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index e096a6549159a5329229d3838bb5469b0adc5227..d42a3c3d73c68bdb37f0faf8f7da2b1062e79f62 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -162,19 +162,24 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { mem.copy(T, self.items[oldlen..], items); } - /// Same as `append` except it returns the number of bytes written, which is always the same - /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API. - /// This function may be called only when `T` is `u8`. - fn appendWrite(self: *Self, m: []const u8) !usize { - try self.appendSlice(m); - return m.len; - } + pub usingnamespace if (T != u8) struct { } else struct { + pub const Writer = std.io.Writer(*Self, error{OutOfMemory}, appendWrite); - /// Initializes an OutStream which will append to the list. - /// This function may be called only when `T` is `u8`. - pub fn outStream(self: *Self) std.io.OutStream(*Self, error{OutOfMemory}, appendWrite) { - return .{ .context = self }; - } + /// Initializes a Writer which will append to the list. + pub fn writer(self: *Self) Writer { + return .{ .context = self }; + } + + /// Deprecated: use `writer` + pub const outStream = writer; + + /// Same as `append` except it returns the number of bytes written, which is always the same + /// as `m.len`. The purpose of this function existing is to match `std.io.Writer` API. + fn appendWrite(self: *Self, m: []const u8) !usize { + try self.appendSlice(m); + return m.len; + } + }; /// Append a value to the list `n` times. /// Allocates more memory as necessary. @@ -694,3 +699,15 @@ test "std.ArrayList.shrink still sets length on error.OutOfMemory" { list.shrink(1); testing.expect(list.items.len == 1); } + +test "std.ArrayList.writer" { + var list = ArrayList(u8).init(std.testing.allocator); + defer list.deinit(); + + const writer = list.writer(); + try writer.writeAll("a"); + try writer.writeAll("bc"); + try writer.writeAll("d"); + try writer.writeAll("efg"); + testing.expectEqualSlices(u8, list.items, "abcdefg"); +} -- 2.54.0 From 0bd067d19a434a5c7fd04d816d131cd593bdc1bf Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 15 May 2020 17:10:56 +0200 Subject: [PATCH 042/295] Introduce std.log std.log provides 8 log levels and corresponding logging functions. It allows the user to override the logging "backend" by defining root.log and to override the default log level by defining root.log_level. Logging functions accept a scope parameter which allows the implementer of the logging "backend" to filter logging by library as well as level. Using the standardized syslog [1] log levels ensures that std.log will be flexible enough to work for as many use-cases as possible. If we were to stick with only 3/4 log levels, std.log would be insufficient for large and/or complex projects such as a kernel or display server. [1]: https://tools.ietf.org/html/rfc5424#section-6.2.1 --- lib/std/log.zig | 202 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/std/std.zig | 1 + 2 files changed, 203 insertions(+) create mode 100644 lib/std/log.zig diff --git a/lib/std/log.zig b/lib/std/log.zig new file mode 100644 index 0000000000000000000000000000000000000000..706c2b23788c827e7e0853fb35819f1ebea16280 --- /dev/null +++ b/lib/std/log.zig @@ -0,0 +1,202 @@ +const std = @import("std.zig"); +const builtin = std.builtin; +const root = @import("root"); + +//! std.log is standardized interface for logging which allows for the logging +//! of programs and libraries using this interface to be formatted and filtered +//! by the implementer of the root.log function. +//! +//! The scope parameter should be used to give context to the logging. For +//! example, a library called 'libfoo' might use .libfoo as its scope. +//! +//! An example root.log might look something like this: +//! +//! ``` +//! const std = @import("std"); +//! +//! // Set the log level to warning +//! pub const log_level: std.log.Level = .warn; +//! +//! // Define root.log to override the std implementation +//! pub fn log( +//! comptime level: std.log.Level, +//! comptime scope: @TypeOf(.EnumLiteral), +//! comptime format: []const u8, +//! args: var, +//! ) void { +//! // Ignore all non-critical logging from sources other than +//! // .my_project and .nice_library +//! const scope_prefix = "(" ++ switch (scope) { +//! .my_project, .nice_library => @tagName(scope), +//! else => if (@enumToInt(level) <= @enumToInt(std.log.Level.crit)) +//! @tagName(scope) +//! else +//! return, +//! } ++ "): "; +//! +//! const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix; +//! +//! // Print the message to stderr, silently ignoring any errors +//! const held = std.debug.getStderrMutex().acquire(); +//! defer held.release(); +//! const stderr = std.debug.getStderrStream(); +//! nosuspend stderr.print(prefix ++ format, args) catch return; +//! } +//! +//! pub fn main() void { +//! // Won't be printed as log_level is .warn +//! std.log.info(.my_project, "Starting up.\n", .{}); +//! std.log.err(.nice_library, "Something went very wrong, sorry.\n", .{}); +//! // Won't be printed as it gets filtered out by our log function +//! std.log.err(.lib_that_logs_too_much, "Added 1 + 1\n", .{}); +//! } +//! ``` +//! Which produces the following output: +//! ``` +//! [err] (nice_library): Something went very wrong, sorry. +//! ``` + +pub const Level = enum { + /// Emergency: a condition that cannot be handled, usually followed by a + /// panic. + emerg, + /// Alert: a condition that should be corrected immediately (e.g. database + /// corruption). + alert, + /// Critical: A bug has been detected or something has gone wrong and it + /// will have an effect on the operation of the program. + crit, + /// Error: A bug has been detected or something has gone wrong but it is + /// recoverable. + err, + /// Warning: it is uncertain if something has gone wrong or not, but the + /// circumstances would be worth investigating. + warn, + /// Notice: non-error but significant conditions. + notice, + /// Informational: general messages about the state of the program. + info, + /// Debug: messages only useful for debugging. + debug, +}; + +/// The default log level is based on build mode. Note that in ReleaseSmall +/// builds the default level is emerg but no messages will be stored/logged +/// by the default logger to save space. +pub const default_level: Level = switch (builtin.mode) { + .Debug => .debug, + .ReleaseSafe => .notice, + .ReleaseFast => .err, + .ReleaseSmall => .emerg, +}; + +/// The current log level. This is set to root.log_level if present, otherwise +/// log.default_level. +pub const level: Level = if (@hasDecl(root, "log_level")) + root.log_level +else + default_level; + +fn log( + comptime message_level: Level, + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + if (@enumToInt(message_level) <= @enumToInt(level)) { + if (@hasDecl(root, "log")) { + root.log(message_level, scope, format, args); + } else if (builtin.mode != .ReleaseSmall) { + const held = std.debug.getStderrMutex().acquire(); + defer held.release(); + const stderr = io.getStdErr().writer(); + nosuspend stderr.print(format, args) catch return; + } + } +} + +/// Log an emergency message to stderr. This log level is intended to be used +/// for conditions that cannot be handled and is usually followed by a panic. +pub fn emerg( + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + @setCold(true); + log(.emerg, scope, format, args); +} + +/// Log an alert message to stderr. This log level is intended to be used for +/// conditions that should be corrected immediately (e.g. database corruption). +pub fn alert( + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + @setCold(true); + log(.alert, scope, format, args); +} + +/// Log a critical message to stderr. This log level is intended to be used +/// when a bug has been detected or something has gone wrong and it will have +/// an effect on the operation of the program. +pub fn crit( + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + @setCold(true); + log(.crit, scope, format, args); +} + +/// Log an error message to stderr. This log level is intended to be used when +/// a bug has been detected or something has gone wrong but it is recoverable. +pub fn err( + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + @setCold(true); + log(.err, scope, format, args); +} + +/// Log a warning message to stderr. This log level is intended to be used if +/// it is uncertain whether something has gone wrong or not, but the +/// circumstances would be worth investigating. +pub fn warn( + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + log(.warn, scope, format, args); +} + +/// Log a notice message to stderr. This log level is intended to be used for +/// non-error but significant conditions. +pub fn notice( + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + log(.notice, scope, format, args); +} + +/// Log an info message to stderr. This log level is intended to be used for +/// general messages about the state of the program. +pub fn info( + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + log(.info, scope, format, args); +} + +/// Log a debug message to stderr. This log level is intended to be used for +/// messages which are only useful for debugging. +pub fn debug( + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + log(.debug, scope, format, args); +} diff --git a/lib/std/std.zig b/lib/std/std.zig index b1cab77109db516aeac83f3910c376be8e4dab2b..0961991ea650ffb945c87611b9a272025d12aa93 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -49,6 +49,7 @@ pub const heap = @import("heap.zig"); pub const http = @import("http.zig"); pub const io = @import("io.zig"); pub const json = @import("json.zig"); +pub const log = @import("log.zig"); pub const macho = @import("macho.zig"); pub const math = @import("math.zig"); pub const mem = @import("mem.zig"); -- 2.54.0 From 8e5393a779ee115846821e28bdb47affdf158992 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Tue, 2 Jun 2020 18:43:27 +0200 Subject: [PATCH 043/295] Deprecate std.debug.warn --- lib/std/debug.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 591f2d1a802a9380681aee243d9a7537bd941a56..f3c2cf3b31ed0759399b4316f37ba1216626d22a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -52,8 +52,7 @@ pub const LineInfo = struct { var stderr_mutex = std.Mutex.init(); -/// Tries to write to stderr, unbuffered, and ignores any error returned. -/// Does not append a newline. +/// Deprecated. Use `std.log` functions for logging. pub fn warn(comptime fmt: []const u8, args: var) void { const held = stderr_mutex.acquire(); defer held.release(); -- 2.54.0 From 1157ee130732449811294d70021aaafa588d3048 Mon Sep 17 00:00:00 2001 From: antlilja Date: Wed, 17 Jun 2020 17:35:45 +0200 Subject: [PATCH 044/295] Improve builtin op support for f128/comptime_float * Add support for fabs, floor, ceil, trunc and round * Add behavior tests --- CMakeLists.txt | 1 + src/ir.cpp | 21 ++++-- src/softfloat_ext.cpp | 25 +++++++ src/softfloat_ext.hpp | 9 +++ test/stage1/behavior/math.zig | 122 ++++++++++++++++++++++++++++++++++ 5 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 src/softfloat_ext.cpp create mode 100644 src/softfloat_ext.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8599d01a5d9ea343619f630529050f4570d263d1..94219c16315e4b621a5275b244ad4b47bf400121 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -288,6 +288,7 @@ set(ZIG_SOURCES "${CMAKE_SOURCE_DIR}/src/target.cpp" "${CMAKE_SOURCE_DIR}/src/tokenizer.cpp" "${CMAKE_SOURCE_DIR}/src/util.cpp" + "${CMAKE_SOURCE_DIR}/src/softfloat_ext.cpp" "${ZIG_SOURCES_MEM_PROFILE}" ) set(OPTIMIZED_C_SOURCES diff --git a/src/ir.cpp b/src/ir.cpp index d28648e128ba386e206afb2aac38cd3f21016423..82678426714ab0ed1a2e7157172d838f2a6cf449 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -13,6 +13,7 @@ #include "os.hpp" #include "range_set.hpp" #include "softfloat.hpp" +#include "softfloat_ext.hpp" #include "util.hpp" #include "mem_list.hpp" #include "all_types.hpp" @@ -30303,6 +30304,21 @@ static ErrorMsg *ir_eval_float_op(IrAnalyze *ira, IrInst* source_instr, BuiltinF case BuiltinFnIdSqrt: f128M_sqrt(in, out); break; + case BuiltinFnIdFabs: + f128M_abs(in, out); + break; + case BuiltinFnIdFloor: + f128M_roundToInt(in, softfloat_round_min, false, out); + break; + case BuiltinFnIdCeil: + f128M_roundToInt(in, softfloat_round_max, false, out); + break; + case BuiltinFnIdTrunc: + f128M_trunc(in, out); + break; + case BuiltinFnIdRound: + f128M_roundToInt(in, softfloat_round_near_maxMag, false, out); + break; case BuiltinFnIdNearbyInt: case BuiltinFnIdSin: case BuiltinFnIdCos: @@ -30311,11 +30327,6 @@ static ErrorMsg *ir_eval_float_op(IrAnalyze *ira, IrInst* source_instr, BuiltinF case BuiltinFnIdLog: case BuiltinFnIdLog10: case BuiltinFnIdLog2: - case BuiltinFnIdFabs: - case BuiltinFnIdFloor: - case BuiltinFnIdCeil: - case BuiltinFnIdTrunc: - case BuiltinFnIdRound: return ir_add_error(ira, source_instr, buf_sprintf("compiler bug: TODO: implement '%s' for type '%s'. See https://github.com/ziglang/zig/issues/4026", float_op_to_name(fop), buf_ptr(&float_type->name))); diff --git a/src/softfloat_ext.cpp b/src/softfloat_ext.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8408a1511682fdc6384870a8cef7ee6501ce5118 --- /dev/null +++ b/src/softfloat_ext.cpp @@ -0,0 +1,25 @@ +#include "softfloat_ext.hpp" + +extern "C" { + #include "softfloat.h" +} + +void f128M_abs(const float128_t *aPtr, float128_t *zPtr) { + float128_t zero_float; + ui32_to_f128M(0, &zero_float); + if (f128M_lt(aPtr, &zero_float)) { + f128M_sub(&zero_float, aPtr, zPtr); + } else { + *zPtr = *aPtr; + } +} + +void f128M_trunc(const float128_t *aPtr, float128_t *zPtr) { + float128_t zero_float; + ui32_to_f128M(0, &zero_float); + if (f128M_lt(aPtr, &zero_float)) { + f128M_roundToInt(aPtr, softfloat_round_max, false, zPtr); + } else { + f128M_roundToInt(aPtr, softfloat_round_min, false, zPtr); + } +} \ No newline at end of file diff --git a/src/softfloat_ext.hpp b/src/softfloat_ext.hpp new file mode 100644 index 0000000000000000000000000000000000000000..0a1f9589334ac5d1af151affd3765bc366598c26 --- /dev/null +++ b/src/softfloat_ext.hpp @@ -0,0 +1,9 @@ +#ifndef ZIG_SOFTFLOAT_EXT_HPP +#define ZIG_SOFTFLOAT_EXT_HPP + +#include "softfloat_types.h" + +void f128M_abs(const float128_t *aPtr, float128_t *zPtr); +void f128M_trunc(const float128_t *aPtr, float128_t *zPtr); + +#endif \ No newline at end of file diff --git a/test/stage1/behavior/math.zig b/test/stage1/behavior/math.zig index 1d361494eb7cd060427f7748270f8580d2246dfc..b13b1ce1e007bcc41b00ee75fdc1d3013e0643f8 100644 --- a/test/stage1/behavior/math.zig +++ b/test/stage1/behavior/math.zig @@ -634,6 +634,128 @@ fn testSqrt(comptime T: type, x: T) void { expect(@sqrt(x * x) == x); } +test "@fabs" { + testFabs(f128, 12.0); + comptime testFabs(f128, 12.0); + testFabs(f64, 12.0); + comptime testFabs(f64, 12.0); + testFabs(f32, 12.0); + comptime testFabs(f32, 12.0); + testFabs(f16, 12.0); + comptime testFabs(f16, 12.0); + + const x = 14.0; + const y = -x; + const z = @fabs(y); + comptime expectEqual(x, z); +} + +fn testFabs(comptime T: type, x: T) void { + const y = -x; + const z = @fabs(y); + expectEqual(x, z); +} + +test "@floor" { + // FIXME: Generates a floorl function call + // testFloor(f128, 12.0); + comptime testFloor(f128, 12.0); + testFloor(f64, 12.0); + comptime testFloor(f64, 12.0); + testFloor(f32, 12.0); + comptime testFloor(f32, 12.0); + testFloor(f16, 12.0); + comptime testFloor(f16, 12.0); + + const x = 14.0; + const y = x + 0.7; + const z = @floor(y); + comptime expectEqual(x, z); +} + +fn testFloor(comptime T: type, x: T) void { + const y = x + 0.6; + const z = @floor(y); + expectEqual(x, z); +} + +test "@ceil" { + // FIXME: Generates a ceill function call + //testCeil(f128, 12.0); + comptime testCeil(f128, 12.0); + testCeil(f64, 12.0); + comptime testCeil(f64, 12.0); + testCeil(f32, 12.0); + comptime testCeil(f32, 12.0); + testCeil(f16, 12.0); + comptime testCeil(f16, 12.0); + + const x = 14.0; + const y = x - 0.7; + const z = @ceil(y); + comptime expectEqual(x, z); +} + +fn testCeil(comptime T: type, x: T) void { + const y = x - 0.8; + const z = @ceil(y); + expectEqual(x, z); +} + +test "@trunc" { + // FIXME: Generates a truncl function call + //testTrunc(f128, 12.0); + comptime testTrunc(f128, 12.0); + testTrunc(f64, 12.0); + comptime testTrunc(f64, 12.0); + testTrunc(f32, 12.0); + comptime testTrunc(f32, 12.0); + testTrunc(f16, 12.0); + comptime testTrunc(f16, 12.0); + + const x = 14.0; + const y = x + 0.7; + const z = @trunc(y); + comptime expectEqual(x, z); +} + +fn testTrunc(comptime T: type, x: T) void { + { + const y = x + 0.8; + const z = @trunc(y); + expectEqual(x, z); + } + + { + const y = -x - 0.8; + const z = @trunc(y); + expectEqual(-x, z); + } +} + +test "@round" { + // FIXME: Generates a roundl function call + //testRound(f128, 12.0); + comptime testRound(f128, 12.0); + testRound(f64, 12.0); + comptime testRound(f64, 12.0); + testRound(f32, 12.0); + comptime testRound(f32, 12.0); + testRound(f16, 12.0); + comptime testRound(f16, 12.0); + + const x = 14.0; + const y = x + 0.4; + const z = @round(y); + comptime expectEqual(x, z); +} + +fn testRound(comptime T: type, x: T) void { + const y = x - 0.5; + const z = @round(y); + expectEqual(x, z); +} + test "comptime_int param and return" { const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702); expect(a == 137114567242441932203689521744947848950); -- 2.54.0 From eb7fad28f8f11b985c8ee6fbeb4a68345a9b3a5e Mon Sep 17 00:00:00 2001 From: antlilja Date: Wed, 17 Jun 2020 18:18:45 +0200 Subject: [PATCH 045/295] Improve f128 standard library support * Add functions: floor128, ceil128, trunc128 and round128 * Add corresponding tests --- lib/std/math.zig | 5 +++++ lib/std/math/ceil.zig | 43 ++++++++++++++++++++++++++++++++++++ lib/std/math/floor.zig | 43 ++++++++++++++++++++++++++++++++++++ lib/std/math/round.zig | 50 ++++++++++++++++++++++++++++++++++++++++++ lib/std/math/trunc.zig | 37 +++++++++++++++++++++++++++++++ 5 files changed, 178 insertions(+) diff --git a/lib/std/math.zig b/lib/std/math.zig index 5cf6d40d8a8ceca792bbfead641c617ff5b8308a..799c42846b22233d6e529c505327b4034c99bd40 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -122,6 +122,11 @@ pub fn forceEval(value: var) void { const p = @ptrCast(*volatile f64, &x); p.* = x; }, + f128 => { + var x: f128 = undefined; + const p = @ptrCast(*volatile f128, &x); + p.* = x; + }, else => { @compileError("forceEval not implemented for " ++ @typeName(T)); }, diff --git a/lib/std/math/ceil.zig b/lib/std/math/ceil.zig index b94e13a17647f60bc6c0d01d8d05279d56699026..e3b567931881fd4c7b560952b9c19456066f339f 100644 --- a/lib/std/math/ceil.zig +++ b/lib/std/math/ceil.zig @@ -20,6 +20,7 @@ pub fn ceil(x: var) @TypeOf(x) { return switch (T) { f32 => ceil32(x), f64 => ceil64(x), + f128 => ceil128(x), else => @compileError("ceil not implemented for " ++ @typeName(T)), }; } @@ -86,9 +87,37 @@ fn ceil64(x: f64) f64 { } } +fn ceil128(x: f128) f128 { + const u = @bitCast(u128, x); + const e = (u >> 112) & 0x7FFF; + var y: f128 = undefined; + + if (e >= 0x3FFF + 112 or x == 0) return x; + + if (u >> 127 != 0) { + y = x - math.f128_toint + math.f128_toint - x; + } else { + y = x + math.f128_toint - math.f128_toint - x; + } + + if (e <= 0x3FFF - 1) { + math.forceEval(y); + if (u >> 127 != 0) { + return -0.0; + } else { + return 1.0; + } + } else if (y < 0) { + return x + y + 1; + } else { + return x + y; + } +} + test "math.ceil" { expect(ceil(@as(f32, 0.0)) == ceil32(0.0)); expect(ceil(@as(f64, 0.0)) == ceil64(0.0)); + expect(ceil(@as(f128, 0.0)) == ceil128(0.0)); } test "math.ceil32" { @@ -103,6 +132,12 @@ test "math.ceil64" { expect(ceil64(0.2) == 1.0); } +test "math.ceil128" { + expect(ceil128(1.3) == 2.0); + expect(ceil128(-1.3) == -1.0); + expect(ceil128(0.2) == 1.0); +} + test "math.ceil32.special" { expect(ceil32(0.0) == 0.0); expect(ceil32(-0.0) == -0.0); @@ -118,3 +153,11 @@ test "math.ceil64.special" { expect(math.isNegativeInf(ceil64(-math.inf(f64)))); expect(math.isNan(ceil64(math.nan(f64)))); } + +test "math.ceil128.special" { + expect(ceil128(0.0) == 0.0); + expect(ceil128(-0.0) == -0.0); + expect(math.isPositiveInf(ceil128(math.inf(f128)))); + expect(math.isNegativeInf(ceil128(-math.inf(f128)))); + expect(math.isNan(ceil128(math.nan(f128)))); +} diff --git a/lib/std/math/floor.zig b/lib/std/math/floor.zig index 1eda362e6903f882e5afe5afba5ac9a3210db331..565e2911a94eb74669360c00aa0a6e6a4e61802c 100644 --- a/lib/std/math/floor.zig +++ b/lib/std/math/floor.zig @@ -21,6 +21,7 @@ pub fn floor(x: var) @TypeOf(x) { f16 => floor16(x), f32 => floor32(x), f64 => floor64(x), + f128 => floor128(x), else => @compileError("floor not implemented for " ++ @typeName(T)), }; } @@ -122,10 +123,38 @@ fn floor64(x: f64) f64 { } } +fn floor128(x: f128) f128 { + const u = @bitCast(u128, x); + const e = (u >> 112) & 0x7FFF; + var y: f128 = undefined; + + if (e >= 0x3FFF + 112 or x == 0) return x; + + if (u >> 127 != 0) { + y = x - math.f128_toint + math.f128_toint - x; + } else { + y = x + math.f128_toint - math.f128_toint - x; + } + + if (e <= 0x3FFF - 1) { + math.forceEval(y); + if (u >> 127 != 0) { + return -1.0; + } else { + return 0.0; + } + } else if (y > 0) { + return x + y - 1; + } else { + return x + y; + } +} + test "math.floor" { expect(floor(@as(f16, 1.3)) == floor16(1.3)); expect(floor(@as(f32, 1.3)) == floor32(1.3)); expect(floor(@as(f64, 1.3)) == floor64(1.3)); + expect(floor(@as(f128, 1.3)) == floor128(1.3)); } test "math.floor16" { @@ -146,6 +175,12 @@ test "math.floor64" { expect(floor64(0.2) == 0.0); } +test "math.floor128" { + expect(floor128(1.3) == 1.0); + expect(floor128(-1.3) == -2.0); + expect(floor128(0.2) == 0.0); +} + test "math.floor16.special" { expect(floor16(0.0) == 0.0); expect(floor16(-0.0) == -0.0); @@ -169,3 +204,11 @@ test "math.floor64.special" { expect(math.isNegativeInf(floor64(-math.inf(f64)))); expect(math.isNan(floor64(math.nan(f64)))); } + +test "math.floor128.special" { + expect(floor128(0.0) == 0.0); + expect(floor128(-0.0) == -0.0); + expect(math.isPositiveInf(floor128(math.inf(f128)))); + expect(math.isNegativeInf(floor128(-math.inf(f128)))); + expect(math.isNan(floor128(math.nan(f128)))); +} diff --git a/lib/std/math/round.zig b/lib/std/math/round.zig index dceb3ed770bfa528482a4ec808355d0ccfcbd023..052c0f767047c40bf75932993ddd08b3413fee36 100644 --- a/lib/std/math/round.zig +++ b/lib/std/math/round.zig @@ -20,6 +20,7 @@ pub fn round(x: var) @TypeOf(x) { return switch (T) { f32 => round32(x), f64 => round64(x), + f128 => round128(x), else => @compileError("round not implemented for " ++ @typeName(T)), }; } @@ -90,9 +91,43 @@ fn round64(x_: f64) f64 { } } +fn round128(x_: f128) f128 { + var x = x_; + const u = @bitCast(u128, x); + const e = (u >> 112) & 0x7FFF; + var y: f128 = undefined; + + if (e >= 0x3FFF + 112) { + return x; + } + if (u >> 127 != 0) { + x = -x; + } + if (e < 0x3FFF - 1) { + math.forceEval(x + math.f64_toint); + return 0 * @bitCast(f128, u); + } + + y = x + math.f128_toint - math.f128_toint - x; + if (y > 0.5) { + y = y + x - 1; + } else if (y <= -0.5) { + y = y + x + 1; + } else { + y = y + x; + } + + if (u >> 127 != 0) { + return -y; + } else { + return y; + } +} + test "math.round" { expect(round(@as(f32, 1.3)) == round32(1.3)); expect(round(@as(f64, 1.3)) == round64(1.3)); + expect(round(@as(f128, 1.3)) == round128(1.3)); } test "math.round32" { @@ -109,6 +144,13 @@ test "math.round64" { expect(round64(1.8) == 2.0); } +test "math.round128" { + expect(round128(1.3) == 1.0); + expect(round128(-1.3) == -1.0); + expect(round128(0.2) == 0.0); + expect(round128(1.8) == 2.0); +} + test "math.round32.special" { expect(round32(0.0) == 0.0); expect(round32(-0.0) == -0.0); @@ -124,3 +166,11 @@ test "math.round64.special" { expect(math.isNegativeInf(round64(-math.inf(f64)))); expect(math.isNan(round64(math.nan(f64)))); } + +test "math.round128.special" { + expect(round128(0.0) == 0.0); + expect(round128(-0.0) == -0.0); + expect(math.isPositiveInf(round128(math.inf(f128)))); + expect(math.isNegativeInf(round128(-math.inf(f128)))); + expect(math.isNan(round128(math.nan(f128)))); +} diff --git a/lib/std/math/trunc.zig b/lib/std/math/trunc.zig index b70f0c6be3a92c4fae146e1929052b904b81b661..cdd2fa3c6b0b3009e8776597f11fd726951a694d 100644 --- a/lib/std/math/trunc.zig +++ b/lib/std/math/trunc.zig @@ -20,6 +20,7 @@ pub fn trunc(x: var) @TypeOf(x) { return switch (T) { f32 => trunc32(x), f64 => trunc64(x), + f128 => trunc128(x), else => @compileError("trunc not implemented for " ++ @typeName(T)), }; } @@ -66,9 +67,31 @@ fn trunc64(x: f64) f64 { } } +fn trunc128(x: f128) f128 { + const u = @bitCast(u128, x); + var e = @intCast(i32, ((u >> 112) & 0x7FFF)) - 0x3FFF + 16; + var m: u128 = undefined; + + if (e >= 112 + 16) { + return x; + } + if (e < 16) { + e = 1; + } + + m = @as(u128, maxInt(u128)) >> @intCast(u7, e); + if (u & m == 0) { + return x; + } else { + math.forceEval(x + 0x1p120); + return @bitCast(f128, u & ~m); + } +} + test "math.trunc" { expect(trunc(@as(f32, 1.3)) == trunc32(1.3)); expect(trunc(@as(f64, 1.3)) == trunc64(1.3)); + expect(trunc(@as(f128, 1.3)) == trunc128(1.3)); } test "math.trunc32" { @@ -83,6 +106,12 @@ test "math.trunc64" { expect(trunc64(0.2) == 0.0); } +test "math.trunc128" { + expect(trunc128(1.3) == 1.0); + expect(trunc128(-1.3) == -1.0); + expect(trunc128(0.2) == 0.0); +} + test "math.trunc32.special" { expect(trunc32(0.0) == 0.0); // 0x3F800000 expect(trunc32(-0.0) == -0.0); @@ -98,3 +127,11 @@ test "math.trunc64.special" { expect(math.isNegativeInf(trunc64(-math.inf(f64)))); expect(math.isNan(trunc64(math.nan(f64)))); } + +test "math.trunc128.special" { + expect(trunc128(0.0) == 0.0); + expect(trunc128(-0.0) == -0.0); + expect(math.isPositiveInf(trunc128(math.inf(f128)))); + expect(math.isNegativeInf(trunc128(-math.inf(f128)))); + expect(math.isNan(trunc128(math.nan(f128)))); +} -- 2.54.0 From c3e0224792510e69763dbc6ba68794f9134925f2 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Tue, 2 Jun 2020 19:22:22 +0200 Subject: [PATCH 046/295] Add std.debug.print for "printf debugging" --- lib/std/debug.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index f3c2cf3b31ed0759399b4316f37ba1216626d22a..92b79be35cacd0ad5af869bf705d9e301fabfebb 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -52,8 +52,13 @@ pub const LineInfo = struct { var stderr_mutex = std.Mutex.init(); -/// Deprecated. Use `std.log` functions for logging. -pub fn warn(comptime fmt: []const u8, args: var) void { +/// Deprecated. Use `std.log` functions for logging or `std.debug.print` for +/// "printf debugging". +pub const warn = print; + +/// Print to stderr, unbuffered, and silently returning on failure. Intended +/// for use in "printf debugging." Use `std.log` functions for proper logging. +pub fn print(comptime fmt: []const u8, args: var) void { const held = stderr_mutex.acquire(); defer held.release(); const stderr = io.getStdErr().writer(); -- 2.54.0 From a5379aa3ee376197820f1526c88921607a787a11 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 18 Jun 2020 20:45:48 +0300 Subject: [PATCH 047/295] implement `@src` --- lib/std/builtin.zig | 9 ++++++ src/all_types.hpp | 6 ++++ src/codegen.cpp | 1 + src/ir.cpp | 76 +++++++++++++++++++++++++++++++++++++++++++++ src/ir_print.cpp | 9 ++++++ 5 files changed, 101 insertions(+) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index af8033ae91852f5cea5a4e77afecfdd7e991f380..822901be73c696cd969868dc88a430e0ae0c8a41 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -131,6 +131,15 @@ pub const CallingConvention = enum { AAPCSVFP, }; +/// This data structure is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const SourceLocation = struct { + file: []const u8, + fn_name: []const u8, + line: u32, + column: u32, +}; + pub const TypeId = @TagType(TypeInfo); /// This data structure is used by the Zig language code generation and diff --git a/src/all_types.hpp b/src/all_types.hpp index 9413ea73a4a7e5f70feee86c2b3faa7b32cb1f65..88c7e96943f9ec70328cefcb363b0042e2fc804b 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1827,6 +1827,7 @@ enum BuiltinFnId { BuiltinFnIdBitSizeof, BuiltinFnIdWasmMemorySize, BuiltinFnIdWasmMemoryGrow, + BuiltinFnIdSrc, }; struct BuiltinFnEntry { @@ -2754,6 +2755,7 @@ enum IrInstSrcId { IrInstSrcIdSpillEnd, IrInstSrcIdWasmMemorySize, IrInstSrcIdWasmMemoryGrow, + IrInstSrcIdSrc, }; // ir_render_* functions in codegen.cpp consume Gen instructions and produce LLVM IR. @@ -3761,6 +3763,10 @@ struct IrInstGenWasmMemoryGrow { IrInstGen *delta; }; +struct IrInstSrcSrc { + IrInstSrc base; +}; + struct IrInstSrcSlice { IrInstSrc base; diff --git a/src/codegen.cpp b/src/codegen.cpp index f945dd65457aba785bc55dc94510cfd9b071fb3e..e20d6d60f5f17779f2a13880a4bd04056030afb7 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -8714,6 +8714,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdBitSizeof, "bitSizeOf", 1); create_builtin_fn(g, BuiltinFnIdWasmMemorySize, "wasmMemorySize", 1); create_builtin_fn(g, BuiltinFnIdWasmMemoryGrow, "wasmMemoryGrow", 2); + create_builtin_fn(g, BuiltinFnIdSrc, "src", 0); } static const char *bool_to_str(bool b) { diff --git a/src/ir.cpp b/src/ir.cpp index 71e3b473b2537a5594bbe76acf678417fbadcbae..6406458bf5c2ac280d49e7d024bc706c8584a52d 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -561,6 +561,8 @@ static void destroy_instruction_src(IrInstSrc *inst) { return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdWasmMemoryGrow: return heap::c_allocator.destroy(reinterpret_cast(inst)); + case IrInstSrcIdSrc: + return heap::c_allocator.destroy(reinterpret_cast(inst)); } zig_unreachable(); } @@ -1627,6 +1629,9 @@ static constexpr IrInstSrcId ir_inst_id(IrInstSrcWasmMemoryGrow *) { return IrInstSrcIdWasmMemoryGrow; } +static constexpr IrInstSrcId ir_inst_id(IrInstSrcSrc *) { + return IrInstSrcIdSrc; +} static constexpr IrInstGenId ir_inst_id(IrInstGenDeclVar *) { return IrInstGenIdDeclVar; @@ -5030,6 +5035,11 @@ static IrInstGen *ir_build_wasm_memory_grow_gen(IrAnalyze *ira, IrInst *source_i return &instruction->base; } +static IrInstSrc *ir_build_src(IrBuilderSrc *irb, Scope *scope, AstNode *source_node) { + IrInstSrcSrc *instruction = ir_build_instruction(irb, scope, source_node); + + return &instruction->base; +} static void ir_count_defers(IrBuilderSrc *irb, Scope *inner_scope, Scope *outer_scope, size_t *results) { results[ReturnKindUnconditional] = 0; @@ -7450,6 +7460,11 @@ static IrInstSrc *ir_gen_builtin_fn_call(IrBuilderSrc *irb, Scope *scope, AstNod return ir_gen_union_init_expr(irb, scope, node, union_type_inst, name_inst, init_node, lval, result_loc); } + case BuiltinFnIdSrc: + { + IrInstSrc *src_inst = ir_build_src(irb, scope, node); + return ir_lval_wrap(irb, scope, src_inst, lval, result_loc); + } } zig_unreachable(); } @@ -30859,6 +30874,64 @@ static IrInstGen *ir_analyze_instruction_spill_end(IrAnalyze *ira, IrInstSrcSpil return ir_build_spill_end_gen(ira, &instruction->base.base, begin, operand->value->type); } +static IrInstGen *ir_analyze_instruction_src(IrAnalyze *ira, IrInstSrcSrc *instruction) { + ZigFn *fn_entry = scope_fn_entry(instruction->base.base.scope); + if (fn_entry == nullptr) { + ir_add_error(ira, &instruction->base.base, buf_sprintf("@src outside function")); + return ira->codegen->invalid_inst_gen; + } + + ZigType *u8_ptr = get_pointer_to_type_extra( + ira->codegen, ira->codegen->builtin_types.entry_u8, + true, false, PtrLenUnknown, + 0, 0, 0, false); + ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr); + + ZigType *source_location_type = get_builtin_type(ira->codegen, "SourceLocation"); + if (type_resolve(ira->codegen, source_location_type, ResolveStatusSizeKnown)) { + zig_unreachable(); + } + + ZigValue *result = ira->codegen->pass1_arena->create(); + result->special = ConstValSpecialStatic; + result->type = source_location_type; + + ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 4); + result->data.x_struct.fields = fields; + + // file: []const u8 + ensure_field_index(source_location_type, "file", 0); + fields[0]->special = ConstValSpecialStatic; + fields[0]->type = u8_slice; + + ZigType *import = instruction->base.base.source_node->owner; + Buf *path = import->data.structure.root_struct->path; + ZigValue *file_name = create_const_str_lit(ira->codegen, path)->data.x_ptr.data.ref.pointee; + init_const_slice(ira->codegen, fields[0], file_name, 0, buf_len(path), true); + + // fn_name: []const u8 + ensure_field_index(source_location_type, "fn_name", 1); + fields[1]->special = ConstValSpecialStatic; + fields[1]->type = u8_slice; + + ZigValue *fn_name = create_const_str_lit(ira->codegen, &fn_entry->symbol_name)->data.x_ptr.data.ref.pointee; + init_const_slice(ira->codegen, fields[1], fn_name, 0, buf_len(&fn_entry->symbol_name), true); + + // line: u32 + ensure_field_index(source_location_type, "line", 2); + fields[2]->special = ConstValSpecialStatic; + fields[2]->type = ira->codegen->builtin_types.entry_u32; + bigint_init_unsigned(&fields[2]->data.x_bigint, instruction->base.base.source_node->line + 1); + + // column: u32 + ensure_field_index(source_location_type, "column", 3); + fields[3]->special = ConstValSpecialStatic; + fields[3]->type = ira->codegen->builtin_types.entry_u32; + bigint_init_unsigned(&fields[3]->data.x_bigint, instruction->base.base.source_node->column + 1); + + return ir_const_move(ira, &instruction->base.base, result); +} + static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruction) { switch (instruction->id) { case IrInstSrcIdInvalid: @@ -31130,6 +31203,8 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc return ir_analyze_instruction_wasm_memory_size(ira, (IrInstSrcWasmMemorySize *)instruction); case IrInstSrcIdWasmMemoryGrow: return ir_analyze_instruction_wasm_memory_grow(ira, (IrInstSrcWasmMemoryGrow *)instruction); + case IrInstSrcIdSrc: + return ir_analyze_instruction_src(ira, (IrInstSrcSrc *)instruction); } zig_unreachable(); } @@ -31525,6 +31600,7 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) { case IrInstSrcIdAlloca: case IrInstSrcIdSpillEnd: case IrInstSrcIdWasmMemorySize: + case IrInstSrcIdSrc: return false; case IrInstSrcIdAsm: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index c826d76e03580d588f8e49e4f7cde920927c39f0..27bedff47f458051486d4b63f61940bb1e39da08 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -325,6 +325,8 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { return "SrcWasmMemorySize"; case IrInstSrcIdWasmMemoryGrow: return "SrcWasmMemoryGrow"; + case IrInstSrcIdSrc: + return "SrcSrc"; } zig_unreachable(); } @@ -1744,6 +1746,10 @@ static void ir_print_wasm_memory_grow(IrPrintGen *irp, IrInstGenWasmMemoryGrow * fprintf(irp->f, ")"); } +static void ir_print_builtin_src(IrPrintSrc *irp, IrInstSrcSrc *instruction) { + fprintf(irp->f, "@src()"); +} + static void ir_print_memset(IrPrintSrc *irp, IrInstSrcMemset *instruction) { fprintf(irp->f, "@memset("); ir_print_other_inst_src(irp, instruction->dest_ptr); @@ -2994,6 +3000,9 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai case IrInstSrcIdWasmMemoryGrow: ir_print_wasm_memory_grow(irp, (IrInstSrcWasmMemoryGrow *)instruction); break; + case IrInstSrcIdSrc: + ir_print_builtin_src(irp, (IrInstSrcSrc *)instruction); + break; } fprintf(irp->f, "\n"); } -- 2.54.0 From 14acc65675a35f838b5e73aeaca595ced0c10a0c Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 18 Jun 2020 21:03:08 +0300 Subject: [PATCH 048/295] add tests for `@src` --- test/compile_errors.zig | 8 ++++++++ test/stage1/behavior.zig | 1 + test/stage1/behavior/src.zig | 15 +++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 test/stage1/behavior/src.zig diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 278d66ab9f1a365424687947b19f9e8cf7282731..3f898cc337730baf5acbf02b90f39e980be18283 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,14 @@ const tests = @import("tests.zig"); const std = @import("std"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add("@src outside function", + \\comptime { + \\ @src(); + \\} + , &[_][]const u8{ + "tmp.zig:2:5: error: @src outside function", + }); + cases.add("call assigned to constant", \\const Foo = struct { \\ x: i32, diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index 1620aa52df2f2021f9d8412f5efff12ae86d0f7c..8c567fa99f57e0aa9fd437c09ca0997ad82fc00f 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -131,4 +131,5 @@ comptime { } _ = @import("behavior/while.zig"); _ = @import("behavior/widening.zig"); + _ = @import("behavior/src.zig"); } diff --git a/test/stage1/behavior/src.zig b/test/stage1/behavior/src.zig new file mode 100644 index 0000000000000000000000000000000000000000..eec11543745040da7a5d8511bbd01f98e52b56e5 --- /dev/null +++ b/test/stage1/behavior/src.zig @@ -0,0 +1,15 @@ +const std = @import("std"); +const expect = std.testing.expect; + +test "@src" { + doTheTest(); +} + +fn doTheTest() void { + const src = @src(); + + expect(src.line == 9); + expect(src.column == 17); + expect(std.mem.endsWith(u8, src.fn_name, "doTheTest")); + expect(std.mem.endsWith(u8, src.file, "src.zig")); +} -- 2.54.0 From 4a387996311a025a021409f08a61bab9e9885987 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Jun 2020 17:09:10 -0400 Subject: [PATCH 049/295] make file and fn_name fields of SourceLocation also null-terminated One of the main motivating use cases for this language feature is tracing/profiling tools, which expect null-terminated strings for these values. Since the data is statically allocated, making them additionally null-terminated comes at no cost. This prevents the requirement of compile-time code to convert to null-termination, which could increase the compilation time of code with tracing enabled. See #2029 --- lib/std/builtin.zig | 4 ++-- src/ir.cpp | 12 ++++++------ test/stage1/behavior/src.zig | 2 ++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 822901be73c696cd969868dc88a430e0ae0c8a41..195b301840dcd03db8472f3a1ee938ea4085408a 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -134,8 +134,8 @@ pub const CallingConvention = enum { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. pub const SourceLocation = struct { - file: []const u8, - fn_name: []const u8, + file: [:0]const u8, + fn_name: [:0]const u8, line: u32, column: u32, }; diff --git a/src/ir.cpp b/src/ir.cpp index 6406458bf5c2ac280d49e7d024bc706c8584a52d..48e5db8f28b85e3411d737d37a4e570eec7cf134 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -30881,10 +30881,10 @@ static IrInstGen *ir_analyze_instruction_src(IrAnalyze *ira, IrInstSrcSrc *instr return ira->codegen->invalid_inst_gen; } - ZigType *u8_ptr = get_pointer_to_type_extra( + ZigType *u8_ptr = get_pointer_to_type_extra2( ira->codegen, ira->codegen->builtin_types.entry_u8, true, false, PtrLenUnknown, - 0, 0, 0, false); + 0, 0, 0, false, VECTOR_INDEX_NONE, nullptr, ira->codegen->intern.for_zero_byte()); ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr); ZigType *source_location_type = get_builtin_type(ira->codegen, "SourceLocation"); @@ -30899,23 +30899,23 @@ static IrInstGen *ir_analyze_instruction_src(IrAnalyze *ira, IrInstSrcSrc *instr ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 4); result->data.x_struct.fields = fields; - // file: []const u8 + // file: [:0]const u8 ensure_field_index(source_location_type, "file", 0); fields[0]->special = ConstValSpecialStatic; - fields[0]->type = u8_slice; ZigType *import = instruction->base.base.source_node->owner; Buf *path = import->data.structure.root_struct->path; ZigValue *file_name = create_const_str_lit(ira->codegen, path)->data.x_ptr.data.ref.pointee; init_const_slice(ira->codegen, fields[0], file_name, 0, buf_len(path), true); + fields[0]->type = u8_slice; - // fn_name: []const u8 + // fn_name: [:0]const u8 ensure_field_index(source_location_type, "fn_name", 1); fields[1]->special = ConstValSpecialStatic; - fields[1]->type = u8_slice; ZigValue *fn_name = create_const_str_lit(ira->codegen, &fn_entry->symbol_name)->data.x_ptr.data.ref.pointee; init_const_slice(ira->codegen, fields[1], fn_name, 0, buf_len(&fn_entry->symbol_name), true); + fields[1]->type = u8_slice; // line: u32 ensure_field_index(source_location_type, "line", 2); diff --git a/test/stage1/behavior/src.zig b/test/stage1/behavior/src.zig index eec11543745040da7a5d8511bbd01f98e52b56e5..27fa144e54fc4149c856a09649ce793d81700f6f 100644 --- a/test/stage1/behavior/src.zig +++ b/test/stage1/behavior/src.zig @@ -12,4 +12,6 @@ fn doTheTest() void { expect(src.column == 17); expect(std.mem.endsWith(u8, src.fn_name, "doTheTest")); expect(std.mem.endsWith(u8, src.file, "src.zig")); + expect(src.fn_name[src.fn_name.len] == 0); + expect(src.file[src.file.len] == 0); } -- 2.54.0 From b4eac0414a01b1096e8dd7e89455db88f19789cf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 11 Jun 2020 01:22:07 -0400 Subject: [PATCH 050/295] stage2: hook up Zig AST to ZIR * Introduce the concept of anonymous Decls * Primitive Hello, World with inline asm works * There is still an unsolved problem of how to manage ZIR instructions memory when generating from AST. Currently it leaks. --- lib/std/zig.zig | 17 + src-self-hosted/Module.zig | 1096 ++++++++++++++++++++++++++++++------ src-self-hosted/main.zig | 2 +- src-self-hosted/value.zig | 4 +- src-self-hosted/zir.zig | 52 +- 5 files changed, 980 insertions(+), 191 deletions(-) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index bb4f95579766ccfa2d7d70a541d4254427d53357..87960d432100bb0985fab1152436213e5119e350 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -1,4 +1,6 @@ +const std = @import("std.zig"); const tokenizer = @import("zig/tokenizer.zig"); + pub const Token = tokenizer.Token; pub const Tokenizer = tokenizer.Tokenizer; pub const parse = @import("zig/parse.zig").parse; @@ -9,6 +11,21 @@ pub const ast = @import("zig/ast.zig"); pub const system = @import("zig/system.zig"); pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget; +pub const SrcHash = [16]u8; + +/// If the source is small enough, it is used directly as the hash. +/// If it is long, blake3 hash is computed. +pub fn hashSrc(src: []const u8) SrcHash { + var out: SrcHash = undefined; + if (src.len <= SrcHash.len) { + std.mem.copy(u8, &out, src); + std.mem.set(u8, out[src.len..], 0); + } else { + std.crypto.Blake3.hash(src, &out); + } + return out; +} + pub fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } { var line: usize = 0; var column: usize = 0; diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 4bcc30a65e99282a533942a146e0cc66f8d5b72f..a157c53491b31cb14a2cf9b9edeebc1a884d89a1 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -15,13 +15,15 @@ const ir = @import("ir.zig"); const zir = @import("zir.zig"); const Module = @This(); const Inst = ir.Inst; +const ast = std.zig.ast; /// General-purpose allocator. allocator: *Allocator, /// Pointer to externally managed resource. root_pkg: *Package, /// Module owns this resource. -root_scope: *Scope.ZIRModule, +/// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`. +root_scope: *Scope, bin_file: link.ElfFile, bin_file_dir: std.fs.Dir, bin_file_path: []const u8, @@ -49,8 +51,8 @@ work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), /// a Decl can have a failed_decls entry but have analysis status of success. failed_decls: std.AutoHashMap(*Decl, *ErrorMsg), /// Using a map here for consistency with the other fields here. -/// The ErrorMsg memory is owned by the `Scope.ZIRModule`, using Module's allocator. -failed_files: std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg), +/// The ErrorMsg memory is owned by the `Scope`, using Module's allocator. +failed_files: std.AutoHashMap(*Scope, *ErrorMsg), /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Export`, using Module's allocator. failed_exports: std.AutoHashMap(*Export, *ErrorMsg), @@ -64,11 +66,18 @@ generation: u32 = 0, /// contains Decls that need to be deleted if they end up having no references to them. deletion_set: std.ArrayListUnmanaged(*Decl) = std.ArrayListUnmanaged(*Decl){}, -pub const WorkItem = union(enum) { +const WorkItem = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Decl, /// Decl has been determined to be outdated; perform semantic analysis again. re_analyze_decl: *Decl, + /// This AST node needs to be converted to a Decl and then semantically analyzed. + ast_gen_decl: AstGenDecl, + + const AstGenDecl = struct { + ast_node: *ast.Node, + scope: *Scope, + }; }; pub const Export = struct { @@ -99,10 +108,9 @@ pub const Decl = struct { /// mapping them to an address in the output file. /// Memory owned by this decl, using Module's allocator. name: [*:0]const u8, - /// The direct parent container of the Decl. This field will need to get more fleshed out when - /// self-hosted supports proper struct types and Zig AST => ZIR. + /// The direct parent container of the Decl. This is either a `Scope.File` or `Scope.ZIRModule`. /// Reference to externally owned memory. - scope: *Scope.ZIRModule, + scope: *Scope, /// Byte offset into the source file that contains this declaration. /// This is the base offset that src offsets within this Decl are relative to. src: usize, @@ -171,17 +179,8 @@ pub const Decl = struct { pub const Hash = [16]u8; - /// If the name is small enough, it is used directly as the hash. - /// If it is long, blake3 hash is computed. pub fn hashSimpleName(name: []const u8) Hash { - var out: Hash = undefined; - if (name.len <= Hash.len) { - mem.copy(u8, &out, name); - mem.set(u8, out[name.len..], 0); - } else { - std.crypto.Blake3.hash(name, &out); - } - return out; + return std.zig.hashSrc(name); } /// Must generate unique bytes with no collisions with other decls. @@ -290,6 +289,7 @@ pub const Scope = struct { .block => return self.cast(Block).?.arena, .decl => return &self.cast(DeclAnalysis).?.arena.allocator, .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, + .file => unreachable, } } @@ -300,16 +300,27 @@ pub const Scope = struct { .block => self.cast(Block).?.decl, .decl => self.cast(DeclAnalysis).?.decl, .zir_module => null, + .file => null, }; } - /// Asserts the scope has a parent which is a ZIRModule and + /// Asserts the scope has a parent which is a ZIRModule or File and /// returns it. - pub fn namespace(self: *Scope) *ZIRModule { + pub fn namespace(self: *Scope) *Scope { switch (self.tag) { .block => return self.cast(Block).?.decl.scope, .decl => return self.cast(DeclAnalysis).?.decl.scope, - .zir_module => return self.cast(ZIRModule).?, + .zir_module, .file => return self, + } + } + + /// Asserts the scope is a child of a File and has an AST tree and returns the tree. + pub fn tree(self: *Scope) *ast.Tree { + switch (self.tag) { + .file => return self.cast(File).?.contents.tree, + .zir_module => unreachable, + .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(File).?.contents.tree, + .block => return self.cast(Block).?.decl.scope.cast(File).?.contents.tree, } } @@ -325,12 +336,133 @@ pub const Scope = struct { }); } + /// Asserts the scope has a parent which is a ZIRModule or File and + /// returns the sub_file_path field. + pub fn subFilePath(base: *Scope) []const u8 { + switch (base.tag) { + .file => return @fieldParentPtr(File, "base", base).sub_file_path, + .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path, + .block => unreachable, + .decl => unreachable, + } + } + + pub fn unload(base: *Scope, allocator: *Allocator) void { + switch (base.tag) { + .file => return @fieldParentPtr(File, "base", base).unload(allocator), + .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(allocator), + .block => unreachable, + .decl => unreachable, + } + } + + pub fn getSource(base: *Scope, module: *Module) ![:0]const u8 { + switch (base.tag) { + .file => return @fieldParentPtr(File, "base", base).getSource(module), + .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module), + .block => unreachable, + .decl => unreachable, + } + } + + /// Asserts the scope is a File or ZIRModule and deinitializes it, then deallocates it. + pub fn destroy(base: *Scope, allocator: *Allocator) void { + switch (base.tag) { + .file => { + const scope_file = @fieldParentPtr(File, "base", base); + scope_file.deinit(allocator); + allocator.destroy(scope_file); + }, + .zir_module => { + const scope_zir_module = @fieldParentPtr(ZIRModule, "base", base); + scope_zir_module.deinit(allocator); + allocator.destroy(scope_zir_module); + }, + .block => unreachable, + .decl => unreachable, + } + } + pub const Tag = enum { + /// .zir source code. zir_module, + /// .zig source code. + file, block, decl, }; + pub const File = struct { + pub const base_tag: Tag = .file; + base: Scope = Scope{ .tag = base_tag }, + + /// Relative to the owning package's root_src_dir. + /// Reference to external memory, not owned by File. + sub_file_path: []const u8, + source: union(enum) { + unloaded: void, + bytes: [:0]const u8, + }, + contents: union { + not_available: void, + tree: *ast.Tree, + }, + status: enum { + never_loaded, + unloaded_success, + unloaded_parse_failure, + loaded_success, + }, + + pub fn unload(self: *File, allocator: *Allocator) void { + switch (self.status) { + .never_loaded, + .unloaded_parse_failure, + .unloaded_success, + => {}, + + .loaded_success => { + self.contents.tree.deinit(); + self.status = .unloaded_success; + }, + } + switch (self.source) { + .bytes => |bytes| { + allocator.free(bytes); + self.source = .{ .unloaded = {} }; + }, + .unloaded => {}, + } + } + + pub fn deinit(self: *File, allocator: *Allocator) void { + self.unload(allocator); + self.* = undefined; + } + + pub fn dumpSrc(self: *File, src: usize) void { + const loc = std.zig.findLineColumn(self.source.bytes, src); + std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); + } + + pub fn getSource(self: *File, module: *Module) ![:0]const u8 { + switch (self.source) { + .unloaded => { + const source = try module.root_pkg.root_src_dir.readFileAllocOptions( + module.allocator, + self.sub_file_path, + std.math.maxInt(u32), + 1, + 0, + ); + self.source = .{ .bytes = source }; + return source; + }, + .bytes => |bytes| return bytes, + } + } + }; + pub const ZIRModule = struct { pub const base_tag: Tag = .zir_module; base: Scope = Scope{ .tag = base_tag }, @@ -392,6 +524,23 @@ pub const Scope = struct { const loc = std.zig.findLineColumn(self.source.bytes, src); std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); } + + pub fn getSource(self: *ZIRModule, module: *Module) ![:0]const u8 { + switch (self.source) { + .unloaded => { + const source = try module.root_pkg.root_src_dir.readFileAllocOptions( + module.allocator, + self.sub_file_path, + std.math.maxInt(u32), + 1, + 0, + ); + self.source = .{ .bytes = source }; + return source; + }, + .bytes => |bytes| return bytes, + } + } }; /// This is a temporary structure, references to it are valid only @@ -466,16 +615,6 @@ pub const InitOptions = struct { }; pub fn init(gpa: *Allocator, options: InitOptions) !Module { - const root_scope = try gpa.create(Scope.ZIRModule); - errdefer gpa.destroy(root_scope); - - root_scope.* = .{ - .sub_file_path = options.root_pkg.root_src_path, - .source = .{ .unloaded = {} }, - .contents = .{ .not_available = {} }, - .status = .never_loaded, - }; - const bin_file_dir = options.bin_file_dir orelse std.fs.cwd(); var bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{ .target = options.target, @@ -485,6 +624,30 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { }); errdefer bin_file.deinit(); + const root_scope = blk: { + if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zig")) { + const root_scope = try gpa.create(Scope.File); + root_scope.* = .{ + .sub_file_path = options.root_pkg.root_src_path, + .source = .{ .unloaded = {} }, + .contents = .{ .not_available = {} }, + .status = .never_loaded, + }; + break :blk &root_scope.base; + } else if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zir")) { + const root_scope = try gpa.create(Scope.ZIRModule); + root_scope.* = .{ + .sub_file_path = options.root_pkg.root_src_path, + .source = .{ .unloaded = {} }, + .contents = .{ .not_available = {} }, + .status = .never_loaded, + }; + break :blk &root_scope.base; + } else { + unreachable; + } + }; + return Module{ .allocator = gpa, .root_pkg = options.root_pkg, @@ -497,7 +660,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa), .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa), .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa), - .failed_files = std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg).init(gpa), + .failed_files = std.AutoHashMap(*Scope, *ErrorMsg).init(gpa), .failed_exports = std.AutoHashMap(*Export, *ErrorMsg).init(gpa), .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), }; @@ -551,10 +714,7 @@ pub fn deinit(self: *Module) void { } self.export_owners.deinit(); } - { - self.root_scope.deinit(allocator); - allocator.destroy(self.root_scope); - } + self.root_scope.destroy(allocator); self.* = undefined; } @@ -574,16 +734,25 @@ pub fn update(self: *Module) !void { self.generation += 1; // TODO Use the cache hash file system to detect which source files changed. - // Here we simulate a full cache miss. - // Analyze the root source file now. - // Source files could have been loaded for any reason; to force a refresh we unload now. - self.root_scope.unload(self.allocator); - self.analyzeRoot(self.root_scope) catch |err| switch (err) { - error.AnalysisFail => { - assert(self.totalErrorCount() != 0); - }, - else => |e| return e, - }; + // Until then we simulate a full cache miss. Source files could have been loaded for any reason; + // to force a refresh we unload now. + if (self.root_scope.cast(Scope.File)) |zig_file| { + zig_file.unload(self.allocator); + self.analyzeRootSrcFile(zig_file) catch |err| switch (err) { + error.AnalysisFail => { + assert(self.totalErrorCount() != 0); + }, + else => |e| return e, + }; + } else if (self.root_scope.cast(Scope.ZIRModule)) |zir_module| { + zir_module.unload(self.allocator); + self.analyzeRootZIRModule(zir_module) catch |err| switch (err) { + error.AnalysisFail => { + assert(self.totalErrorCount() != 0); + }, + else => |e| return e, + }; + } try self.performAllTheWork(); @@ -619,10 +788,10 @@ pub fn makeBinFileWritable(self: *Module) !void { } pub fn totalErrorCount(self: *Module) usize { - return self.failed_decls.size + + const total = self.failed_decls.size + self.failed_files.size + - self.failed_exports.size + - @boolToInt(self.link_error_flags.no_entry_point_found); + self.failed_exports.size; + return if (total == 0) @boolToInt(self.link_error_flags.no_entry_point_found) else total; } pub fn getAllErrorsAlloc(self: *Module) !AllErrors { @@ -637,8 +806,8 @@ pub fn getAllErrorsAlloc(self: *Module) !AllErrors { while (it.next()) |kv| { const scope = kv.key; const err_msg = kv.value; - const source = try self.getSource(scope); - try AllErrors.add(&arena, &errors, scope.sub_file_path, source, err_msg.*); + const source = try scope.getSource(self); + try AllErrors.add(&arena, &errors, scope.subFilePath(), source, err_msg.*); } } { @@ -646,8 +815,8 @@ pub fn getAllErrorsAlloc(self: *Module) !AllErrors { while (it.next()) |kv| { const decl = kv.key; const err_msg = kv.value; - const source = try self.getSource(decl.scope); - try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*); + const source = try decl.scope.getSource(self); + try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); } } { @@ -655,12 +824,12 @@ pub fn getAllErrorsAlloc(self: *Module) !AllErrors { while (it.next()) |kv| { const decl = kv.key.owner_decl; const err_msg = kv.value; - const source = try self.getSource(decl.scope); - try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*); + const source = try decl.scope.getSource(self); + try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); } } - if (self.link_error_flags.no_entry_point_found) { + if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) { try errors.append(.{ .src_path = self.root_pkg.root_src_path, .line = 0, @@ -740,30 +909,491 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { => continue, .outdated => { - const zir_module = self.getSrcModule(decl.scope) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); - self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - self.allocator, - decl.src, - "unable to load source file '{}': {}", - .{ decl.scope.sub_file_path, @errorName(err) }, - )); - decl.analysis = .codegen_failure_retryable; - continue; - }, - }; - const decl_name = mem.spanZ(decl.name); - // We already detected deletions, so we know this will be found. - const src_decl = zir_module.findDecl(decl_name).?; - self.reAnalyzeDecl(decl, src_decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => continue, - }; + if (decl.scope.cast(Scope.File)) |file_scope| { + @panic("TODO re_analyze_decl for .zig files"); + } else if (decl.scope.cast(Scope.ZIRModule)) |zir_scope| { + const zir_module = self.getSrcModule(zir_scope) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); + self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + self.allocator, + decl.src, + "unable to load source file '{}': {}", + .{ zir_scope.sub_file_path, @errorName(err) }, + )); + decl.analysis = .codegen_failure_retryable; + continue; + }, + }; + const decl_name = mem.spanZ(decl.name); + // We already detected deletions, so we know this will be found. + const src_decl = zir_module.findDecl(decl_name).?; + self.reAnalyzeDecl(decl, src_decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => continue, + }; + } else { + unreachable; + } }, }, + .ast_gen_decl => |item| { + self.astGenDecl(item.scope, item.ast_node) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => continue, + }; + }, + }; +} + +fn astGenDecl(self: *Module, parent_scope: *Scope, ast_node: *ast.Node) !void { + switch (ast_node.id) { + .FnProto => { + const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", ast_node); + + const name_tok = fn_proto.name_token orelse + return self.failTok(parent_scope, fn_proto.fn_token, "missing function name", .{}); + const tree = parent_scope.tree(); + const name_loc = tree.token_locs[name_tok]; + const name = tree.tokenSliceLoc(name_loc); + const name_hash = Decl.hashSimpleName(name); + const contents_hash = std.zig.hashSrc(tree.getNodeSource(ast_node)); + const new_decl = try self.createNewDecl(parent_scope, name, name_loc.start, name_hash, contents_hash); + + // This DeclAnalysis scope's arena memory is discarded after the ZIR generation + // pass completes, and semantic analysis of it completes. + var gen_scope: Scope.DeclAnalysis = .{ + .decl = new_decl, + .arena = std.heap.ArenaAllocator.init(self.allocator), + }; + // TODO free this memory + //defer gen_scope.arena.deinit(); + + const body_node = fn_proto.body_node orelse + return self.failTok(&gen_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); + if (fn_proto.params_len != 0) { + return self.failTok( + &gen_scope.base, + fn_proto.params()[0].name_token.?, + "TODO implement function parameters", + .{}, + ); + } + if (fn_proto.lib_name) |lib_name| { + return self.failNode(&gen_scope.base, lib_name, "TODO implement function library name", .{}); + } + if (fn_proto.align_expr) |align_expr| { + return self.failNode(&gen_scope.base, align_expr, "TODO implement function align expression", .{}); + } + if (fn_proto.section_expr) |sect_expr| { + return self.failNode(&gen_scope.base, sect_expr, "TODO implement function section expression", .{}); + } + if (fn_proto.callconv_expr) |callconv_expr| { + return self.failNode( + &gen_scope.base, + callconv_expr, + "TODO implement function calling convention expression", + .{}, + ); + } + const return_type_expr = switch (fn_proto.return_type) { + .Explicit => |node| node, + .InferErrorSet => |node| return self.failNode(&gen_scope.base, node, "TODO implement inferred error sets", .{}), + .Invalid => |tok| return self.failTok(&gen_scope.base, tok, "unable to parse return type", .{}), + }; + + const return_type_inst = try self.astGenExpr(&gen_scope.base, return_type_expr); + const body_block = body_node.cast(ast.Node.Block).?; + const body = try self.astGenBlock(&gen_scope.base, body_block); + const fn_type_inst = try gen_scope.arena.allocator.create(zir.Inst.FnType); + fn_type_inst.* = .{ + .base = .{ + .tag = zir.Inst.FnType.base_tag, + .name = "", + .src = name_loc.start, + }, + .positionals = .{ + .return_type = return_type_inst, + .param_types = &[0]*zir.Inst{}, + }, + .kw_args = .{}, + }; + const fn_inst = try gen_scope.arena.allocator.create(zir.Inst.Fn); + fn_inst.* = .{ + .base = .{ + .tag = zir.Inst.Fn.base_tag, + .name = name, + .src = name_loc.start, + .contents_hash = contents_hash, + }, + .positionals = .{ + .fn_type = &fn_type_inst.base, + .body = body, + }, + .kw_args = .{}, + }; + try self.analyzeNewDecl(new_decl, &fn_inst.base); + + if (fn_proto.extern_export_inline_token) |maybe_export_token| { + if (tree.token_ids[maybe_export_token] == .Keyword_export) { + var str_inst = zir.Inst.Str{ + .base = .{ + .tag = zir.Inst.Str.base_tag, + .name = "", + .src = name_loc.start, + }, + .positionals = .{ + .bytes = name, + }, + .kw_args = .{}, + }; + var ref_inst = zir.Inst.Ref{ + .base = .{ + .tag = zir.Inst.Ref.base_tag, + .name = "", + .src = name_loc.start, + }, + .positionals = .{ + .operand = &str_inst.base, + }, + .kw_args = .{}, + }; + var export_inst = zir.Inst.Export{ + .base = .{ + .tag = zir.Inst.Export.base_tag, + .name = "", + .src = name_loc.start, + .contents_hash = contents_hash, + }, + .positionals = .{ + .symbol_name = &ref_inst.base, + .value = &fn_inst.base, + }, + .kw_args = .{}, + }; + // Here we analyze the export using the arena that expires at the end of this + // function call. + try self.analyzeExport(&gen_scope.base, &export_inst); + } + } + }, + .VarDecl => @panic("TODO var decl"), + .Comptime => @panic("TODO comptime decl"), + .Use => @panic("TODO usingnamespace decl"), + else => unreachable, + } +} + +fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir.Inst { + switch (ast_node.id) { + .Identifier => return self.astGenIdent(scope, @fieldParentPtr(ast.Node.Identifier, "base", ast_node)), + .Asm => return self.astGenAsm(scope, @fieldParentPtr(ast.Node.Asm, "base", ast_node)), + .StringLiteral => return self.astGenStringLiteral(scope, @fieldParentPtr(ast.Node.StringLiteral, "base", ast_node)), + .IntegerLiteral => return self.astGenIntegerLiteral(scope, @fieldParentPtr(ast.Node.IntegerLiteral, "base", ast_node)), + .BuiltinCall => return self.astGenBuiltinCall(scope, @fieldParentPtr(ast.Node.BuiltinCall, "base", ast_node)), + .Unreachable => return self.astGenUnreachable(scope, @fieldParentPtr(ast.Node.Unreachable, "base", ast_node)), + else => return self.failNode(scope, ast_node, "TODO implement astGenExpr for {}", .{@tagName(ast_node.id)}), + } +} + +fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerError!*zir.Inst { + const tree = scope.tree(); + const ident_name = tree.tokenSlice(ident.token); + if (mem.eql(u8, ident_name, "_")) { + return self.failNode(scope, &ident.base, "TODO implement '_' identifier", .{}); + } + + if (getSimplePrimitiveValue(ident_name)) |typed_value| { + const const_inst = try scope.arena().create(zir.Inst.Const); + const_inst.* = .{ + .base = .{ + .tag = zir.Inst.Const.base_tag, + .name = "", + .src = tree.token_locs[ident.token].start, + }, + .positionals = .{ + .typed_value = typed_value, + }, + .kw_args = .{}, + }; + return &const_inst.base; + } + + if (ident_name.len >= 2) integer: { + const first_c = ident_name[0]; + if (first_c == 'i' or first_c == 'u') { + const is_signed = first_c == 'i'; + const bit_count = std.fmt.parseInt(u16, ident_name[1..], 10) catch |err| switch (err) { + error.Overflow => return self.failNode( + scope, + &ident.base, + "primitive integer type '{}' exceeds maximum bit width of 65535", + .{ident_name}, + ), + error.InvalidCharacter => break :integer, + }; + return self.failNode(scope, &ident.base, "TODO implement arbitrary integer bitwidth types", .{}); + } + } + + return self.failNode(scope, &ident.base, "TODO implement identifier lookup", .{}); +} + +fn astGenStringLiteral(self: *Module, scope: *Scope, str_lit: *ast.Node.StringLiteral) InnerError!*zir.Inst { + const tree = scope.tree(); + const unparsed_bytes = tree.tokenSlice(str_lit.token); + const arena = scope.arena(); + + var bad_index: usize = undefined; + const bytes = std.zig.parseStringLiteral(arena, unparsed_bytes, &bad_index) catch |err| switch (err) { + error.InvalidCharacter => { + const bad_byte = unparsed_bytes[bad_index]; + const src = tree.token_locs[str_lit.token].start; + return self.fail(scope, src + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte}); + }, + else => |e| return e, + }; + + var str_inst = try arena.create(zir.Inst.Str); + str_inst.* = .{ + .base = .{ + .tag = zir.Inst.Str.base_tag, + .name = "", + .src = tree.token_locs[str_lit.token].start, + }, + .positionals = .{ + .bytes = bytes, + }, + .kw_args = .{}, + }; + var ref_inst = try arena.create(zir.Inst.Ref); + ref_inst.* = .{ + .base = .{ + .tag = zir.Inst.Ref.base_tag, + .name = "", + .src = tree.token_locs[str_lit.token].start, + }, + .positionals = .{ + .operand = &str_inst.base, + }, + .kw_args = .{}, + }; + return &ref_inst.base; +} + +fn astGenIntegerLiteral(self: *Module, scope: *Scope, int_lit: *ast.Node.IntegerLiteral) InnerError!*zir.Inst { + const arena = scope.arena(); + const tree = scope.tree(); + const bytes = tree.tokenSlice(int_lit.token); + + if (mem.startsWith(u8, bytes, "0x")) { + return self.failTok(scope, int_lit.token, "TODO implement 0x int prefix", .{}); + } else if (mem.startsWith(u8, bytes, "0o")) { + return self.failTok(scope, int_lit.token, "TODO implement 0o int prefix", .{}); + } else if (mem.startsWith(u8, bytes, "0b")) { + return self.failTok(scope, int_lit.token, "TODO implement 0b int prefix", .{}); + } + if (std.fmt.parseInt(u64, bytes, 10)) |small_int| { + var int_payload = try arena.create(Value.Payload.Int_u64); + int_payload.* = .{ + .int = small_int, + }; + var const_inst = try arena.create(zir.Inst.Const); + const_inst.* = .{ + .base = .{ + .tag = zir.Inst.Const.base_tag, + .name = "", + .src = tree.token_locs[int_lit.token].start, + }, + .positionals = .{ + .typed_value = .{ + .ty = Type.initTag(.comptime_int), + .val = Value.initPayload(&int_payload.base), + }, + }, + .kw_args = .{}, + }; + return &const_inst.base; + } else |err| { + return self.failTok(scope, int_lit.token, "TODO implement int literals that don't fit in a u64", .{}); + } +} + +fn astGenBlock(self: *Module, scope: *Scope, block_node: *ast.Node.Block) !zir.Module.Body { + if (block_node.label) |label| { + return self.failTok(scope, label, "TODO implement labeled blocks", .{}); + } + const arena = scope.arena(); + var instructions = std.ArrayList(*zir.Inst).init(arena); + + try instructions.ensureCapacity(block_node.statements_len); + + for (block_node.statements()) |statement| { + const inst = try self.astGenExpr(scope, statement); + instructions.appendAssumeCapacity(inst); + } + + return zir.Module.Body{ + .instructions = instructions.items, + }; +} + +fn astGenAsm(self: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zir.Inst { + if (asm_node.outputs.len != 0) { + return self.failNode(scope, &asm_node.base, "TODO implement asm with an output", .{}); + } + const arena = scope.arena(); + const tree = scope.tree(); + + const inputs = try arena.alloc(*zir.Inst, asm_node.inputs.len); + const args = try arena.alloc(*zir.Inst, asm_node.inputs.len); + + for (asm_node.inputs) |input, i| { + // TODO semantically analyze constraints + inputs[i] = try self.astGenExpr(scope, input.constraint); + args[i] = try self.astGenExpr(scope, input.expr); + } + + const return_type = try arena.create(zir.Inst.Const); + return_type.* = .{ + .base = .{ + .tag = zir.Inst.Const.base_tag, + .name = "", + .src = tree.token_locs[asm_node.asm_token].start, + }, + .positionals = .{ + .typed_value = .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.void_type), + }, + }, + .kw_args = .{}, + }; + + const asm_inst = try arena.create(zir.Inst.Asm); + asm_inst.* = .{ + .base = .{ + .tag = zir.Inst.Asm.base_tag, + .name = "", + .src = tree.token_locs[asm_node.asm_token].start, + }, + .positionals = .{ + .asm_source = try self.astGenExpr(scope, asm_node.template), + .return_type = &return_type.base, + }, + .kw_args = .{ + .@"volatile" = asm_node.volatile_token != null, + //.clobbers = TODO handle clobbers + .inputs = inputs, + .args = args, + }, + }; + return &asm_inst.base; +} + +fn astGenBuiltinCall(self: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + const tree = scope.tree(); + const builtin_name = tree.tokenSlice(call.builtin_token); + const arena = scope.arena(); + + if (mem.eql(u8, builtin_name, "@ptrToInt")) { + if (call.params_len != 1) { + return self.failTok(scope, call.builtin_token, "expected 1 parameter, found {}", .{call.params_len}); + } + const ptrtoint = try arena.create(zir.Inst.PtrToInt); + ptrtoint.* = .{ + .base = .{ + .tag = zir.Inst.PtrToInt.base_tag, + .name = "", + .src = tree.token_locs[call.builtin_token].start, + }, + .positionals = .{ + .ptr = try self.astGenExpr(scope, call.params()[0]), + }, + .kw_args = .{}, + }; + return &ptrtoint.base; + } else { + return self.failTok(scope, call.builtin_token, "TODO implement builtin call for '{}'", .{builtin_name}); + } +} + +fn astGenUnreachable(self: *Module, scope: *Scope, unreach_node: *ast.Node.Unreachable) InnerError!*zir.Inst { + const tree = scope.tree(); + const arena = scope.arena(); + const unreach = try arena.create(zir.Inst.Unreachable); + unreach.* = .{ + .base = .{ + .tag = zir.Inst.Unreachable.base_tag, + .name = "", + .src = tree.token_locs[unreach_node.token].start, + }, + .positionals = .{}, + .kw_args = .{}, }; + return &unreach.base; +} + +fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { + const simple_types = std.ComptimeStringMap(Value.Tag, .{ + .{ "u8", .u8_type }, + .{ "i8", .i8_type }, + .{ "isize", .isize_type }, + .{ "usize", .usize_type }, + .{ "c_short", .c_short_type }, + .{ "c_ushort", .c_ushort_type }, + .{ "c_int", .c_int_type }, + .{ "c_uint", .c_uint_type }, + .{ "c_long", .c_long_type }, + .{ "c_ulong", .c_ulong_type }, + .{ "c_longlong", .c_longlong_type }, + .{ "c_ulonglong", .c_ulonglong_type }, + .{ "c_longdouble", .c_longdouble_type }, + .{ "f16", .f16_type }, + .{ "f32", .f32_type }, + .{ "f64", .f64_type }, + .{ "f128", .f128_type }, + .{ "c_void", .c_void_type }, + .{ "bool", .bool_type }, + .{ "void", .void_type }, + .{ "type", .type_type }, + .{ "anyerror", .anyerror_type }, + .{ "comptime_int", .comptime_int_type }, + .{ "comptime_float", .comptime_float_type }, + .{ "noreturn", .noreturn_type }, + }); + if (simple_types.get(name)) |tag| { + return TypedValue{ + .ty = Type.initTag(.type), + .val = Value.initTag(tag), + }; + } + if (mem.eql(u8, name, "null")) { + return TypedValue{ + .ty = Type.initTag(.@"null"), + .val = Value.initTag(.null_value), + }; + } + if (mem.eql(u8, name, "undefined")) { + return TypedValue{ + .ty = Type.initTag(.@"undefined"), + .val = Value.initTag(.undef), + }; + } + if (mem.eql(u8, name, "true")) { + return TypedValue{ + .ty = Type.initTag(.bool), + .val = Value.initTag(.bool_true), + }; + } + if (mem.eql(u8, name, "false")) { + return TypedValue{ + .ty = Type.initTag(.bool), + .val = Value.initTag(.bool_false), + }; + } + return null; } fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void { @@ -783,29 +1413,12 @@ fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void } } -fn getSource(self: *Module, root_scope: *Scope.ZIRModule) ![:0]const u8 { - switch (root_scope.source) { - .unloaded => { - const source = try self.root_pkg.root_src_dir.readFileAllocOptions( - self.allocator, - root_scope.sub_file_path, - std.math.maxInt(u32), - 1, - 0, - ); - root_scope.source = .{ .bytes = source }; - return source; - }, - .bytes => |bytes| return bytes, - } -} - fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { switch (root_scope.status) { .never_loaded, .unloaded_success => { try self.failed_files.ensureCapacity(self.failed_files.size + 1); - const source = try self.getSource(root_scope); + const source = try root_scope.getSource(self); var keep_zir_module = false; const zir_module = try self.allocator.create(zir.Module); @@ -816,7 +1429,7 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { if (zir_module.error_msg) |src_err_msg| { self.failed_files.putAssumeCapacityNoClobber( - root_scope, + &root_scope.base, try ErrorMsg.create(self.allocator, src_err_msg.byte_offset, "{}", .{src_err_msg.msg}), ); root_scope.status = .unloaded_parse_failure; @@ -838,7 +1451,83 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { } } -fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { +fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { + switch (root_scope.status) { + .never_loaded, .unloaded_success => { + try self.failed_files.ensureCapacity(self.failed_files.size + 1); + + const source = try root_scope.getSource(self); + + var keep_tree = false; + const tree = try std.zig.parse(self.allocator, source); + defer if (!keep_tree) tree.deinit(); + + if (tree.errors.len != 0) { + const parse_err = tree.errors[0]; + + var msg = std.ArrayList(u8).init(self.allocator); + defer msg.deinit(); + + try parse_err.render(tree.token_ids, msg.outStream()); + const err_msg = try self.allocator.create(ErrorMsg); + err_msg.* = .{ + .msg = msg.toOwnedSlice(), + .byte_offset = tree.token_locs[parse_err.loc()].start, + }; + + self.failed_files.putAssumeCapacityNoClobber(&root_scope.base, err_msg); + root_scope.status = .unloaded_parse_failure; + return error.AnalysisFail; + } + + root_scope.status = .loaded_success; + root_scope.contents = .{ .tree = tree }; + keep_tree = true; + + return tree; + }, + + .unloaded_parse_failure => return error.AnalysisFail, + + .loaded_success => return root_scope.contents.tree, + } +} + +fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { + switch (root_scope.status) { + .never_loaded => { + const tree = try self.getAstTree(root_scope); + const decls = tree.root_node.decls(); + + try self.work_queue.ensureUnusedCapacity(decls.len); + + for (decls) |decl| { + if (decl.cast(ast.Node.FnProto)) |proto_decl| { + if (proto_decl.extern_export_inline_token) |maybe_export_token| { + if (tree.token_ids[maybe_export_token] == .Keyword_export) { + self.work_queue.writeItemAssumeCapacity(.{ + .ast_gen_decl = .{ + .ast_node = decl, + .scope = &root_scope.base, + }, + }); + } + } + } + // TODO also look for comptime blocks and exported globals + } + }, + + .unloaded_parse_failure, + .unloaded_success, + .loaded_success, + => { + @panic("TODO process update"); + }, + } +} + +fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { switch (root_scope.status) { .never_loaded => { const src_module = try self.getSrcModule(root_scope); @@ -882,12 +1571,10 @@ fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { if (self.decl_table.get(name_hash)) |kv| { const decl = kv.value; deleted_decls.removeAssertDiscard(decl); - const new_contents_hash = Decl.hashSimpleName(src_decl.contents); //std.debug.warn("'{}' contents: '{}'\n", .{ src_decl.name, src_decl.contents }); - if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) { - //std.debug.warn("'{}' {x} => {x}\n", .{ src_decl.name, decl.contents_hash, new_contents_hash }); + if (!mem.eql(u8, &src_decl.contents_hash, &decl.contents_hash)) { try self.markOutdatedDecl(decl); - decl.contents_hash = new_contents_hash; + decl.contents_hash = src_decl.contents_hash; } } else if (src_decl.cast(zir.Inst.Export)) |export_inst| { try exports_to_resolve.append(&export_inst.base); @@ -1038,7 +1725,7 @@ fn reAnalyzeDecl(self: *Module, decl: *Decl, old_inst: *zir.Inst) InnerError!voi }; errdefer decl_scope.arena.deinit(); - const typed_value = self.analyzeInstConst(&decl_scope.base, old_inst) catch |err| switch (err) { + const typed_value = self.analyzeConstInst(&decl_scope.base, old_inst) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => { switch (decl.analysis) { @@ -1109,9 +1796,91 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { decl.analysis = .outdated; } +fn allocateNewDecl( + self: *Module, + scope: *Scope, + src: usize, + contents_hash: std.zig.SrcHash, +) !*Decl { + const new_decl = try self.allocator.create(Decl); + new_decl.* = .{ + .name = "", + .scope = scope.namespace(), + .src = src, + .typed_value = .{ .never_succeeded = {} }, + .analysis = .in_progress, + .deletion_flag = false, + .contents_hash = contents_hash, + .link = link.ElfFile.TextBlock.empty, + .generation = 0, + }; + return new_decl; +} + +fn createNewDecl( + self: *Module, + scope: *Scope, + decl_name: []const u8, + src: usize, + name_hash: Decl.Hash, + contents_hash: std.zig.SrcHash, +) !*Decl { + try self.decl_table.ensureCapacity(self.decl_table.size + 1); + const new_decl = try self.allocateNewDecl(scope, src, contents_hash); + errdefer self.allocator.destroy(new_decl); + new_decl.name = try mem.dupeZ(self.allocator, u8, decl_name); + self.decl_table.putAssumeCapacityNoClobber(name_hash, new_decl); + return new_decl; +} + +fn analyzeNewDecl(self: *Module, new_decl: *Decl, old_inst: *zir.Inst) InnerError!void { + var decl_scope: Scope.DeclAnalysis = .{ + .decl = new_decl, + .arena = std.heap.ArenaAllocator.init(self.allocator), + }; + errdefer decl_scope.arena.deinit(); + + const typed_value = self.analyzeConstInst(&decl_scope.base, old_inst) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + switch (new_decl.analysis) { + .in_progress => new_decl.analysis = .dependency_failure, + else => {}, + } + new_decl.generation = self.generation; + return error.AnalysisFail; + }, + }; + const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); + + arena_state.* = decl_scope.arena.state; + + new_decl.typed_value = .{ + .most_recent = .{ + .typed_value = typed_value, + .arena = arena_state, + }, + }; + new_decl.analysis = .complete; + new_decl.generation = self.generation; + if (typed_value.ty.hasCodeGenBits()) { + // We don't fully codegen the decl until later, but we do need to reserve a global + // offset table index for it. This allows us to codegen decls out of dependency order, + // increasing how many computations can be done in parallel. + try self.bin_file.allocateDeclIndexes(new_decl); + try self.work_queue.writeItem(.{ .codegen_decl = new_decl }); + } +} + fn resolveDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { - const hash = Decl.hashSimpleName(old_inst.name); - if (self.decl_table.get(hash)) |kv| { + if (old_inst.name.len == 0) { + // If the name is empty, then we make this an anonymous Decl. + const new_decl = try self.allocateNewDecl(scope, old_inst.src, old_inst.contents_hash); + try self.analyzeNewDecl(new_decl, old_inst); + return new_decl; + } + const name_hash = Decl.hashSimpleName(old_inst.name); + if (self.decl_table.get(name_hash)) |kv| { const decl = kv.value; try self.reAnalyzeDecl(decl, old_inst); return decl; @@ -1119,63 +1888,9 @@ fn resolveDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*De // This is just a named reference to another decl. return self.analyzeDeclVal(scope, decl_val); } else { - const new_decl = blk: { - try self.decl_table.ensureCapacity(self.decl_table.size + 1); - const new_decl = try self.allocator.create(Decl); - errdefer self.allocator.destroy(new_decl); - const name = try mem.dupeZ(self.allocator, u8, old_inst.name); - errdefer self.allocator.free(name); - new_decl.* = .{ - .name = name, - .scope = scope.namespace(), - .src = old_inst.src, - .typed_value = .{ .never_succeeded = {} }, - .analysis = .in_progress, - .deletion_flag = false, - .contents_hash = Decl.hashSimpleName(old_inst.contents), - .link = link.ElfFile.TextBlock.empty, - .generation = 0, - }; - self.decl_table.putAssumeCapacityNoClobber(hash, new_decl); - break :blk new_decl; - }; + const new_decl = try self.createNewDecl(scope, old_inst.name, old_inst.src, name_hash, old_inst.contents_hash); + try self.analyzeNewDecl(new_decl, old_inst); - var decl_scope: Scope.DeclAnalysis = .{ - .decl = new_decl, - .arena = std.heap.ArenaAllocator.init(self.allocator), - }; - errdefer decl_scope.arena.deinit(); - - const typed_value = self.analyzeInstConst(&decl_scope.base, old_inst) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - switch (new_decl.analysis) { - .in_progress => new_decl.analysis = .dependency_failure, - else => {}, - } - new_decl.generation = self.generation; - return error.AnalysisFail; - }, - }; - const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); - - arena_state.* = decl_scope.arena.state; - - new_decl.typed_value = .{ - .most_recent = .{ - .typed_value = typed_value, - .arena = arena_state, - }, - }; - new_decl.analysis = .complete; - new_decl.generation = self.generation; - if (typed_value.ty.hasCodeGenBits()) { - // We don't fully codegen the decl until later, but we do need to reserve a global - // offset table index for it. This allows us to codegen decls out of dependency order, - // increasing how many computations can be done in parallel. - try self.bin_file.allocateDeclIndexes(new_decl); - try self.work_queue.writeItem(.{ .codegen_decl = new_decl }); - } return new_decl; } } @@ -1208,9 +1923,13 @@ fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In } } - const decl = try self.resolveCompleteDecl(scope, old_inst); - const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl); - return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); + if (scope.namespace().tag == .zir_module) { + const decl = try self.resolveCompleteDecl(scope, old_inst); + const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl); + return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); + } + + return self.analyzeInst(scope, old_inst); } fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { @@ -1451,7 +2170,7 @@ fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigI }); } -fn analyzeInstConst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { +fn analyzeConstInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { const new_inst = try self.analyzeInst(scope, old_inst); return TypedValue{ .ty = new_inst.ty, @@ -1459,11 +2178,16 @@ fn analyzeInstConst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerErro }; } +fn analyzeInstConst(self: *Module, scope: *Scope, const_inst: *zir.Inst.Const) InnerError!*Inst { + return self.constInst(scope, const_inst.base.src, const_inst.positionals.typed_value); +} + fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { switch (old_inst.tag) { .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.cast(zir.Inst.Breakpoint).?), .call => return self.analyzeInstCall(scope, old_inst.cast(zir.Inst.Call).?), .compileerror => return self.analyzeInstCompileError(scope, old_inst.cast(zir.Inst.CompileError).?), + .@"const" => return self.analyzeInstConst(scope, old_inst.cast(zir.Inst.Const).?), .declref => return self.analyzeInstDeclRef(scope, old_inst.cast(zir.Inst.DeclRef).?), .declval => return self.analyzeInstDeclVal(scope, old_inst.cast(zir.Inst.DeclVal).?), .str => { @@ -1520,18 +2244,23 @@ fn analyzeInstRef(self: *Module, scope: *Scope, inst: *zir.Inst.Ref) InnerError! fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { const decl_name = try self.resolveConstString(scope, inst.positionals.name); // This will need to get more fleshed out when there are proper structs & namespaces. - const zir_module = scope.namespace(); - const src_decl = zir_module.contents.module.findDecl(decl_name) orelse - return self.fail(scope, inst.positionals.name.src, "use of undeclared identifier '{}'", .{decl_name}); + const namespace = scope.namespace(); + if (namespace.cast(Scope.File)) |scope_file| { + return self.fail(scope, inst.base.src, "TODO implement declref for zig source", .{}); + } else if (namespace.cast(Scope.ZIRModule)) |zir_module| { + const src_decl = zir_module.contents.module.findDecl(decl_name) orelse + return self.fail(scope, inst.positionals.name.src, "use of undeclared identifier '{}'", .{decl_name}); - const decl = try self.resolveCompleteDecl(scope, src_decl); - return self.analyzeDeclRef(scope, inst.base.src, decl); + const decl = try self.resolveCompleteDecl(scope, src_decl); + return self.analyzeDeclRef(scope, inst.base.src, decl); + } else { + unreachable; + } } fn analyzeDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl { const decl_name = inst.positionals.name; - // This will need to get more fleshed out when there are proper structs & namespaces. - const zir_module = scope.namespace(); + const zir_module = scope.namespace().cast(Scope.ZIRModule).?; const src_decl = zir_module.contents.module.findDecl(decl_name) orelse return self.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name}); @@ -2316,6 +3045,30 @@ fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, a return self.failWithOwnedErrorMsg(scope, src, err_msg); } +fn failTok( + self: *Module, + scope: *Scope, + token_index: ast.TokenIndex, + comptime format: []const u8, + args: var, +) InnerError { + @setCold(true); + const src = scope.tree().token_locs[token_index].start; + return self.fail(scope, src, format, args); +} + +fn failNode( + self: *Module, + scope: *Scope, + ast_node: *ast.Node, + comptime format: []const u8, + args: var, +) InnerError { + @setCold(true); + const src = scope.tree().token_locs[ast_node.firstToken()].start; + return self.fail(scope, src, format, args); +} + fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *ErrorMsg) InnerError { { errdefer err_msg.destroy(self.allocator); @@ -2336,8 +3089,9 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err .zir_module => { const zir_module = scope.cast(Scope.ZIRModule).?; zir_module.status = .loaded_sema_failure; - self.failed_files.putAssumeCapacityNoClobber(zir_module, err_msg); + self.failed_files.putAssumeCapacityNoClobber(scope, err_msg); }, + .file => unreachable, } return error.AnalysisFail; } diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index eda9dcfc31b1fb5f20138ce4c76892fed1232f10..3c696cafa08709fd82023902a0fb501be8f57aef 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -86,7 +86,7 @@ const usage_build_generic = \\ zig build-obj [files] \\ \\Supported file types: - \\ (planned) .zig Zig source code + \\ .zig Zig source code \\ .zir Zig Intermediate Representation code \\ (planned) .o ELF object file \\ (planned) .o MACH-O (macOS) object file diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 5660cd760f84fd07b508e297ad588650f62ec29d..7caacc7960bb13ee22236ab429e0a72b8442897c 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -78,8 +78,8 @@ pub const Value = extern union { pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; }; - pub fn initTag(comptime small_tag: Tag) Value { - comptime assert(@enumToInt(small_tag) < Tag.no_payload_count); + pub fn initTag(small_tag: Tag) Value { + assert(@enumToInt(small_tag) < Tag.no_payload_count); return .{ .tag_if_small_enough = @enumToInt(small_tag) }; } diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index a00782771d7387d32a5b8fdabcbf53404fa40a0d..c1b547ce9962a11d338e58a35f8490af5b90aab8 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -14,20 +14,24 @@ const IrModule = @import("Module.zig"); /// These are instructions that correspond to the ZIR text format. See `ir.Inst` for /// in-memory, analyzed instructions with types and values. +/// TODO Separate into Decl and Inst. Decl will have extra fields, and will make the +/// undefined default field value of contents_hash no longer needed. pub const Inst = struct { tag: Tag, /// Byte offset into the source. src: usize, name: []const u8, - /// Slice into the source of the part after the = and before the next instruction. - contents: []const u8 = &[0]u8{}, + /// Hash of slice into the source of the part after the = and before the next instruction. + contents_hash: std.zig.SrcHash = undefined, /// These names are used directly as the instruction names in the text format. pub const Tag = enum { breakpoint, call, compileerror, + /// Special case, has no textual representation. + @"const", /// Represents a pointer to a global decl by name. declref, /// The syntax `@foo` is equivalent to `declval("foo")`. @@ -43,10 +47,10 @@ pub const Inst = struct { @"unreachable", @"return", @"fn", + fntype, @"export", primitive, ref, - fntype, intcast, bitcast, elemptr, @@ -64,6 +68,7 @@ pub const Inst = struct { .declref => DeclRef, .declval => DeclVal, .compileerror => CompileError, + .@"const" => Const, .str => Str, .int => Int, .ptrtoint => PtrToInt, @@ -147,6 +152,16 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const Const = struct { + pub const base_tag = Tag.@"const"; + base: Inst, + + positionals: struct { + typed_value: TypedValue, + }, + kw_args: struct {}, + }; + pub const Str = struct { pub const base_tag = Tag.str; base: Inst, @@ -253,6 +268,19 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const FnType = struct { + pub const base_tag = Tag.fntype; + base: Inst, + + positionals: struct { + param_types: []*Inst, + return_type: *Inst, + }, + kw_args: struct { + cc: std.builtin.CallingConvention = .Unspecified, + }, + }; + pub const Export = struct { pub const base_tag = Tag.@"export"; base: Inst, @@ -348,19 +376,6 @@ pub const Inst = struct { }; }; - pub const FnType = struct { - pub const base_tag = Tag.fntype; - base: Inst, - - positionals: struct { - param_types: []*Inst, - return_type: *Inst, - }, - kw_args: struct { - cc: std.builtin.CallingConvention = .Unspecified, - }, - }; - pub const IntCast = struct { pub const base_tag = Tag.intcast; base: Inst, @@ -526,6 +541,7 @@ pub const Module = struct { .declref => return self.writeInstToStreamGeneric(stream, .declref, decl, inst_table), .declval => return self.writeInstToStreamGeneric(stream, .declval, decl, inst_table), .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, decl, inst_table), + .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", decl, inst_table), .str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table), .int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table), .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table), @@ -619,6 +635,7 @@ pub const Module = struct { bool => return stream.writeByte("01"[@boolToInt(param)]), []u8, []const u8 => return std.zig.renderStringLiteral(param, stream), BigIntConst => return stream.print("{}", .{param}), + TypedValue => unreachable, // this is a special case else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } @@ -929,7 +946,7 @@ const Parser = struct { } try requireEatBytes(self, ")"); - inst_specific.base.contents = self.source[contents_start..self.i]; + inst_specific.base.contents_hash = std.zig.hashSrc(self.source[contents_start..self.i]); //std.debug.warn("parsed {} = '{}'\n", .{ inst_specific.base.name, inst_specific.base.contents }); return &inst_specific.base; @@ -978,6 +995,7 @@ const Parser = struct { *Inst => return parseParameterInst(self, body_ctx), []u8, []const u8 => return self.parseStringLiteral(), BigIntConst => return self.parseIntegerLiteral(), + TypedValue => return self.fail("'const' is a special instruction; not legal in ZIR text", .{}), else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), } return self.fail("TODO parse parameter {}", .{@typeName(T)}); -- 2.54.0 From 7e58c56ca72099f6e71752289be7165947bfaa04 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Jun 2020 04:29:54 -0400 Subject: [PATCH 051/295] self-hosted: implement Decl lookup * Take advantage of coercing anonymous struct literals to struct types. * Reworks Module to favor Zig source as the primary use case. Breaks ZIR compilation, which will have to be restored in a future commit. * Decl uses src_index rather then src, pointing to an AST Decl node index, or ZIR Module Decl index, rather than a byte offset. * ZIR instructions have an `analyzed_inst` field instead of Module having a hash table. * Module.Fn loses the `fn_type` field since it is redundant with its `owner_decl` `TypedValue` type. * Implement Type and Value copying. A ZIR Const instruction's TypedValue is copied to the Decl arena during analysis, which allows freeing the ZIR text instructions post-analysis. * Don't flush the ELF file if there are compilation errors. * Function return types allow arbitrarily complex expressions. * AST->ZIR for function calls and return statements. --- lib/std/zig/ast.zig | 2 + src-self-hosted/Module.zig | 946 +++++++++++++++++++-------------- src-self-hosted/TypedValue.zig | 8 + src-self-hosted/codegen.zig | 19 +- src-self-hosted/ir.zig | 9 + src-self-hosted/link.zig | 12 +- src-self-hosted/type.zig | 97 +++- src-self-hosted/value.zig | 120 ++++- src-self-hosted/zir.zig | 74 ++- src/codegen.cpp | 6 + 10 files changed, 865 insertions(+), 428 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 4d630112669609588022b2c9b53a9b569b32f09e..370f42b463e741c57020ee2726a9c72fe7e30bff 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -2260,6 +2260,8 @@ pub const Node = struct { } }; + /// TODO break this into separate Break, Continue, Return AST Nodes to save memory. + /// Could be further broken into LabeledBreak, LabeledContinue, and ReturnVoid to save even more. pub const ControlFlowExpression = struct { base: Node = Node{ .id = .ControlFlowExpression }, ltoken: TokenIndex, diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index a157c53491b31cb14a2cf9b9edeebc1a884d89a1..1dfca43c8d097d16ebbdc3638c8ccdffab3c2104 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -37,10 +37,10 @@ decl_exports: std.AutoHashMap(*Decl, []*Export), /// This table owns the Export memory. export_owners: std.AutoHashMap(*Decl, []*Export), /// Maps fully qualified namespaced names to the Decl struct for them. -decl_table: std.AutoHashMap(Decl.Hash, *Decl), +decl_table: std.AutoHashMap(Scope.NameHash, *Decl), optimize_mode: std.builtin.Mode, -link_error_flags: link.ElfFile.ErrorFlags = link.ElfFile.ErrorFlags{}, +link_error_flags: link.ElfFile.ErrorFlags = .{}, work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), @@ -64,20 +64,15 @@ generation: u32 = 0, /// Candidates for deletion. After a semantic analysis update completes, this list /// contains Decls that need to be deleted if they end up having no references to them. -deletion_set: std.ArrayListUnmanaged(*Decl) = std.ArrayListUnmanaged(*Decl){}, +deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, const WorkItem = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Decl, /// Decl has been determined to be outdated; perform semantic analysis again. re_analyze_decl: *Decl, - /// This AST node needs to be converted to a Decl and then semantically analyzed. - ast_gen_decl: AstGenDecl, - - const AstGenDecl = struct { - ast_node: *ast.Node, - scope: *Scope, - }; + /// The Decl needs to be analyzed and possibly export itself. + analyze_decl: *Decl, }; pub const Export = struct { @@ -111,9 +106,9 @@ pub const Decl = struct { /// The direct parent container of the Decl. This is either a `Scope.File` or `Scope.ZIRModule`. /// Reference to externally owned memory. scope: *Scope, - /// Byte offset into the source file that contains this declaration. - /// This is the base offset that src offsets within this Decl are relative to. - src: usize, + /// The AST Node decl index or ZIR Inst index that contains this declaration. + /// Must be recomputed when the corresponding source file is modified. + src_index: usize, /// The most recent value of the Decl after a successful semantic analysis. typed_value: union(enum) { never_succeeded: void, @@ -124,6 +119,9 @@ pub const Decl = struct { /// analysis of the function body is performed with this value set to `success`. Functions /// have their own analysis status field. analysis: enum { + /// This Decl corresponds to an AST Node that has not been referenced yet, and therefore + /// because of Zig's lazy declaration analysis, it will remain unanalyzed until referenced. + unreferenced, /// Semantic analysis for this Decl is running right now. This state detects dependency loops. in_progress, /// This Decl might be OK but it depends on another one which did not successfully complete @@ -133,6 +131,10 @@ pub const Decl = struct { /// There will be a corresponding ErrorMsg in Module.failed_decls. sema_failure, /// There will be a corresponding ErrorMsg in Module.failed_decls. + /// This indicates the failure was something like running out of disk space, + /// and attempting semantic analysis again may succeed. + sema_failure_retryable, + /// There will be a corresponding ErrorMsg in Module.failed_decls. codegen_failure, /// There will be a corresponding ErrorMsg in Module.failed_decls. /// This indicates the failure was something like running out of disk space, @@ -158,7 +160,7 @@ pub const Decl = struct { /// This is populated regardless of semantic analysis and code generation. link: link.ElfFile.TextBlock = link.ElfFile.TextBlock.empty, - contents_hash: Hash, + contents_hash: std.zig.SrcHash, /// The shallow set of other decls whose typed_value could possibly change if this Decl's /// typed_value is modified. @@ -177,19 +179,28 @@ pub const Decl = struct { allocator.destroy(self); } - pub const Hash = [16]u8; - - pub fn hashSimpleName(name: []const u8) Hash { - return std.zig.hashSrc(name); + pub fn src(self: Decl) usize { + switch (self.scope.tag) { + .file => { + const file = @fieldParentPtr(Scope.File, "base", self.scope); + const tree = file.contents.tree; + const decl_node = tree.root_node.decls()[self.src_index]; + return tree.token_locs[decl_node.firstToken()].start; + }, + .zir_module => { + const zir_module = @fieldParentPtr(Scope.ZIRModule, "base", self.scope); + const module = zir_module.contents.module; + const decl_inst = module.decls[self.src_index]; + return decl_inst.src; + }, + .block => unreachable, + .gen_zir => unreachable, + .decl => unreachable, + } } - /// Must generate unique bytes with no collisions with other decls. - /// The point of hashing here is only to limit the number of bytes of - /// the unique identifier to a fixed size (16 bytes). - pub fn fullyQualifiedNameHash(self: Decl) Hash { - // Right now we only have ZIRModule as the source. So this is simply the - // relative name of the decl. - return hashSimpleName(mem.spanZ(self.name)); + pub fn fullyQualifiedNameHash(self: Decl) Scope.NameHash { + return self.scope.fullyQualifiedNameHash(mem.spanZ(self.name)); } pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue { @@ -247,11 +258,9 @@ pub const Decl = struct { /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. pub const Fn = struct { /// This memory owned by the Decl's TypedValue.Managed arena allocator. - fn_type: Type, analysis: union(enum) { - /// The value is the source instruction. - queued: *zir.Inst.Fn, - in_progress: *Analysis, + queued: *ZIR, + in_progress, /// There will be a corresponding ErrorMsg in Module.failed_decls sema_failure, /// This Fn might be OK but it depends on another Decl which did not successfully complete @@ -265,16 +274,20 @@ pub const Fn = struct { /// of Fn analysis. pub const Analysis = struct { inner_block: Scope.Block, - /// TODO Performance optimization idea: instead of this inst_table, - /// use a field in the zir.Inst instead to track corresponding instructions - inst_table: std.AutoHashMap(*zir.Inst, *Inst), - needed_inst_capacity: usize, + }; + + /// Contains un-analyzed ZIR instructions generated from Zig source AST. + pub const ZIR = struct { + body: zir.Module.Body, + arena: std.heap.ArenaAllocator.State, }; }; pub const Scope = struct { tag: Tag, + pub const NameHash = [16]u8; + pub fn cast(base: *Scope, comptime T: type) ?*T { if (base.tag != T.base_tag) return null; @@ -288,6 +301,7 @@ pub const Scope = struct { switch (self.tag) { .block => return self.cast(Block).?.arena, .decl => return &self.cast(DeclAnalysis).?.arena.allocator, + .gen_zir => return &self.cast(GenZIR).?.arena.allocator, .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, .file => unreachable, } @@ -298,6 +312,7 @@ pub const Scope = struct { pub fn decl(self: *Scope) ?*Decl { return switch (self.tag) { .block => self.cast(Block).?.decl, + .gen_zir => self.cast(GenZIR).?.decl, .decl => self.cast(DeclAnalysis).?.decl, .zir_module => null, .file => null, @@ -309,11 +324,25 @@ pub const Scope = struct { pub fn namespace(self: *Scope) *Scope { switch (self.tag) { .block => return self.cast(Block).?.decl.scope, + .gen_zir => return self.cast(GenZIR).?.decl.scope, .decl => return self.cast(DeclAnalysis).?.decl.scope, .zir_module, .file => return self, } } + /// Must generate unique bytes with no collisions with other decls. + /// The point of hashing here is only to limit the number of bytes of + /// the unique identifier to a fixed size (16 bytes). + pub fn fullyQualifiedNameHash(self: *Scope, name: []const u8) NameHash { + switch (self.tag) { + .block => unreachable, + .gen_zir => unreachable, + .decl => unreachable, + .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name), + .file => return self.cast(File).?.fullyQualifiedNameHash(name), + } + } + /// Asserts the scope is a child of a File and has an AST tree and returns the tree. pub fn tree(self: *Scope) *ast.Tree { switch (self.tag) { @@ -321,6 +350,7 @@ pub const Scope = struct { .zir_module => unreachable, .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(File).?.contents.tree, .block => return self.cast(Block).?.decl.scope.cast(File).?.contents.tree, + .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(File).?.contents.tree, } } @@ -343,6 +373,7 @@ pub const Scope = struct { .file => return @fieldParentPtr(File, "base", base).sub_file_path, .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path, .block => unreachable, + .gen_zir => unreachable, .decl => unreachable, } } @@ -352,6 +383,7 @@ pub const Scope = struct { .file => return @fieldParentPtr(File, "base", base).unload(allocator), .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(allocator), .block => unreachable, + .gen_zir => unreachable, .decl => unreachable, } } @@ -360,6 +392,7 @@ pub const Scope = struct { switch (base.tag) { .file => return @fieldParentPtr(File, "base", base).getSource(module), .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module), + .gen_zir => unreachable, .block => unreachable, .decl => unreachable, } @@ -379,6 +412,7 @@ pub const Scope = struct { allocator.destroy(scope_zir_module); }, .block => unreachable, + .gen_zir => unreachable, .decl => unreachable, } } @@ -390,6 +424,7 @@ pub const Scope = struct { file, block, decl, + gen_zir, }; pub const File = struct { @@ -461,6 +496,11 @@ pub const Scope = struct { .bytes => |bytes| return bytes, } } + + pub fn fullyQualifiedNameHash(self: *File, name: []const u8) NameHash { + // We don't have struct scopes yet so this is currently just a simple name hash. + return std.zig.hashSrc(name); + } }; pub const ZIRModule = struct { @@ -541,6 +581,11 @@ pub const Scope = struct { .bytes => |bytes| return bytes, } } + + pub fn fullyQualifiedNameHash(self: *ZIRModule, name: []const u8) NameHash { + // ZIR modules only have 1 file with all decls global in the same namespace. + return std.zig.hashSrc(name); + } }; /// This is a temporary structure, references to it are valid only @@ -548,7 +593,7 @@ pub const Scope = struct { pub const Block = struct { pub const base_tag: Tag = .block; base: Scope = Scope{ .tag = base_tag }, - func: *Fn, + func: ?*Fn, decl: *Decl, instructions: ArrayListUnmanaged(*Inst), /// Points to the arena allocator of DeclAnalysis @@ -563,6 +608,16 @@ pub const Scope = struct { decl: *Decl, arena: std.heap.ArenaAllocator, }; + + /// This is a temporary structure, references to it are valid only + /// during semantic analysis of the decl. + pub const GenZIR = struct { + pub const base_tag: Tag = .gen_zir; + base: Scope = Scope{ .tag = base_tag }, + decl: *Decl, + arena: std.heap.ArenaAllocator, + instructions: std.ArrayList(*zir.Inst), + }; }; pub const Body = struct { @@ -656,7 +711,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .bin_file_path = options.bin_file_path, .bin_file = bin_file, .optimize_mode = options.optimize_mode, - .decl_table = std.AutoHashMap(Decl.Hash, *Decl).init(gpa), + .decl_table = std.AutoHashMap(Scope.NameHash, *Decl).init(gpa), .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa), .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa), .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa), @@ -765,14 +820,14 @@ pub fn update(self: *Module) !void { try self.deleteDecl(decl); } + self.link_error_flags = self.bin_file.error_flags; + // If there are any errors, we anticipate the source files being loaded // to report error messages. Otherwise we unload all source files to save memory. if (self.totalErrorCount() == 0) { self.root_scope.unload(self.allocator); + try self.bin_file.flush(); } - - try self.bin_file.flush(); - self.link_error_flags = self.bin_file.error_flags; } /// Having the file open for writing is problematic as far as executing the @@ -852,12 +907,14 @@ const InnerError = error{ OutOfMemory, AnalysisFail }; pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { while (self.work_queue.readItem()) |work_item| switch (work_item) { .codegen_decl => |decl| switch (decl.analysis) { + .unreferenced => unreachable, .in_progress => unreachable, .outdated => unreachable, .sema_failure, .codegen_failure, .dependency_failure, + .sema_failure_retryable, => continue, .complete, .codegen_failure_retryable => { @@ -865,12 +922,10 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { switch (payload.func.analysis) { .queued => self.analyzeFnBody(decl, payload.func) catch |err| switch (err) { error.AnalysisFail => { - if (payload.func.analysis == .queued) { - payload.func.analysis = .dependency_failure; - } + assert(payload.func.analysis != .in_progress); continue; }, - else => |e| return e, + error.OutOfMemory => return error.OutOfMemory, }, .in_progress => unreachable, .sema_failure, .dependency_failure => continue, @@ -889,7 +944,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( self.allocator, - decl.src, + decl.src(), "unable to codegen: {}", .{@errorName(err)}, )); @@ -899,6 +954,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { }, }, .re_analyze_decl => |decl| switch (decl.analysis) { + .unreferenced => unreachable, .in_progress => unreachable, .sema_failure, @@ -906,6 +962,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { .dependency_failure, .complete, .codegen_failure_retryable, + .sema_failure_retryable, => continue, .outdated => { @@ -918,7 +975,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( self.allocator, - decl.src, + decl.src(), "unable to load source file '{}': {}", .{ zir_scope.sub_file_path, @errorName(err) }, )); @@ -929,7 +986,8 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { const decl_name = mem.spanZ(decl.name); // We already detected deletions, so we know this will be found. const src_decl = zir_module.findDecl(decl_name).?; - self.reAnalyzeDecl(decl, src_decl) catch |err| switch (err) { + decl.src_index = src_decl.index; + self.reAnalyzeDecl(decl, src_decl.decl) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => continue, }; @@ -938,8 +996,8 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { } }, }, - .ast_gen_decl => |item| { - self.astGenDecl(item.scope, item.ast_node) catch |err| switch (err) { + .analyze_decl => |decl| { + self.ensureDeclAnalyzed(decl) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => continue, }; @@ -947,51 +1005,83 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { }; } -fn astGenDecl(self: *Module, parent_scope: *Scope, ast_node: *ast.Node) !void { +fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { + switch (decl.analysis) { + .in_progress => unreachable, + .outdated => unreachable, + + .sema_failure, + .sema_failure_retryable, + .codegen_failure, + .dependency_failure, + .codegen_failure_retryable, + => return error.AnalysisFail, + + .complete => return, + + .unreferenced => { + self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => return error.AnalysisFail, + else => { + try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); + self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + self.allocator, + decl.src(), + "unable to analyze: {}", + .{@errorName(err)}, + )); + decl.analysis = .sema_failure_retryable; + return error.AnalysisFail; + }, + }; + }, + } +} + +fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !void { + const file_scope = decl.scope.cast(Scope.File).?; + const tree = try self.getAstTree(file_scope); + const ast_node = tree.root_node.decls()[decl.src_index]; switch (ast_node.id) { .FnProto => { const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", ast_node); - const name_tok = fn_proto.name_token orelse - return self.failTok(parent_scope, fn_proto.fn_token, "missing function name", .{}); - const tree = parent_scope.tree(); - const name_loc = tree.token_locs[name_tok]; - const name = tree.tokenSliceLoc(name_loc); - const name_hash = Decl.hashSimpleName(name); - const contents_hash = std.zig.hashSrc(tree.getNodeSource(ast_node)); - const new_decl = try self.createNewDecl(parent_scope, name, name_loc.start, name_hash, contents_hash); + decl.analysis = .in_progress; - // This DeclAnalysis scope's arena memory is discarded after the ZIR generation - // pass completes, and semantic analysis of it completes. - var gen_scope: Scope.DeclAnalysis = .{ - .decl = new_decl, + // This arena allocator's memory is discarded at the end of this function. It is used + // to determine the type of the function, and hence the type of the decl, which is needed + // to complete the Decl analysis. + var fn_type_scope: Scope.GenZIR = .{ + .decl = decl, .arena = std.heap.ArenaAllocator.init(self.allocator), + .instructions = std.ArrayList(*zir.Inst).init(self.allocator), }; - // TODO free this memory - //defer gen_scope.arena.deinit(); + defer fn_type_scope.arena.deinit(); + defer fn_type_scope.instructions.deinit(); const body_node = fn_proto.body_node orelse - return self.failTok(&gen_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); + return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); if (fn_proto.params_len != 0) { return self.failTok( - &gen_scope.base, + &fn_type_scope.base, fn_proto.params()[0].name_token.?, "TODO implement function parameters", .{}, ); } if (fn_proto.lib_name) |lib_name| { - return self.failNode(&gen_scope.base, lib_name, "TODO implement function library name", .{}); + return self.failNode(&fn_type_scope.base, lib_name, "TODO implement function library name", .{}); } if (fn_proto.align_expr) |align_expr| { - return self.failNode(&gen_scope.base, align_expr, "TODO implement function align expression", .{}); + return self.failNode(&fn_type_scope.base, align_expr, "TODO implement function align expression", .{}); } if (fn_proto.section_expr) |sect_expr| { - return self.failNode(&gen_scope.base, sect_expr, "TODO implement function section expression", .{}); + return self.failNode(&fn_type_scope.base, sect_expr, "TODO implement function section expression", .{}); } if (fn_proto.callconv_expr) |callconv_expr| { return self.failNode( - &gen_scope.base, + &fn_type_scope.base, callconv_expr, "TODO implement function calling convention expression", .{}, @@ -999,82 +1089,94 @@ fn astGenDecl(self: *Module, parent_scope: *Scope, ast_node: *ast.Node) !void { } const return_type_expr = switch (fn_proto.return_type) { .Explicit => |node| node, - .InferErrorSet => |node| return self.failNode(&gen_scope.base, node, "TODO implement inferred error sets", .{}), - .Invalid => |tok| return self.failTok(&gen_scope.base, tok, "unable to parse return type", .{}), + .InferErrorSet => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement inferred error sets", .{}), + .Invalid => |tok| return self.failTok(&fn_type_scope.base, tok, "unable to parse return type", .{}), }; - const return_type_inst = try self.astGenExpr(&gen_scope.base, return_type_expr); - const body_block = body_node.cast(ast.Node.Block).?; - const body = try self.astGenBlock(&gen_scope.base, body_block); - const fn_type_inst = try gen_scope.arena.allocator.create(zir.Inst.FnType); - fn_type_inst.* = .{ - .base = .{ - .tag = zir.Inst.FnType.base_tag, - .name = "", - .src = name_loc.start, - }, - .positionals = .{ - .return_type = return_type_inst, - .param_types = &[0]*zir.Inst{}, - }, - .kw_args = .{}, + const return_type_inst = try self.astGenExpr(&fn_type_scope.base, return_type_expr); + const fn_src = tree.token_locs[fn_proto.fn_token].start; + const fn_type_inst = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.FnType, .{ + .return_type = return_type_inst, + .param_types = &[0]*zir.Inst{}, + }, .{}); + _ = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.Return, .{ .operand = fn_type_inst }, .{}); + + // We need the memory for the Type to go into the arena for the Decl + var decl_arena = std.heap.ArenaAllocator.init(self.allocator); + errdefer decl_arena.deinit(); + const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); + + var block_scope: Scope.Block = .{ + .func = null, + .decl = decl, + .instructions = .{}, + .arena = &decl_arena.allocator, }; - const fn_inst = try gen_scope.arena.allocator.create(zir.Inst.Fn); - fn_inst.* = .{ - .base = .{ - .tag = zir.Inst.Fn.base_tag, - .name = name, - .src = name_loc.start, - .contents_hash = contents_hash, - }, - .positionals = .{ - .fn_type = &fn_type_inst.base, - .body = body, + defer block_scope.instructions.deinit(self.allocator); + + const fn_type = try self.analyzeBodyValueAsType(&block_scope, .{ + .instructions = fn_type_scope.instructions.items, + }); + const new_func = try decl_arena.allocator.create(Fn); + const fn_payload = try decl_arena.allocator.create(Value.Payload.Function); + + const fn_zir = blk: { + // This scope's arena memory is discarded after the ZIR generation + // pass completes, and semantic analysis of it completes. + var gen_scope: Scope.GenZIR = .{ + .decl = decl, + .arena = std.heap.ArenaAllocator.init(self.allocator), + .instructions = std.ArrayList(*zir.Inst).init(self.allocator), + }; + errdefer gen_scope.arena.deinit(); + defer gen_scope.instructions.deinit(); + + const body_block = body_node.cast(ast.Node.Block).?; + + try self.astGenBlock(&gen_scope.base, body_block); + + const fn_zir = try gen_scope.arena.allocator.create(Fn.ZIR); + fn_zir.* = .{ + .body = .{ + .instructions = try gen_scope.arena.allocator.dupe(*zir.Inst, gen_scope.instructions.items), + }, + .arena = gen_scope.arena.state, + }; + break :blk fn_zir; + }; + + new_func.* = .{ + .analysis = .{ .queued = fn_zir }, + .owner_decl = decl, + }; + fn_payload.* = .{ .func = new_func }; + + decl_arena_state.* = decl_arena.state; + decl.typed_value = .{ + .most_recent = .{ + .typed_value = .{ + .ty = fn_type, + .val = Value.initPayload(&fn_payload.base), + }, + .arena = decl_arena_state, }, - .kw_args = .{}, }; - try self.analyzeNewDecl(new_decl, &fn_inst.base); + decl.analysis = .complete; + decl.generation = self.generation; + + // We don't fully codegen the decl until later, but we do need to reserve a global + // offset table index for it. This allows us to codegen decls out of dependency order, + // increasing how many computations can be done in parallel. + try self.bin_file.allocateDeclIndexes(decl); + try self.work_queue.writeItem(.{ .codegen_decl = decl }); if (fn_proto.extern_export_inline_token) |maybe_export_token| { if (tree.token_ids[maybe_export_token] == .Keyword_export) { - var str_inst = zir.Inst.Str{ - .base = .{ - .tag = zir.Inst.Str.base_tag, - .name = "", - .src = name_loc.start, - }, - .positionals = .{ - .bytes = name, - }, - .kw_args = .{}, - }; - var ref_inst = zir.Inst.Ref{ - .base = .{ - .tag = zir.Inst.Ref.base_tag, - .name = "", - .src = name_loc.start, - }, - .positionals = .{ - .operand = &str_inst.base, - }, - .kw_args = .{}, - }; - var export_inst = zir.Inst.Export{ - .base = .{ - .tag = zir.Inst.Export.base_tag, - .name = "", - .src = name_loc.start, - .contents_hash = contents_hash, - }, - .positionals = .{ - .symbol_name = &ref_inst.base, - .value = &fn_inst.base, - }, - .kw_args = .{}, - }; - // Here we analyze the export using the arena that expires at the end of this - // function call. - try self.analyzeExport(&gen_scope.base, &export_inst); + const export_src = tree.token_locs[maybe_export_token].start; + const name_loc = tree.token_locs[fn_proto.name_token.?]; + const name = tree.tokenSliceLoc(name_loc); + // The scope needs to have the decl in it. + try self.analyzeExport(&block_scope.base, export_src, name, decl); } } }, @@ -1085,6 +1187,19 @@ fn astGenDecl(self: *Module, parent_scope: *Scope, ast_node: *ast.Node) !void { } } +fn analyzeBodyValueAsType(self: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type { + try self.analyzeBody(&block_scope.base, body); + for (block_scope.instructions.items) |inst| { + if (inst.cast(Inst.Ret)) |ret| { + const val = try self.resolveConstValue(&block_scope.base, ret.args.operand); + return val.toType(); + } else { + return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); + } + } + unreachable; +} + fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir.Inst { switch (ast_node.id) { .Identifier => return self.astGenIdent(scope, @fieldParentPtr(ast.Node.Identifier, "base", ast_node)), @@ -1092,11 +1207,33 @@ fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir .StringLiteral => return self.astGenStringLiteral(scope, @fieldParentPtr(ast.Node.StringLiteral, "base", ast_node)), .IntegerLiteral => return self.astGenIntegerLiteral(scope, @fieldParentPtr(ast.Node.IntegerLiteral, "base", ast_node)), .BuiltinCall => return self.astGenBuiltinCall(scope, @fieldParentPtr(ast.Node.BuiltinCall, "base", ast_node)), + .Call => return self.astGenCall(scope, @fieldParentPtr(ast.Node.Call, "base", ast_node)), .Unreachable => return self.astGenUnreachable(scope, @fieldParentPtr(ast.Node.Unreachable, "base", ast_node)), + .ControlFlowExpression => return self.astGenControlFlowExpression(scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)), else => return self.failNode(scope, ast_node, "TODO implement astGenExpr for {}", .{@tagName(ast_node.id)}), } } +fn astGenControlFlowExpression( + self: *Module, + scope: *Scope, + cfe: *ast.Node.ControlFlowExpression, +) InnerError!*zir.Inst { + switch (cfe.kind) { + .Break => return self.failNode(scope, &cfe.base, "TODO implement astGenExpr for Break", .{}), + .Continue => return self.failNode(scope, &cfe.base, "TODO implement astGenExpr for Continue", .{}), + .Return => {}, + } + const tree = scope.tree(); + const src = tree.token_locs[cfe.ltoken].start; + if (cfe.rhs) |rhs_node| { + const operand = try self.astGenExpr(scope, rhs_node); + return self.addZIRInst(scope, src, zir.Inst.Return, .{ .operand = operand }, .{}); + } else { + return self.addZIRInst(scope, src, zir.Inst.ReturnVoid, .{}, .{}); + } +} + fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerError!*zir.Inst { const tree = scope.tree(); const ident_name = tree.tokenSlice(ident.token); @@ -1105,19 +1242,8 @@ fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerE } if (getSimplePrimitiveValue(ident_name)) |typed_value| { - const const_inst = try scope.arena().create(zir.Inst.Const); - const_inst.* = .{ - .base = .{ - .tag = zir.Inst.Const.base_tag, - .name = "", - .src = tree.token_locs[ident.token].start, - }, - .positionals = .{ - .typed_value = typed_value, - }, - .kw_args = .{}, - }; - return &const_inst.base; + const src = tree.token_locs[ident.token].start; + return self.addZIRInstConst(scope, src, typed_value); } if (ident_name.len >= 2) integer: { @@ -1137,7 +1263,15 @@ fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerE } } - return self.failNode(scope, &ident.base, "TODO implement identifier lookup", .{}); + // Decl lookup + const namespace = scope.namespace(); + const name_hash = namespace.fullyQualifiedNameHash(ident_name); + if (self.decl_table.getValue(name_hash)) |decl| { + const src = tree.token_locs[ident.token].start; + return try self.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); + } + + return self.failNode(scope, &ident.base, "TODO implement local variable identifier lookup", .{}); } fn astGenStringLiteral(self: *Module, scope: *Scope, str_lit: *ast.Node.StringLiteral) InnerError!*zir.Inst { @@ -1155,31 +1289,9 @@ fn astGenStringLiteral(self: *Module, scope: *Scope, str_lit: *ast.Node.StringLi else => |e| return e, }; - var str_inst = try arena.create(zir.Inst.Str); - str_inst.* = .{ - .base = .{ - .tag = zir.Inst.Str.base_tag, - .name = "", - .src = tree.token_locs[str_lit.token].start, - }, - .positionals = .{ - .bytes = bytes, - }, - .kw_args = .{}, - }; - var ref_inst = try arena.create(zir.Inst.Ref); - ref_inst.* = .{ - .base = .{ - .tag = zir.Inst.Ref.base_tag, - .name = "", - .src = tree.token_locs[str_lit.token].start, - }, - .positionals = .{ - .operand = &str_inst.base, - }, - .kw_args = .{}, - }; - return &ref_inst.base; + const src = tree.token_locs[str_lit.token].start; + const str_inst = try self.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); + return self.addZIRInst(scope, src, zir.Inst.Ref, .{ .operand = str_inst }, .{}); } fn astGenIntegerLiteral(self: *Module, scope: *Scope, int_lit: *ast.Node.IntegerLiteral) InnerError!*zir.Inst { @@ -1195,48 +1307,25 @@ fn astGenIntegerLiteral(self: *Module, scope: *Scope, int_lit: *ast.Node.Integer return self.failTok(scope, int_lit.token, "TODO implement 0b int prefix", .{}); } if (std.fmt.parseInt(u64, bytes, 10)) |small_int| { - var int_payload = try arena.create(Value.Payload.Int_u64); - int_payload.* = .{ - .int = small_int, - }; - var const_inst = try arena.create(zir.Inst.Const); - const_inst.* = .{ - .base = .{ - .tag = zir.Inst.Const.base_tag, - .name = "", - .src = tree.token_locs[int_lit.token].start, - }, - .positionals = .{ - .typed_value = .{ - .ty = Type.initTag(.comptime_int), - .val = Value.initPayload(&int_payload.base), - }, - }, - .kw_args = .{}, - }; - return &const_inst.base; + const int_payload = try arena.create(Value.Payload.Int_u64); + int_payload.* = .{ .int = small_int }; + const src = tree.token_locs[int_lit.token].start; + return self.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.comptime_int), + .val = Value.initPayload(&int_payload.base), + }); } else |err| { return self.failTok(scope, int_lit.token, "TODO implement int literals that don't fit in a u64", .{}); } } -fn astGenBlock(self: *Module, scope: *Scope, block_node: *ast.Node.Block) !zir.Module.Body { +fn astGenBlock(self: *Module, scope: *Scope, block_node: *ast.Node.Block) !void { if (block_node.label) |label| { return self.failTok(scope, label, "TODO implement labeled blocks", .{}); } - const arena = scope.arena(); - var instructions = std.ArrayList(*zir.Inst).init(arena); - - try instructions.ensureCapacity(block_node.statements_len); - for (block_node.statements()) |statement| { - const inst = try self.astGenExpr(scope, statement); - instructions.appendAssumeCapacity(inst); + _ = try self.astGenExpr(scope, statement); } - - return zir.Module.Body{ - .instructions = instructions.items, - }; } fn astGenAsm(self: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zir.Inst { @@ -1255,84 +1344,59 @@ fn astGenAsm(self: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!* args[i] = try self.astGenExpr(scope, input.expr); } - const return_type = try arena.create(zir.Inst.Const); - return_type.* = .{ - .base = .{ - .tag = zir.Inst.Const.base_tag, - .name = "", - .src = tree.token_locs[asm_node.asm_token].start, - }, - .positionals = .{ - .typed_value = .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.void_type), - }, - }, - .kw_args = .{}, - }; - - const asm_inst = try arena.create(zir.Inst.Asm); - asm_inst.* = .{ - .base = .{ - .tag = zir.Inst.Asm.base_tag, - .name = "", - .src = tree.token_locs[asm_node.asm_token].start, - }, - .positionals = .{ - .asm_source = try self.astGenExpr(scope, asm_node.template), - .return_type = &return_type.base, - }, - .kw_args = .{ - .@"volatile" = asm_node.volatile_token != null, - //.clobbers = TODO handle clobbers - .inputs = inputs, - .args = args, - }, - }; - return &asm_inst.base; + const src = tree.token_locs[asm_node.asm_token].start; + const return_type = try self.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.void_type), + }); + const asm_inst = try self.addZIRInst(scope, src, zir.Inst.Asm, .{ + .asm_source = try self.astGenExpr(scope, asm_node.template), + .return_type = return_type, + }, .{ + .@"volatile" = asm_node.volatile_token != null, + //.clobbers = TODO handle clobbers + .inputs = inputs, + .args = args, + }); + return asm_inst; } fn astGenBuiltinCall(self: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { const tree = scope.tree(); const builtin_name = tree.tokenSlice(call.builtin_token); - const arena = scope.arena(); if (mem.eql(u8, builtin_name, "@ptrToInt")) { if (call.params_len != 1) { return self.failTok(scope, call.builtin_token, "expected 1 parameter, found {}", .{call.params_len}); } - const ptrtoint = try arena.create(zir.Inst.PtrToInt); - ptrtoint.* = .{ - .base = .{ - .tag = zir.Inst.PtrToInt.base_tag, - .name = "", - .src = tree.token_locs[call.builtin_token].start, - }, - .positionals = .{ - .ptr = try self.astGenExpr(scope, call.params()[0]), - }, - .kw_args = .{}, - }; - return &ptrtoint.base; + const src = tree.token_locs[call.builtin_token].start; + return self.addZIRInst(scope, src, zir.Inst.PtrToInt, .{ + .ptr = try self.astGenExpr(scope, call.params()[0]), + }, .{}); } else { return self.failTok(scope, call.builtin_token, "TODO implement builtin call for '{}'", .{builtin_name}); } } +fn astGenCall(self: *Module, scope: *Scope, call: *ast.Node.Call) InnerError!*zir.Inst { + const tree = scope.tree(); + + if (call.params_len != 0) { + return self.failNode(scope, &call.base, "TODO implement fn calls with parameters", .{}); + } + const lhs = try self.astGenExpr(scope, call.lhs); + + const src = tree.token_locs[call.lhs.firstToken()].start; + return self.addZIRInst(scope, src, zir.Inst.Call, .{ + .func = lhs, + .args = &[0]*zir.Inst{}, + }, .{}); +} + fn astGenUnreachable(self: *Module, scope: *Scope, unreach_node: *ast.Node.Unreachable) InnerError!*zir.Inst { const tree = scope.tree(); - const arena = scope.arena(); - const unreach = try arena.create(zir.Inst.Unreachable); - unreach.* = .{ - .base = .{ - .tag = zir.Inst.Unreachable.base_tag, - .name = "", - .src = tree.token_locs[unreach_node.token].start, - }, - .positionals = .{}, - .kw_args = .{}, - }; - return &unreach.base; + const src = tree.token_locs[unreach_node.token].start; + return self.addZIRInst(scope, src, zir.Inst.Unreachable, .{}, .{}); } fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { @@ -1501,19 +1565,23 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { try self.work_queue.ensureUnusedCapacity(decls.len); - for (decls) |decl| { - if (decl.cast(ast.Node.FnProto)) |proto_decl| { - if (proto_decl.extern_export_inline_token) |maybe_export_token| { + for (decls) |src_decl, decl_i| { + if (src_decl.cast(ast.Node.FnProto)) |fn_proto| { + // We will create a Decl for it regardless of analysis status. + const name_tok = fn_proto.name_token orelse + @panic("TODO handle missing function name in the parser"); + const name_loc = tree.token_locs[name_tok]; + const name = tree.tokenSliceLoc(name_loc); + const name_hash = root_scope.fullyQualifiedNameHash(name); + const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); + const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); + if (fn_proto.extern_export_inline_token) |maybe_export_token| { if (tree.token_ids[maybe_export_token] == .Keyword_export) { - self.work_queue.writeItemAssumeCapacity(.{ - .ast_gen_decl = .{ - .ast_node = decl, - .scope = &root_scope.base, - }, - }); + self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); } } } + // TODO also look for global variable declarations // TODO also look for comptime blocks and exported globals } }, @@ -1567,7 +1635,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { } for (src_module.decls) |src_decl| { - const name_hash = Decl.hashSimpleName(src_decl.name); + const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name); if (self.decl_table.get(name_hash)) |kv| { const decl = kv.value; deleted_decls.removeAssertDiscard(decl); @@ -1664,36 +1732,33 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { // Use the Decl's arena for function memory. var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); defer decl.typed_value.most_recent.arena.?.* = arena.state; - var analysis: Fn.Analysis = .{ - .inner_block = .{ - .func = func, - .decl = decl, - .instructions = .{}, - .arena = &arena.allocator, - }, - .needed_inst_capacity = 0, - .inst_table = std.AutoHashMap(*zir.Inst, *Inst).init(self.allocator), + var inner_block: Scope.Block = .{ + .func = func, + .decl = decl, + .instructions = .{}, + .arena = &arena.allocator, }; - defer analysis.inner_block.instructions.deinit(self.allocator); - defer analysis.inst_table.deinit(); + defer inner_block.instructions.deinit(self.allocator); - const fn_inst = func.analysis.queued; - func.analysis = .{ .in_progress = &analysis }; + const fn_zir = func.analysis.queued; + defer fn_zir.arena.promote(self.allocator).deinit(); + func.analysis = .{ .in_progress = {} }; + std.debug.warn("set {} to in_progress\n", .{decl.name}); - try self.analyzeBody(&analysis.inner_block.base, fn_inst.positionals.body); + try self.analyzeBody(&inner_block.base, fn_zir.body); - func.analysis = .{ - .success = .{ - .instructions = try arena.allocator.dupe(*Inst, analysis.inner_block.instructions.items), - }, - }; + const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items); + func.analysis = .{ .success = .{ .instructions = instructions } }; + std.debug.warn("set {} to success\n", .{decl.name}); } fn reAnalyzeDecl(self: *Module, decl: *Decl, old_inst: *zir.Inst) InnerError!void { switch (decl.analysis) { + .unreferenced => unreachable, .in_progress => unreachable, .dependency_failure, .sema_failure, + .sema_failure_retryable, .codegen_failure, .codegen_failure_retryable, .complete, @@ -1702,7 +1767,6 @@ fn reAnalyzeDecl(self: *Module, decl: *Decl, old_inst: *zir.Inst) InnerError!voi .outdated => {}, // Decl re-analysis } //std.debug.warn("re-analyzing {}\n", .{decl.name}); - decl.src = old_inst.src; // The exports this Decl performs will be re-discovered, so we remove them here // prior to re-analysis. @@ -1771,11 +1835,13 @@ fn reAnalyzeDecl(self: *Module, decl: *Decl, old_inst: *zir.Inst) InnerError!voi if (type_changed or typed_value.val.tag() != .function) { for (decl.dependants.items) |dep| { switch (dep.analysis) { + .unreferenced => unreachable, .in_progress => unreachable, .outdated => continue, // already queued for update .dependency_failure, .sema_failure, + .sema_failure_retryable, .codegen_failure, .codegen_failure_retryable, .complete, @@ -1799,16 +1865,16 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { fn allocateNewDecl( self: *Module, scope: *Scope, - src: usize, + src_index: usize, contents_hash: std.zig.SrcHash, ) !*Decl { const new_decl = try self.allocator.create(Decl); new_decl.* = .{ .name = "", .scope = scope.namespace(), - .src = src, + .src_index = src_index, .typed_value = .{ .never_succeeded = {} }, - .analysis = .in_progress, + .analysis = .unreferenced, .deletion_flag = false, .contents_hash = contents_hash, .link = link.ElfFile.TextBlock.empty, @@ -1821,12 +1887,12 @@ fn createNewDecl( self: *Module, scope: *Scope, decl_name: []const u8, - src: usize, - name_hash: Decl.Hash, + src_index: usize, + name_hash: Scope.NameHash, contents_hash: std.zig.SrcHash, ) !*Decl { try self.decl_table.ensureCapacity(self.decl_table.size + 1); - const new_decl = try self.allocateNewDecl(scope, src, contents_hash); + const new_decl = try self.allocateNewDecl(scope, src_index, contents_hash); errdefer self.allocator.destroy(new_decl); new_decl.name = try mem.dupeZ(self.allocator, u8, decl_name); self.decl_table.putAssumeCapacityNoClobber(name_hash, new_decl); @@ -1840,6 +1906,8 @@ fn analyzeNewDecl(self: *Module, new_decl: *Decl, old_inst: *zir.Inst) InnerErro }; errdefer decl_scope.arena.deinit(); + new_decl.analysis = .in_progress; + const typed_value = self.analyzeConstInst(&decl_scope.base, old_inst) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => { @@ -1873,37 +1941,40 @@ fn analyzeNewDecl(self: *Module, new_decl: *Decl, old_inst: *zir.Inst) InnerErro } fn resolveDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { - if (old_inst.name.len == 0) { - // If the name is empty, then we make this an anonymous Decl. - const new_decl = try self.allocateNewDecl(scope, old_inst.src, old_inst.contents_hash); - try self.analyzeNewDecl(new_decl, old_inst); - return new_decl; - } - const name_hash = Decl.hashSimpleName(old_inst.name); - if (self.decl_table.get(name_hash)) |kv| { - const decl = kv.value; - try self.reAnalyzeDecl(decl, old_inst); - return decl; - } else if (old_inst.cast(zir.Inst.DeclVal)) |decl_val| { - // This is just a named reference to another decl. - return self.analyzeDeclVal(scope, decl_val); - } else { - const new_decl = try self.createNewDecl(scope, old_inst.name, old_inst.src, name_hash, old_inst.contents_hash); - try self.analyzeNewDecl(new_decl, old_inst); + assert(old_inst.name.len == 0); + // If the name is empty, then we make this an anonymous Decl. + const scope_decl = scope.decl().?; + const new_decl = try self.allocateNewDecl(scope, scope_decl.src_index, old_inst.contents_hash); + try self.analyzeNewDecl(new_decl, old_inst); + return new_decl; + //const name_hash = Decl.hashSimpleName(old_inst.name); + //if (self.decl_table.get(name_hash)) |kv| { + // const decl = kv.value; + // decl.src = old_inst.src; + // try self.reAnalyzeDecl(decl, old_inst); + // return decl; + //} else if (old_inst.cast(zir.Inst.DeclVal)) |decl_val| { + // // This is just a named reference to another decl. + // return self.analyzeDeclVal(scope, decl_val); + //} else { + // const new_decl = try self.createNewDecl(scope, old_inst.name, old_inst.src, name_hash, old_inst.contents_hash); + // try self.analyzeNewDecl(new_decl, old_inst); - return new_decl; - } + // return new_decl; + //} } /// Declares a dependency on the decl. fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { const decl = try self.resolveDecl(scope, old_inst); switch (decl.analysis) { + .unreferenced => unreachable, .in_progress => unreachable, .outdated => unreachable, .dependency_failure, .sema_failure, + .sema_failure_retryable, .codegen_failure, .codegen_failure_retryable, => return error.AnalysisFail, @@ -1916,20 +1987,9 @@ fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerE return decl; } +/// TODO look into removing this function fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { - if (scope.cast(Scope.Block)) |block| { - if (block.func.analysis.in_progress.inst_table.get(old_inst)) |kv| { - return kv.value; - } - } - - if (scope.namespace().tag == .zir_module) { - const decl = try self.resolveCompleteDecl(scope, old_inst); - const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl); - return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); - } - - return self.analyzeInst(scope, old_inst); + return old_inst.analyzed_inst; } fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { @@ -1977,21 +2037,15 @@ fn resolveType(self: *Module, scope: *Scope, old_inst: *zir.Inst) !Type { return val.toType(); } -fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!void { - try self.decl_exports.ensureCapacity(self.decl_exports.size + 1); - try self.export_owners.ensureCapacity(self.export_owners.size + 1); - const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name); - const exported_decl = try self.resolveCompleteDecl(scope, export_inst.positionals.value); +fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const u8, exported_decl: *Decl) !void { const typed_value = exported_decl.typed_value.most_recent.typed_value; switch (typed_value.ty.zigTypeTag()) { .Fn => {}, - else => return self.fail( - scope, - export_inst.positionals.value.src, - "unable to export type '{}'", - .{typed_value.ty}, - ), + else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), } + try self.decl_exports.ensureCapacity(self.decl_exports.size + 1); + try self.export_owners.ensureCapacity(self.export_owners.size + 1); + const new_export = try self.allocator.create(Export); errdefer self.allocator.destroy(new_export); @@ -1999,7 +2053,7 @@ fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) In new_export.* = .{ .options = .{ .name = symbol_name }, - .src = export_inst.base.src, + .src = src, .link = .{}, .owner_decl = owner_decl, .exported_decl = exported_decl, @@ -2030,7 +2084,7 @@ fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) In try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( self.allocator, - export_inst.base.src, + src, "unable to export: {}", .{@errorName(err)}, )); @@ -2039,7 +2093,6 @@ fn analyzeExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) In }; } -/// TODO should not need the cast on the last parameter at the callsites fn addNewInstArgs( self: *Module, block: *Scope.Block, @@ -2053,6 +2106,47 @@ fn addNewInstArgs( return &inst.base; } +fn newZIRInst( + allocator: *Allocator, + src: usize, + comptime T: type, + positionals: std.meta.fieldInfo(T, "positionals").field_type, + kw_args: std.meta.fieldInfo(T, "kw_args").field_type, +) !*zir.Inst { + const inst = try allocator.create(T); + inst.* = .{ + .base = .{ + .tag = T.base_tag, + .name = "", + .src = src, + }, + .positionals = positionals, + .kw_args = kw_args, + }; + return &inst.base; +} + +fn addZIRInst( + self: *Module, + scope: *Scope, + src: usize, + comptime T: type, + positionals: std.meta.fieldInfo(T, "positionals").field_type, + kw_args: std.meta.fieldInfo(T, "kw_args").field_type, +) !*zir.Inst { + const gen_zir = scope.cast(Scope.GenZIR).?; + try gen_zir.instructions.ensureCapacity(gen_zir.instructions.items.len + 1); + const inst = try newZIRInst(&gen_zir.arena.allocator, src, T, positionals, kw_args); + gen_zir.instructions.appendAssumeCapacity(inst); + return inst; +} + +/// TODO The existence of this function is a workaround for a bug in stage1. +fn addZIRInstConst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst { + const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type; + return self.addZIRInst(scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); +} + fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime T: type) !*T { const inst = try block.arena.create(T); inst.* = .{ @@ -2107,6 +2201,13 @@ fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst { }); } +fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst { + return self.constInst(scope, src, .{ + .ty = Type.initTag(.noreturn), + .val = Value.initTag(.the_one_possible_value), + }); +} + fn constUndef(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { return self.constInst(scope, src, .{ .ty = ty, @@ -2179,7 +2280,10 @@ fn analyzeConstInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerErro } fn analyzeInstConst(self: *Module, scope: *Scope, const_inst: *zir.Inst.Const) InnerError!*Inst { - return self.constInst(scope, const_inst.base.src, const_inst.positionals.typed_value); + // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions + // after analysis. + const typed_value_copy = try const_inst.positionals.typed_value.copy(scope.arena()); + return self.constInst(scope, const_inst.base.src, typed_value_copy); } fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { @@ -2190,6 +2294,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In .@"const" => return self.analyzeInstConst(scope, old_inst.cast(zir.Inst.Const).?), .declref => return self.analyzeInstDeclRef(scope, old_inst.cast(zir.Inst.DeclRef).?), .declval => return self.analyzeInstDeclVal(scope, old_inst.cast(zir.Inst.DeclVal).?), + .declval_in_module => return self.analyzeInstDeclValInModule(scope, old_inst.cast(zir.Inst.DeclValInModule).?), .str => { const bytes = old_inst.cast(zir.Inst.Str).?.positionals.bytes; // The bytes references memory inside the ZIR module, which can get deallocated @@ -2208,11 +2313,9 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In .@"asm" => return self.analyzeInstAsm(scope, old_inst.cast(zir.Inst.Asm).?), .@"unreachable" => return self.analyzeInstUnreachable(scope, old_inst.cast(zir.Inst.Unreachable).?), .@"return" => return self.analyzeInstRet(scope, old_inst.cast(zir.Inst.Return).?), + .returnvoid => return self.analyzeInstRetVoid(scope, old_inst.cast(zir.Inst.ReturnVoid).?), .@"fn" => return self.analyzeInstFn(scope, old_inst.cast(zir.Inst.Fn).?), - .@"export" => { - try self.analyzeExport(scope, old_inst.cast(zir.Inst.Export).?); - return self.constVoid(scope, old_inst.src); - }, + .@"export" => return self.analyzeInstExport(scope, old_inst.cast(zir.Inst.Export).?), .primitive => return self.analyzeInstPrimitive(scope, old_inst.cast(zir.Inst.Primitive).?), .ref => return self.analyzeInstRef(scope, old_inst.cast(zir.Inst.Ref).?), .fntype => return self.analyzeInstFnType(scope, old_inst.cast(zir.Inst.FnType).?), @@ -2227,13 +2330,20 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In } } +fn analyzeInstExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst { + const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name); + const exported_decl = try self.resolveCompleteDecl(scope, export_inst.positionals.value); + try self.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl); + return self.constVoid(scope, export_inst.base.src); +} + fn analyzeInstCompileError(self: *Module, scope: *Scope, inst: *zir.Inst.CompileError) InnerError!*Inst { return self.fail(scope, inst.base.src, "{}", .{inst.positionals.msg}); } fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.Breakpoint) InnerError!*Inst { const b = try self.requireRuntimeBlock(scope, inst.base.src); - return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, Inst.Args(Inst.Breakpoint){}); + return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, {}); } fn analyzeInstRef(self: *Module, scope: *Scope, inst: *zir.Inst.Ref) InnerError!*Inst { @@ -2251,7 +2361,7 @@ fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) Inn const src_decl = zir_module.contents.module.findDecl(decl_name) orelse return self.fail(scope, inst.positionals.name.src, "use of undeclared identifier '{}'", .{decl_name}); - const decl = try self.resolveCompleteDecl(scope, src_decl); + const decl = try self.resolveCompleteDecl(scope, src_decl.decl); return self.analyzeDeclRef(scope, inst.base.src, decl); } else { unreachable; @@ -2264,7 +2374,7 @@ fn analyzeDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerEr const src_decl = zir_module.contents.module.findDecl(decl_name) orelse return self.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name}); - const decl = try self.resolveCompleteDecl(scope, src_decl); + const decl = try self.resolveCompleteDecl(scope, src_decl.decl); return decl; } @@ -2275,12 +2385,34 @@ fn analyzeInstDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) Inn return self.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); } +fn analyzeInstDeclValInModule(self: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst { + const decl = inst.positionals.decl; + const ptr = try self.analyzeDeclRef(scope, inst.base.src, decl); + return self.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); +} + fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst { + const scope_decl = scope.decl().?; + try self.declareDeclDependency(scope_decl, decl); + self.ensureDeclAnalyzed(decl) catch |err| { + if (scope.cast(Scope.Block)) |block| { + if (block.func) |func| { + func.analysis = .dependency_failure; + } else { + block.decl.analysis = .dependency_failure; + } + } else { + scope_decl.analysis = .dependency_failure; + } + return err; + }; + const decl_tv = try decl.typedValue(); const ty_payload = try scope.arena().create(Type.Payload.SingleConstPointer); ty_payload.* = .{ .pointee_type = decl_tv.ty }; const val_payload = try scope.arena().create(Value.Payload.DeclRef); val_payload.* = .{ .decl = decl }; + return self.constInst(scope, src, .{ .ty = Type.initPayload(&ty_payload.base), .val = Value.initPayload(&val_payload.base), @@ -2345,26 +2477,26 @@ fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerErro } const b = try self.requireRuntimeBlock(scope, inst.base.src); - return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Call, Inst.Args(Inst.Call){ + return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Call, .{ .func = func, .args = casted_args, }); } fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { - const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type); - const new_func = try scope.arena().create(Fn); - new_func.* = .{ - .fn_type = fn_type, - .analysis = .{ .queued = fn_inst }, - .owner_decl = scope.decl().?, - }; - const fn_payload = try scope.arena().create(Value.Payload.Function); - fn_payload.* = .{ .func = new_func }; - return self.constInst(scope, fn_inst.base.src, .{ - .ty = fn_type, - .val = Value.initPayload(&fn_payload.base), - }); + return self.fail(scope, fn_inst.base.src, "TODO implement ZIR fn inst", .{}); + //const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type); + //const new_func = try scope.arena().create(Fn); + //new_func.* = .{ + // .analysis = .{ .queued = fn_inst }, + // .owner_decl = scope.decl().?, + //}; + //const fn_payload = try scope.arena().create(Value.Payload.Function); + //fn_payload.* = .{ .func = new_func }; + //return self.constInst(scope, fn_inst.base.src, .{ + // .ty = fn_type, + // .val = Value.initPayload(&fn_payload.base), + //}); } fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { @@ -2377,6 +2509,13 @@ fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) Inn return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args)); } + if (return_type.zigTypeTag() == .Void and + fntype.positionals.param_types.len == 0 and + fntype.kw_args.cc == .Unspecified) + { + return self.constType(scope, fntype.base.src, Type.initTag(.fn_void_no_args)); + } + if (return_type.zigTypeTag() == .NoReturn and fntype.positionals.param_types.len == 0 and fntype.kw_args.cc == .Naked) @@ -2412,7 +2551,7 @@ fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *zir.Inst.PtrToIn // TODO handle known-pointer-address const b = try self.requireRuntimeBlock(scope, ptrtoint.base.src); const ty = Type.initTag(.usize); - return self.addNewInstArgs(b, ptrtoint.base.src, ty, Inst.PtrToInt, Inst.Args(Inst.PtrToInt){ .ptr = ptr }); + return self.addNewInstArgs(b, ptrtoint.base.src, ty, Inst.PtrToInt, .{ .ptr = ptr }); } fn analyzeInstFieldPtr(self: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst { @@ -2604,7 +2743,7 @@ fn analyzeInstAsm(self: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerEr } const b = try self.requireRuntimeBlock(scope, assembly.base.src); - return self.addNewInstArgs(b, assembly.base.src, return_type, Inst.Assembly, Inst.Args(Inst.Assembly){ + return self.addNewInstArgs(b, assembly.base.src, return_type, Inst.Assembly, .{ .asm_source = asm_source, .is_volatile = assembly.kw_args.@"volatile", .output = output, @@ -2640,20 +2779,12 @@ fn analyzeInstCmp(self: *Module, scope: *Scope, inst: *zir.Inst.Cmp) InnerError! } const b = try self.requireRuntimeBlock(scope, inst.base.src); switch (op) { - .eq => return self.addNewInstArgs( - b, - inst.base.src, - Type.initTag(.bool), - Inst.IsNull, - Inst.Args(Inst.IsNull){ .operand = opt_operand }, - ), - .neq => return self.addNewInstArgs( - b, - inst.base.src, - Type.initTag(.bool), - Inst.IsNonNull, - Inst.Args(Inst.IsNonNull){ .operand = opt_operand }, - ), + .eq => return self.addNewInstArgs(b, inst.base.src, Type.initTag(.bool), Inst.IsNull, .{ + .operand = opt_operand, + }), + .neq => return self.addNewInstArgs(b, inst.base.src, Type.initTag(.bool), Inst.IsNonNull, .{ + .operand = opt_operand, + }), else => unreachable, } } else if (is_equality_cmp and @@ -2748,23 +2879,19 @@ fn analyzeInstUnreachable(self: *Module, scope: *Scope, unreach: *zir.Inst.Unrea } fn analyzeInstRet(self: *Module, scope: *Scope, inst: *zir.Inst.Return) InnerError!*Inst { + const operand = try self.resolveInst(scope, inst.positionals.operand); const b = try self.requireRuntimeBlock(scope, inst.base.src); - return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.Ret, {}); + return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.Ret, .{ .operand = operand }); +} + +fn analyzeInstRetVoid(self: *Module, scope: *Scope, inst: *zir.Inst.ReturnVoid) InnerError!*Inst { + const b = try self.requireRuntimeBlock(scope, inst.base.src); + return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.RetVoid, {}); } fn analyzeBody(self: *Module, scope: *Scope, body: zir.Module.Body) !void { - if (scope.cast(Scope.Block)) |b| { - const analysis = b.func.analysis.in_progress; - analysis.needed_inst_capacity += body.instructions.len; - try analysis.inst_table.ensureCapacity(analysis.needed_inst_capacity); - for (body.instructions) |src_inst| { - const new_inst = try self.analyzeInst(scope, src_inst); - analysis.inst_table.putAssumeCapacityNoClobber(src_inst, new_inst); - } - } else { - for (body.instructions) |src_inst| { - _ = try self.analyzeInst(scope, src_inst); - } + for (body.instructions) |src_inst| { + src_inst.analyzed_inst = try self.analyzeInst(scope, src_inst); } } @@ -2847,7 +2974,7 @@ fn cmpNumeric( }; const casted_lhs = try self.coerce(scope, dest_type, lhs); const casted_rhs = try self.coerce(scope, dest_type, rhs); - return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){ + return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, .{ .lhs = casted_lhs, .rhs = casted_rhs, .op = op, @@ -2951,7 +3078,7 @@ fn cmpNumeric( const casted_lhs = try self.coerce(scope, dest_type, lhs); const casted_rhs = try self.coerce(scope, dest_type, lhs); - return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){ + return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, .{ .lhs = casted_lhs, .rhs = casted_rhs, .op = op, @@ -3028,7 +3155,7 @@ fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { } // TODO validate the type size and other compile errors const b = try self.requireRuntimeBlock(scope, inst.src); - return self.addNewInstArgs(b, inst.src, dest_type, Inst.BitCast, Inst.Args(Inst.BitCast){ .operand = inst }); + return self.addNewInstArgs(b, inst.src, dest_type, Inst.BitCast, .{ .operand = inst }); } fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { @@ -3083,9 +3210,18 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err }, .block => { const block = scope.cast(Scope.Block).?; - block.func.analysis = .sema_failure; + if (block.func) |func| { + func.analysis = .sema_failure; + } else { + block.decl.analysis = .sema_failure; + } self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg); }, + .gen_zir => { + const gen_zir = scope.cast(Scope.GenZIR).?; + gen_zir.decl.analysis = .sema_failure; + self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); + }, .zir_module => { const zir_module = scope.cast(Scope.ZIRModule).?; zir_module.status = .loaded_sema_failure; diff --git a/src-self-hosted/TypedValue.zig b/src-self-hosted/TypedValue.zig index 83a8f3c09ff427d28cea7faf36da10e4f8e555ff..48b2c04970d15593a420f73d40967f003cbec9d9 100644 --- a/src-self-hosted/TypedValue.zig +++ b/src-self-hosted/TypedValue.zig @@ -21,3 +21,11 @@ pub const Managed = struct { self.* = undefined; } }; + +/// Assumes arena allocation. Does a recursive copy. +pub fn copy(self: TypedValue, allocator: *Allocator) error{OutOfMemory}!TypedValue { + return TypedValue{ + .ty = try self.ty.copy(allocator), + .val = try self.val.copy(allocator), + }; +} diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index a3a15b463e45fa8e9cf04195377e060eda5cc1ab..f412b2dad2163440f744b2ef6a7a57080eec41bb 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -178,6 +178,7 @@ const Function = struct { .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), .ret => return self.genRet(inst.cast(ir.Inst.Ret).?), + .retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?), .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?), .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?), .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?), @@ -213,7 +214,7 @@ const Function = struct { try self.code.resize(self.code.items.len + 7); self.code.items[self.code.items.len - 7 ..][0..3].* = [3]u8{ 0xff, 0x14, 0x25 }; mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], got_addr); - const return_type = func.fn_type.fnReturnType(); + const return_type = func.owner_decl.typed_value.most_recent.typed_value.ty.fnReturnType(); switch (return_type.zigTypeTag()) { .Void => return MCValue{ .none = {} }, .NoReturn => return MCValue{ .unreach = {} }, @@ -230,16 +231,28 @@ const Function = struct { } } - fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue { + fn ret(self: *Function, src: usize, mcv: MCValue) !MCValue { + if (mcv != .none) { + return self.fail(src, "TODO implement return with non-void operand", .{}); + } switch (self.target.cpu.arch) { .i386, .x86_64 => { try self.code.append(0xc3); // ret }, - else => return self.fail(inst.base.src, "TODO implement return for {}", .{self.target.cpu.arch}), + else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), } return .unreach; } + fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue { + const operand = try self.resolveInst(inst.args.operand); + return self.ret(inst.base.src, operand); + } + + fn genRetVoid(self: *Function, inst: *ir.Inst.RetVoid) !MCValue { + return self.ret(inst.base.src, .none); + } + fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue { switch (self.target.cpu.arch) { else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 330b1c4135aba75b40c384cdf4d64ed2a7e28e4a..387c88df3b5300814e5588a145873b03737d035b 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -26,6 +26,7 @@ pub const Inst = struct { isnull, ptrtoint, ret, + retvoid, unreach, }; @@ -146,6 +147,14 @@ pub const Inst = struct { pub const Ret = struct { pub const base_tag = Tag.ret; base: Inst, + args: struct { + operand: *Inst, + }, + }; + + pub const RetVoid = struct { + pub const base_tag = Tag.retvoid; + base: Inst, args: void, }; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index f394d45864b9f7f771841a17c75969078ab82781..fd38bee8631998638f836996b24b908206284e61 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -956,10 +956,10 @@ pub const ElfFile = struct { try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); if (self.local_symbol_free_list.popOrNull()) |i| { - //std.debug.warn("reusing symbol index {} for {}\n", .{i, decl.name}); + std.debug.warn("reusing symbol index {} for {}\n", .{i, decl.name}); decl.link.local_sym_index = i; } else { - //std.debug.warn("allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); + std.debug.warn("allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -1002,7 +1002,7 @@ pub const ElfFile = struct { defer code_buffer.deinit(); const typed_value = decl.typed_value.most_recent.typed_value; - const code = switch (try codegen.generateSymbol(self, decl.src, typed_value, &code_buffer)) { + const code = switch (try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer)) { .externally_managed => |x| x, .appended => code_buffer.items, .fail => |em| { @@ -1027,11 +1027,11 @@ pub const ElfFile = struct { !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); if (need_realloc) { const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); - //std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); + std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); if (vaddr != local_sym.st_value) { local_sym.st_value = vaddr; - //std.debug.warn(" (writing new offset table entry)\n", .{}); + std.debug.warn(" (writing new offset table entry)\n", .{}); self.offset_table.items[decl.link.offset_table_index] = vaddr; try self.writeOffsetTableEntry(decl.link.offset_table_index); } @@ -1049,7 +1049,7 @@ pub const ElfFile = struct { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); - //std.debug.warn("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); + std.debug.warn("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); errdefer self.freeTextBlock(&decl.link); local_sym.* = .{ diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index aa8c0000953d9c7f856f733ba98daeb365418d0c..d8bc40a4f87ffb9ded29840c9d07f012ac0065c7 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -54,6 +54,7 @@ pub const Type = extern union { .@"undefined" => return .Undefined, .fn_noreturn_no_args => return .Fn, + .fn_void_no_args => return .Fn, .fn_naked_noreturn_no_args => return .Fn, .fn_ccc_void_no_args => return .Fn, @@ -163,6 +164,77 @@ pub const Type = extern union { } } + pub fn copy(self: Type, allocator: *Allocator) error{OutOfMemory}!Type { + if (self.tag_if_small_enough < Tag.no_payload_count) { + return Type{ .tag_if_small_enough = self.tag_if_small_enough }; + } else switch (self.ptr_otherwise.tag) { + .u8, + .i8, + .isize, + .usize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .c_longdouble, + .c_void, + .f16, + .f32, + .f64, + .f128, + .bool, + .void, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .noreturn, + .@"null", + .@"undefined", + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + .single_const_pointer_to_comptime_int, + .const_slice_u8, + => unreachable, + + .array_u8_sentinel_0 => return self.copyPayloadShallow(allocator, Payload.Array_u8_Sentinel0), + .array => { + const payload = @fieldParentPtr(Payload.Array, "base", self.ptr_otherwise); + const new_payload = try allocator.create(Payload.Array); + new_payload.* = .{ + .base = payload.base, + .len = payload.len, + .elem_type = try payload.elem_type.copy(allocator), + }; + return Type{ .ptr_otherwise = &new_payload.base }; + }, + .single_const_pointer => { + const payload = @fieldParentPtr(Payload.SingleConstPointer, "base", self.ptr_otherwise); + const new_payload = try allocator.create(Payload.SingleConstPointer); + new_payload.* = .{ + .base = payload.base, + .pointee_type = try payload.pointee_type.copy(allocator), + }; + return Type{ .ptr_otherwise = &new_payload.base }; + }, + .int_signed => return self.copyPayloadShallow(allocator, Payload.IntSigned), + .int_unsigned => return self.copyPayloadShallow(allocator, Payload.IntUnsigned), + } + } + + fn copyPayloadShallow(self: Type, allocator: *Allocator, comptime T: type) error{OutOfMemory}!Type { + const payload = @fieldParentPtr(T, "base", self.ptr_otherwise); + const new_payload = try allocator.create(T); + new_payload.* = payload.*; + return Type{ .ptr_otherwise = &new_payload.base }; + } + pub fn format( self: Type, comptime fmt: []const u8, @@ -206,6 +278,7 @@ pub const Type = extern union { .const_slice_u8 => return out_stream.writeAll("[]const u8"), .fn_noreturn_no_args => return out_stream.writeAll("fn() noreturn"), + .fn_void_no_args => return out_stream.writeAll("fn() void"), .fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), .fn_ccc_void_no_args => return out_stream.writeAll("fn() callconv(.C) void"), .single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"), @@ -269,6 +342,7 @@ pub const Type = extern union { .@"null" => return Value.initTag(.null_type), .@"undefined" => return Value.initTag(.undefined_type), .fn_noreturn_no_args => return Value.initTag(.fn_noreturn_no_args_type), + .fn_void_no_args => return Value.initTag(.fn_void_no_args_type), .fn_naked_noreturn_no_args => return Value.initTag(.fn_naked_noreturn_no_args_type), .fn_ccc_void_no_args => return Value.initTag(.fn_ccc_void_no_args_type), .single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type), @@ -303,6 +377,7 @@ pub const Type = extern union { .bool, .anyerror, .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .single_const_pointer_to_comptime_int, @@ -333,6 +408,7 @@ pub const Type = extern union { .i8, .bool, .fn_noreturn_no_args, // represents machine code; not a pointer + .fn_void_no_args, // represents machine code; not a pointer .fn_naked_noreturn_no_args, // represents machine code; not a pointer .fn_ccc_void_no_args, // represents machine code; not a pointer .array_u8_sentinel_0, @@ -420,6 +496,7 @@ pub const Type = extern union { .array_u8_sentinel_0, .const_slice_u8, .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .int_unsigned, @@ -466,6 +543,7 @@ pub const Type = extern union { .single_const_pointer, .single_const_pointer_to_comptime_int, .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .int_unsigned, @@ -509,6 +587,7 @@ pub const Type = extern union { .array, .array_u8_sentinel_0, .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .int_unsigned, @@ -553,6 +632,7 @@ pub const Type = extern union { .@"null", .@"undefined", .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .int_unsigned, @@ -597,6 +677,7 @@ pub const Type = extern union { .@"null", .@"undefined", .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .single_const_pointer, @@ -642,6 +723,7 @@ pub const Type = extern union { .@"null", .@"undefined", .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .single_const_pointer, @@ -675,6 +757,7 @@ pub const Type = extern union { .@"null", .@"undefined", .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .array, @@ -721,6 +804,7 @@ pub const Type = extern union { .@"null", .@"undefined", .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .array, @@ -777,6 +861,7 @@ pub const Type = extern union { pub fn fnParamLen(self: Type) usize { return switch (self.tag()) { .fn_noreturn_no_args => 0, + .fn_void_no_args => 0, .fn_naked_noreturn_no_args => 0, .fn_ccc_void_no_args => 0, @@ -823,6 +908,7 @@ pub const Type = extern union { pub fn fnParamTypes(self: Type, types: []Type) void { switch (self.tag()) { .fn_noreturn_no_args => return, + .fn_void_no_args => return, .fn_naked_noreturn_no_args => return, .fn_ccc_void_no_args => return, @@ -869,7 +955,10 @@ pub const Type = extern union { return switch (self.tag()) { .fn_noreturn_no_args => Type.initTag(.noreturn), .fn_naked_noreturn_no_args => Type.initTag(.noreturn), - .fn_ccc_void_no_args => Type.initTag(.void), + + .fn_void_no_args, + .fn_ccc_void_no_args, + => Type.initTag(.void), .f16, .f32, @@ -913,6 +1002,7 @@ pub const Type = extern union { pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention { return switch (self.tag()) { .fn_noreturn_no_args => .Unspecified, + .fn_void_no_args => .Unspecified, .fn_naked_noreturn_no_args => .Naked, .fn_ccc_void_no_args => .C, @@ -958,6 +1048,7 @@ pub const Type = extern union { pub fn fnIsVarArgs(self: Type) bool { return switch (self.tag()) { .fn_noreturn_no_args => false, + .fn_void_no_args => false, .fn_naked_noreturn_no_args => false, .fn_ccc_void_no_args => false, @@ -1033,6 +1124,7 @@ pub const Type = extern union { .@"null", .@"undefined", .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .array, @@ -1070,6 +1162,7 @@ pub const Type = extern union { .type, .anyerror, .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .single_const_pointer_to_comptime_int, @@ -1126,6 +1219,7 @@ pub const Type = extern union { .type, .anyerror, .fn_noreturn_no_args, + .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, .single_const_pointer_to_comptime_int, @@ -1180,6 +1274,7 @@ pub const Type = extern union { @"null", @"undefined", fn_noreturn_no_args, + fn_void_no_args, fn_naked_noreturn_no_args, fn_ccc_void_no_args, single_const_pointer_to_comptime_int, diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 7caacc7960bb13ee22236ab429e0a72b8442897c..fc5854c40fb80060915c65acc60ac0bf787c68b7 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -49,6 +49,7 @@ pub const Value = extern union { null_type, undefined_type, fn_noreturn_no_args_type, + fn_void_no_args_type, fn_naked_noreturn_no_args_type, fn_ccc_void_no_args_type, single_const_pointer_to_comptime_int_type, @@ -107,6 +108,109 @@ pub const Value = extern union { return @fieldParentPtr(T, "base", self.ptr_otherwise); } + pub fn copy(self: Value, allocator: *Allocator) error{OutOfMemory}!Value { + if (self.tag_if_small_enough < Tag.no_payload_count) { + return Value{ .tag_if_small_enough = self.tag_if_small_enough }; + } else switch (self.ptr_otherwise.tag) { + .u8_type, + .i8_type, + .isize_type, + .usize_type, + .c_short_type, + .c_ushort_type, + .c_int_type, + .c_uint_type, + .c_long_type, + .c_ulong_type, + .c_longlong_type, + .c_ulonglong_type, + .c_longdouble_type, + .f16_type, + .f32_type, + .f64_type, + .f128_type, + .c_void_type, + .bool_type, + .void_type, + .type_type, + .anyerror_type, + .comptime_int_type, + .comptime_float_type, + .noreturn_type, + .null_type, + .undefined_type, + .fn_noreturn_no_args_type, + .fn_void_no_args_type, + .fn_naked_noreturn_no_args_type, + .fn_ccc_void_no_args_type, + .single_const_pointer_to_comptime_int_type, + .const_slice_u8_type, + .undef, + .zero, + .the_one_possible_value, + .null_value, + .bool_true, + .bool_false, + => unreachable, + + .ty => { + const payload = @fieldParentPtr(Payload.Ty, "base", self.ptr_otherwise); + const new_payload = try allocator.create(Payload.Ty); + new_payload.* = .{ + .base = payload.base, + .ty = try payload.ty.copy(allocator), + }; + return Value{ .ptr_otherwise = &new_payload.base }; + }, + .int_u64 => return self.copyPayloadShallow(allocator, Payload.Int_u64), + .int_i64 => return self.copyPayloadShallow(allocator, Payload.Int_i64), + .int_big_positive => { + @panic("TODO implement copying of big ints"); + }, + .int_big_negative => { + @panic("TODO implement copying of big ints"); + }, + .function => return self.copyPayloadShallow(allocator, Payload.Function), + .ref_val => { + const payload = @fieldParentPtr(Payload.RefVal, "base", self.ptr_otherwise); + const new_payload = try allocator.create(Payload.RefVal); + new_payload.* = .{ + .base = payload.base, + .val = try payload.val.copy(allocator), + }; + return Value{ .ptr_otherwise = &new_payload.base }; + }, + .decl_ref => return self.copyPayloadShallow(allocator, Payload.DeclRef), + .elem_ptr => { + const payload = @fieldParentPtr(Payload.ElemPtr, "base", self.ptr_otherwise); + const new_payload = try allocator.create(Payload.ElemPtr); + new_payload.* = .{ + .base = payload.base, + .array_ptr = try payload.array_ptr.copy(allocator), + .index = payload.index, + }; + return Value{ .ptr_otherwise = &new_payload.base }; + }, + .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), + .repeated => { + const payload = @fieldParentPtr(Payload.Repeated, "base", self.ptr_otherwise); + const new_payload = try allocator.create(Payload.Repeated); + new_payload.* = .{ + .base = payload.base, + .val = try payload.val.copy(allocator), + }; + return Value{ .ptr_otherwise = &new_payload.base }; + }, + } + } + + fn copyPayloadShallow(self: Value, allocator: *Allocator, comptime T: type) error{OutOfMemory}!Value { + const payload = @fieldParentPtr(T, "base", self.ptr_otherwise); + const new_payload = try allocator.create(T); + new_payload.* = payload.*; + return Value{ .ptr_otherwise = &new_payload.base }; + } + pub fn format( self: Value, comptime fmt: []const u8, @@ -144,6 +248,7 @@ pub const Value = extern union { .null_type => return out_stream.writeAll("@TypeOf(null)"), .undefined_type => return out_stream.writeAll("@TypeOf(undefined)"), .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"), + .fn_void_no_args_type => return out_stream.writeAll("fn() void"), .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"), .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"), @@ -229,6 +334,7 @@ pub const Value = extern union { .null_type => Type.initTag(.@"null"), .undefined_type => Type.initTag(.@"undefined"), .fn_noreturn_no_args_type => Type.initTag(.fn_noreturn_no_args), + .fn_void_no_args_type => Type.initTag(.fn_void_no_args), .fn_naked_noreturn_no_args_type => Type.initTag(.fn_naked_noreturn_no_args), .fn_ccc_void_no_args_type => Type.initTag(.fn_ccc_void_no_args), .single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int), @@ -286,6 +392,7 @@ pub const Value = extern union { .null_type, .undefined_type, .fn_noreturn_no_args_type, + .fn_void_no_args_type, .fn_naked_noreturn_no_args_type, .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, @@ -345,6 +452,7 @@ pub const Value = extern union { .null_type, .undefined_type, .fn_noreturn_no_args_type, + .fn_void_no_args_type, .fn_naked_noreturn_no_args_type, .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, @@ -405,6 +513,7 @@ pub const Value = extern union { .null_type, .undefined_type, .fn_noreturn_no_args_type, + .fn_void_no_args_type, .fn_naked_noreturn_no_args_type, .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, @@ -470,6 +579,7 @@ pub const Value = extern union { .null_type, .undefined_type, .fn_noreturn_no_args_type, + .fn_void_no_args_type, .fn_naked_noreturn_no_args_type, .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, @@ -564,6 +674,7 @@ pub const Value = extern union { .null_type, .undefined_type, .fn_noreturn_no_args_type, + .fn_void_no_args_type, .fn_naked_noreturn_no_args_type, .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, @@ -620,6 +731,7 @@ pub const Value = extern union { .null_type, .undefined_type, .fn_noreturn_no_args_type, + .fn_void_no_args_type, .fn_naked_noreturn_no_args_type, .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, @@ -721,6 +833,7 @@ pub const Value = extern union { .null_type, .undefined_type, .fn_noreturn_no_args_type, + .fn_void_no_args_type, .fn_naked_noreturn_no_args_type, .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, @@ -783,6 +896,7 @@ pub const Value = extern union { .null_type, .undefined_type, .fn_noreturn_no_args_type, + .fn_void_no_args_type, .fn_naked_noreturn_no_args_type, .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, @@ -862,6 +976,7 @@ pub const Value = extern union { .null_type, .undefined_type, .fn_noreturn_no_args_type, + .fn_void_no_args_type, .fn_naked_noreturn_no_args_type, .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, @@ -929,11 +1044,6 @@ pub const Value = extern union { len: u64, }; - pub const SingleConstPtrType = struct { - base: Payload = Payload{ .tag = .single_const_ptr_type }, - elem_type: *Type, - }; - /// Represents a pointer to another immutable value. pub const RefVal = struct { base: Payload = Payload{ .tag = .ref_val }, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index c1b547ce9962a11d338e58a35f8490af5b90aab8..90793c51f96f88b7522afa2c82299d3464bc40c9 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -25,6 +25,9 @@ pub const Inst = struct { /// Hash of slice into the source of the part after the = and before the next instruction. contents_hash: std.zig.SrcHash = undefined, + /// Pre-allocated field for mapping ZIR text instructions to post-analysis instructions. + analyzed_inst: *ir.Inst = undefined, + /// These names are used directly as the instruction names in the text format. pub const Tag = enum { breakpoint, @@ -37,6 +40,8 @@ pub const Inst = struct { /// The syntax `@foo` is equivalent to `declval("foo")`. /// declval is equivalent to declref followed by deref. declval, + /// Same as declval but the parameter is a `*Module.Decl` rather than a name. + declval_in_module, str, int, ptrtoint, @@ -46,6 +51,7 @@ pub const Inst = struct { @"asm", @"unreachable", @"return", + returnvoid, @"fn", fntype, @"export", @@ -67,6 +73,7 @@ pub const Inst = struct { .call => Call, .declref => DeclRef, .declval => DeclVal, + .declval_in_module => DeclValInModule, .compileerror => CompileError, .@"const" => Const, .str => Str, @@ -78,6 +85,7 @@ pub const Inst = struct { .@"asm" => Asm, .@"unreachable" => Unreachable, .@"return" => Return, + .returnvoid => ReturnVoid, .@"fn" => Fn, .@"export" => Export, .primitive => Primitive, @@ -142,6 +150,16 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const DeclValInModule = struct { + pub const base_tag = Tag.declval_in_module; + base: Inst, + + positionals: struct { + decl: *IrModule.Decl, + }, + kw_args: struct {}, + }; + pub const CompileError = struct { pub const base_tag = Tag.compileerror; base: Inst, @@ -253,6 +271,16 @@ pub const Inst = struct { pub const base_tag = Tag.@"return"; base: Inst, + positionals: struct { + operand: *Inst, + }, + kw_args: struct {}, + }; + + pub const ReturnVoid = struct { + pub const base_tag = Tag.returnvoid; + base: Inst, + positionals: struct {}, kw_args: struct {}, }; @@ -492,11 +520,19 @@ pub const Module = struct { const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize }); + const DeclAndIndex = struct { + decl: *Inst, + index: usize, + }; + /// TODO Look into making a table to speed this up. - pub fn findDecl(self: Module, name: []const u8) ?*Inst { - for (self.decls) |decl| { + pub fn findDecl(self: Module, name: []const u8) ?DeclAndIndex { + for (self.decls) |decl, i| { if (mem.eql(u8, decl.name, name)) { - return decl; + return DeclAndIndex{ + .decl = decl, + .index = i, + }; } } return null; @@ -540,6 +576,7 @@ pub const Module = struct { .call => return self.writeInstToStreamGeneric(stream, .call, decl, inst_table), .declref => return self.writeInstToStreamGeneric(stream, .declref, decl, inst_table), .declval => return self.writeInstToStreamGeneric(stream, .declval, decl, inst_table), + .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, decl, inst_table), .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, decl, inst_table), .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", decl, inst_table), .str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table), @@ -551,6 +588,7 @@ pub const Module = struct { .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", decl, inst_table), .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", decl, inst_table), .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", decl, inst_table), + .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, decl, inst_table), .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table), .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table), .ref => return self.writeInstToStreamGeneric(stream, .ref, decl, inst_table), @@ -636,6 +674,7 @@ pub const Module = struct { []u8, []const u8 => return std.zig.renderStringLiteral(param, stream), BigIntConst => return stream.print("{}", .{param}), TypedValue => unreachable, // this is a special case + *IrModule.Decl => unreachable, // this is a special case else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } @@ -649,6 +688,8 @@ pub const Module = struct { } } else if (inst.cast(Inst.DeclVal)) |decl_val| { try stream.print("@{}", .{decl_val.positionals.name}); + } else if (inst.cast(Inst.DeclValInModule)) |decl_val| { + try stream.print("@{}", .{decl_val.positionals.decl.name}); } else { //try stream.print("?", .{}); unreachable; @@ -996,6 +1037,7 @@ const Parser = struct { []u8, []const u8 => return self.parseStringLiteral(), BigIntConst => return self.parseIntegerLiteral(), TypedValue => return self.fail("'const' is a special instruction; not legal in ZIR text", .{}), + *IrModule.Decl => return self.fail("'declval_in_module' is a special instruction; not legal in ZIR text", .{}), else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), } return self.fail("TODO parse parameter {}", .{@typeName(T)}); @@ -1105,7 +1147,7 @@ const EmitZIR = struct { } std.sort.sort(*IrModule.Decl, src_decls.items, {}, (struct { fn lessThan(context: void, a: *IrModule.Decl, b: *IrModule.Decl) bool { - return a.src < b.src; + return a.src_index < b.src_index; } }).lessThan); @@ -1113,7 +1155,7 @@ const EmitZIR = struct { for (src_decls.items) |ir_decl| { if (self.old_module.export_owners.getValue(ir_decl)) |exports| { for (exports) |module_export| { - const declval = try self.emitDeclVal(ir_decl.src, mem.spanZ(module_export.exported_decl.name)); + const declval = try self.emitDeclVal(ir_decl.src(), mem.spanZ(module_export.exported_decl.name)); const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name); const export_inst = try self.arena.allocator.create(Inst.Export); export_inst.* = .{ @@ -1131,7 +1173,7 @@ const EmitZIR = struct { try self.decls.append(self.allocator, &export_inst.base); } } else { - const new_decl = try self.emitTypedValue(ir_decl.src, ir_decl.typed_value.most_recent.typed_value); + const new_decl = try self.emitTypedValue(ir_decl.src(), ir_decl.typed_value.most_recent.typed_value); new_decl.name = try self.arena.allocator.dupe(u8, mem.spanZ(ir_decl.name)); } } @@ -1301,7 +1343,7 @@ const EmitZIR = struct { }, } - const fn_type = try self.emitType(src, module_fn.fn_type); + const fn_type = try self.emitType(src, typed_value.ty); const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); mem.copy(*Inst, arena_instrs, instructions.items); @@ -1399,7 +1441,23 @@ const EmitZIR = struct { break :blk &new_inst.base; }, .unreach => try self.emitTrivial(inst.src, Inst.Unreachable), - .ret => try self.emitTrivial(inst.src, Inst.Return), + .ret => blk: { + const old_inst = inst.cast(ir.Inst.Ret).?; + const new_inst = try self.arena.allocator.create(Inst.Return); + new_inst.* = .{ + .base = .{ + .name = try self.autoName(), + .src = inst.src, + .tag = Inst.Return.base_tag, + }, + .positionals = .{ + .operand = try self.resolveInst(inst_table, old_inst.args.operand), + }, + .kw_args = .{}, + }; + break :blk &new_inst.base; + }, + .retvoid => try self.emitTrivial(inst.src, Inst.ReturnVoid), .constant => unreachable, // excluded from function bodies .assembly => blk: { const old_inst = inst.cast(ir.Inst.Assembly).?; diff --git a/src/codegen.cpp b/src/codegen.cpp index e20d6d60f5f17779f2a13880a4bd04056030afb7..75d126eaf66ec8198fd53b2763b73f2c8fd8e3dc 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -7473,6 +7473,12 @@ static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *n continue; } ZigValue *field_val = const_val->data.x_struct.fields[i]; + if (field_val == nullptr) { + add_node_error(g, type_struct_field->decl_node, + buf_sprintf("compiler bug: generating const value for struct field '%s'", + buf_ptr(type_struct_field->name))); + codegen_report_errors_and_exit(g); + } ZigType *field_type = field_val->type; assert(field_type != nullptr); if ((err = ensure_const_val_repr(nullptr, g, nullptr, field_val, field_type))) { -- 2.54.0 From 02f688d710312a43a56a3a24b769778a5aafc5da Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Jun 2020 04:38:15 -0400 Subject: [PATCH 052/295] remove std.debug.warn debugging logs --- src-self-hosted/Module.zig | 4 ++-- src-self-hosted/link.zig | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 1dfca43c8d097d16ebbdc3638c8ccdffab3c2104..f6c80c3d89335d22660b9c94b2fc61e59f8ce16f 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1743,13 +1743,13 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { const fn_zir = func.analysis.queued; defer fn_zir.arena.promote(self.allocator).deinit(); func.analysis = .{ .in_progress = {} }; - std.debug.warn("set {} to in_progress\n", .{decl.name}); + //std.debug.warn("set {} to in_progress\n", .{decl.name}); try self.analyzeBody(&inner_block.base, fn_zir.body); const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items); func.analysis = .{ .success = .{ .instructions = instructions } }; - std.debug.warn("set {} to success\n", .{decl.name}); + //std.debug.warn("set {} to success\n", .{decl.name}); } fn reAnalyzeDecl(self: *Module, decl: *Decl, old_inst: *zir.Inst) InnerError!void { diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index fd38bee8631998638f836996b24b908206284e61..012f543a7e073c6f9e74fe451f2d53eef0d57ab4 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -956,10 +956,10 @@ pub const ElfFile = struct { try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); if (self.local_symbol_free_list.popOrNull()) |i| { - std.debug.warn("reusing symbol index {} for {}\n", .{i, decl.name}); + //std.debug.warn("reusing symbol index {} for {}\n", .{i, decl.name}); decl.link.local_sym_index = i; } else { - std.debug.warn("allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); + //std.debug.warn("allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -1027,11 +1027,11 @@ pub const ElfFile = struct { !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); if (need_realloc) { const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); - std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); + //std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); if (vaddr != local_sym.st_value) { local_sym.st_value = vaddr; - std.debug.warn(" (writing new offset table entry)\n", .{}); + //std.debug.warn(" (writing new offset table entry)\n", .{}); self.offset_table.items[decl.link.offset_table_index] = vaddr; try self.writeOffsetTableEntry(decl.link.offset_table_index); } @@ -1049,7 +1049,7 @@ pub const ElfFile = struct { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); - std.debug.warn("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); + //std.debug.warn("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); errdefer self.freeTextBlock(&decl.link); local_sym.* = .{ -- 2.54.0 From 46b57748a52c32b973903166d40c7ebee53d88c5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Jun 2020 06:07:53 -0400 Subject: [PATCH 053/295] stage1: stop emitting memset to undefined when safety is off --- src/codegen.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 75d126eaf66ec8198fd53b2763b73f2c8fd8e3dc..6c3fdddfb15cdf92dd8c6f43ff8bd14f1b903753 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -5583,8 +5583,12 @@ static LLVMValueRef ir_render_memset(CodeGen *g, IrExecutableGen *executable, Ir bool val_is_undef = value_is_all_undef(g, instruction->byte->value); LLVMValueRef fill_char; - if (val_is_undef && ir_want_runtime_safety_scope(g, instruction->base.base.scope)) { - fill_char = LLVMConstInt(LLVMInt8Type(), 0xaa, false); + if (val_is_undef) { + if (ir_want_runtime_safety_scope(g, instruction->base.base.scope)) { + fill_char = LLVMConstInt(LLVMInt8Type(), 0xaa, false); + } else { + return nullptr; + } } else { fill_char = ir_llvm_value(g, instruction->byte); } -- 2.54.0 From 81f766eecd98287463888a725c3cc053a132c66e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Jun 2020 06:08:15 -0400 Subject: [PATCH 054/295] self-hosted parser: make a function pointer comptime --- lib/std/zig/parse.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 2a3ff9d9de8f379be23e4ae62243ecf9a8cedc9e..7a59ca0c386e75441507d061ae683102d2e57410 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -3223,7 +3223,7 @@ const Parser = struct { } /// Op* Child - fn parsePrefixOpExpr(p: *Parser, opParseFn: NodeParseFn, childParseFn: NodeParseFn) Error!?*Node { + fn parsePrefixOpExpr(p: *Parser, comptime opParseFn: NodeParseFn, comptime childParseFn: NodeParseFn) Error!?*Node { if (try opParseFn(p)) |first_op| { var rightmost_op = first_op; while (true) { -- 2.54.0 From 7e443022609e31ff2636d2c43c32f3c5570793d1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Jun 2020 06:08:37 -0400 Subject: [PATCH 055/295] stage2: explicit hash and equality function for the DeclTable --- src-self-hosted/Module.zig | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index f6c80c3d89335d22660b9c94b2fc61e59f8ce16f..947495f0ae3e2e21abfc215866f012136362ca95 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -37,7 +37,7 @@ decl_exports: std.AutoHashMap(*Decl, []*Export), /// This table owns the Export memory. export_owners: std.AutoHashMap(*Decl, []*Export), /// Maps fully qualified namespaced names to the Decl struct for them. -decl_table: std.AutoHashMap(Scope.NameHash, *Decl), +decl_table: DeclTable, optimize_mode: std.builtin.Mode, link_error_flags: link.ElfFile.ErrorFlags = .{}, @@ -66,6 +66,8 @@ generation: u32 = 0, /// contains Decls that need to be deleted if they end up having no references to them. deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, +const DeclTable = std.HashMap(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql); + const WorkItem = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Decl, @@ -417,6 +419,14 @@ pub const Scope = struct { } } + fn name_hash_hash(x: NameHash) u32 { + return @truncate(u32, @bitCast(u128, x)); + } + + fn name_hash_eql(a: NameHash, b: NameHash) bool { + return @bitCast(u128, a) == @bitCast(u128, b); + } + pub const Tag = enum { /// .zir source code. zir_module, @@ -711,7 +721,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .bin_file_path = options.bin_file_path, .bin_file = bin_file, .optimize_mode = options.optimize_mode, - .decl_table = std.AutoHashMap(Scope.NameHash, *Decl).init(gpa), + .decl_table = DeclTable.init(gpa), .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa), .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa), .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa), -- 2.54.0 From 355319fb674073234ea2f356a310577fb982f72b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Jun 2020 18:17:26 -0400 Subject: [PATCH 056/295] zig cc: add missing cxxabi include path --- src/codegen.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/codegen.cpp b/src/codegen.cpp index 6c3fdddfb15cdf92dd8c6f43ff8bd14f1b903753..a0e49ccf9e686676baac5cf243418f54955d37cb 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -9475,9 +9475,15 @@ void add_cc_args(CodeGen *g, ZigList &args, const char *out_dep_pa const char *libcxx_include_path = buf_ptr(buf_sprintf("%s" OS_SEP "libcxx" OS_SEP "include", buf_ptr(g->zig_lib_dir))); + const char *libcxxabi_include_path = buf_ptr(buf_sprintf("%s" OS_SEP "libcxxabi" OS_SEP "include", + buf_ptr(g->zig_lib_dir))); + args.append("-isystem"); args.append(libcxx_include_path); + args.append("-isystem"); + args.append(libcxxabi_include_path); + if (target_abi_is_musl(g->zig_target->abi)) { args.append("-D_LIBCPP_HAS_MUSL_LIBC"); } -- 2.54.0 From caaa26c9f0db92c463a826f15c8392162be2e037 Mon Sep 17 00:00:00 2001 From: Vexu Date: Wed, 17 Jun 2020 20:24:37 +0300 Subject: [PATCH 057/295] reference emit_raw in std lib tests --- lib/std/build.zig | 7 +++++++ lib/std/build/emit_raw.zig | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/lib/std/build.zig b/lib/std/build.zig index d98ef71a59c596799a7e743dc629b1a617589878..a4d922f2adc62a22c528cfbed2ec71d9768a47d4 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -2558,3 +2558,10 @@ pub const InstalledFile = struct { dir: InstallDir, path: []const u8, }; + +test "" { + // The only purpose of this test is to get all these untested functions + // to be referenced to avoid regression so it is okay to skip some targets. + if (comptime std.Target.current.cpu.arch.ptrBitWidth() == 64) + std.meta.refAllDecls(@This()); +} diff --git a/lib/std/build/emit_raw.zig b/lib/std/build/emit_raw.zig index 8fd27d6cfc98c7feb1e37e7537a5f8bd0d9b77c6..746b0ac91b9417665f93f0c64783244badd9b476 100644 --- a/lib/std/build/emit_raw.zig +++ b/lib/std/build/emit_raw.zig @@ -215,3 +215,7 @@ pub const InstallRawStep = struct { try emitRaw(builder.allocator, full_src_path, full_dest_path); } }; + +test "" { + std.meta.refAllDecls(InstallRawStep); +} -- 2.54.0 From bd17a373cc20c919be9fa279a4420033e959ca92 Mon Sep 17 00:00:00 2001 From: Michael Rees Date: Wed, 17 Jun 2020 06:04:08 -0500 Subject: [PATCH 058/295] Add std.unicode.Utf8Iterator.peek --- lib/std/unicode.zig | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/lib/std/unicode.zig b/lib/std/unicode.zig index df2e16a4bf73fcb67a09af0178d52a8f51d461c3..2c88d2ba0c936853d5c3a96b893b8d5b8fc60dfc 100644 --- a/lib/std/unicode.zig +++ b/lib/std/unicode.zig @@ -235,6 +235,22 @@ pub const Utf8Iterator = struct { else => unreachable, } } + + /// Look ahead at the next n codepoints without advancing the iterator. + /// If fewer than n codepoints are available, then return the remainder of the string. + pub fn peek(it: *Utf8Iterator, n: usize) []const u8 { + const original_i = it.i; + defer it.i = original_i; + + var end_ix = original_i; + var found: usize = 0; + while (found < n) : (found += 1) { + const next_codepoint = it.nextCodepointSlice() orelse return it.bytes[original_i..]; + end_ix += next_codepoint.len; + } + + return it.bytes[original_i..end_ix]; + } }; pub const Utf16LeIterator = struct { @@ -451,6 +467,31 @@ fn testMiscInvalidUtf8() void { testValid("\xee\x80\x80", 0xe000); } +test "utf8 iterator peeking" { + comptime testUtf8Peeking(); + testUtf8Peeking(); +} + +fn testUtf8Peeking() void { + const s = Utf8View.initComptime("noël"); + var it = s.iterator(); + + testing.expect(std.mem.eql(u8, "n", it.nextCodepointSlice().?)); + + testing.expect(std.mem.eql(u8, "o", it.peek(1))); + testing.expect(std.mem.eql(u8, "oë", it.peek(2))); + testing.expect(std.mem.eql(u8, "oël", it.peek(3))); + testing.expect(std.mem.eql(u8, "oël", it.peek(4))); + testing.expect(std.mem.eql(u8, "oël", it.peek(10))); + + testing.expect(std.mem.eql(u8, "o", it.nextCodepointSlice().?)); + testing.expect(std.mem.eql(u8, "ë", it.nextCodepointSlice().?)); + testing.expect(std.mem.eql(u8, "l", it.nextCodepointSlice().?)); + testing.expect(it.nextCodepointSlice() == null); + + testing.expect(std.mem.eql(u8, &[_]u8{}, it.peek(1))); +} + fn testError(bytes: []const u8, expected_err: anyerror) void { testing.expectError(expected_err, testDecode(bytes)); } -- 2.54.0 From b30642a86ae59f9cfe73c8a9ed6e79c3cb5aaa36 Mon Sep 17 00:00:00 2001 From: Cassidy Dingenskirchen Date: Tue, 16 Jun 2020 19:07:55 +0200 Subject: [PATCH 059/295] Fix zig fmt clobbering a file's mode --- src-self-hosted/main.zig | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index eda9dcfc31b1fb5f20138ce4c76892fed1232f10..4db98c60dc88d1e34e314bd26d0d87b0c2c03074 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -684,7 +684,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { if (fmt.seen.exists(real_path)) return; try fmt.seen.put(real_path); - const source_code = fs.cwd().readFileAlloc(fmt.gpa, real_path, max_src_size) catch |err| switch (err) { + const source_file = fs.cwd().openFile(real_path, .{}) catch |err| switch (err) { error.IsDir, error.AccessDenied => { var dir = try fs.cwd().openDir(file_path, .{ .iterate = true }); defer dir.close(); @@ -705,6 +705,13 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { return; }, }; + defer source_file.close(); + + const source_code = source_file.reader().readAllAlloc(fmt.gpa, max_src_size) catch |err| { + std.debug.warn("unable to read '{}': {}\n", .{ file_path, err }); + fmt.any_error = true; + return; + }; defer fmt.gpa.free(source_code); const tree = std.zig.parse(fmt.gpa, source_code) catch |err| { @@ -729,7 +736,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { fmt.any_error = true; } } else { - const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{}); + const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{ .mode = try source_file.mode() }); defer baf.destroy(); const anything_changed = try std.zig.render(fmt.gpa, baf.stream(), tree); -- 2.54.0 From 8b49487c3345d6e203d8c72695e639b42a93c119 Mon Sep 17 00:00:00 2001 From: Cassidy Dingenskirchen Date: Thu, 18 Jun 2020 18:27:42 +0200 Subject: [PATCH 060/295] Fix fs.File.mode() not returning mode_t on windows --- lib/std/fs/file.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index d950a1cfa4cfde4c6ca8d08d141bf8548fab3061..ec4059b9c7b5b467e9486ec9fd6cdf26c8f7254d 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -209,7 +209,7 @@ pub const File = struct { /// TODO: integrate with async I/O pub fn mode(self: File) ModeError!Mode { if (builtin.os.tag == .windows) { - return {}; + return 0; } return (try self.stat()).mode; } -- 2.54.0 From f7bcc8e04087e2308582d8b6f068e2e71b65bef9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Jun 2020 21:08:30 -0400 Subject: [PATCH 061/295] rework zig fmt to only make one allocation taking advantage of the fstat size --- lib/std/fs.zig | 9 ++------- lib/std/fs/file.zig | 27 +++++++++++++++++++++++++++ src-self-hosted/main.zig | 10 ++++++++-- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 262aa4872d3cec16c725531edf340f0e3ded3c73..3ff4819ac5cb29be7c973d5d8e1d7166dd7957e9 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1229,14 +1229,9 @@ pub const Dir = struct { var file = try self.openFile(file_path, .{}); defer file.close(); - const size = math.cast(usize, try file.getEndPos()) catch math.maxInt(usize); - if (size > max_bytes) return error.FileTooBig; + const stat_size = try file.getEndPos(); - const buf = try allocator.allocWithOptions(u8, size, alignment, optional_sentinel); - errdefer allocator.free(buf); - - try file.inStream().readNoEof(buf); - return buf; + return file.readAllAllocOptions(allocator, stat_size, max_bytes, alignment, optional_sentinel); } pub const DeleteTreeError = error{ diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index ec4059b9c7b5b467e9486ec9fd6cdf26c8f7254d..0a3c1b5ab73500838b0b7435b4a0f17f94776bbb 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -306,6 +306,33 @@ pub const File = struct { try os.futimens(self.handle, ×); } + /// On success, caller owns returned buffer. + /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. + pub fn readAllAlloc(self: File, allocator: *mem.Allocator, stat_size: u64, max_bytes: usize) ![]u8 { + return self.readAllAllocOptions(allocator, stat_size, max_bytes, @alignOf(u8), null); + } + + /// On success, caller owns returned buffer. + /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. + /// Allows specifying alignment and a sentinel value. + pub fn readAllAllocOptions( + self: File, + allocator: *mem.Allocator, + stat_size: u64, + max_bytes: usize, + comptime alignment: u29, + comptime optional_sentinel: ?u8, + ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) { + const size = math.cast(usize, stat_size) catch math.maxInt(usize); + if (size > max_bytes) return error.FileTooBig; + + const buf = try allocator.allocWithOptions(u8, size, alignment, optional_sentinel); + errdefer allocator.free(buf); + + try self.inStream().readNoEof(buf); + return buf; + } + pub const ReadError = os.ReadError; pub const PReadError = os.PReadError; diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 4db98c60dc88d1e34e314bd26d0d87b0c2c03074..6c13f8ab004190843d65b0baae5238839deb25b0 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -707,7 +707,13 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { }; defer source_file.close(); - const source_code = source_file.reader().readAllAlloc(fmt.gpa, max_src_size) catch |err| { + const stat = source_file.stat() catch |err| { + std.debug.warn("unable to stat '{}': {}\n", .{ file_path, err }); + fmt.any_error = true; + return; + }; + + const source_code = source_file.readAllAlloc(fmt.gpa, stat.size, max_src_size) catch |err| { std.debug.warn("unable to read '{}': {}\n", .{ file_path, err }); fmt.any_error = true; return; @@ -736,7 +742,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { fmt.any_error = true; } } else { - const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{ .mode = try source_file.mode() }); + const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{ .mode = stat.mode }); defer baf.destroy(); const anything_changed = try std.zig.render(fmt.gpa, baf.stream(), tree); -- 2.54.0 From c9a0ec25e0baae7f128a8f7efe4d308af7fad753 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Jun 2020 19:03:41 -0400 Subject: [PATCH 062/295] self-hosted: add Tracy integration This tool helps give an intuitive picture of performance. This will help us understand where to improve the code. --- build.zig | 13 +++++++++++ lib/std/build.zig | 5 +++-- src-self-hosted/Module.zig | 16 +++++++++++++ src-self-hosted/codegen.zig | 4 ++++ src-self-hosted/tracy.zig | 45 +++++++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 src-self-hosted/tracy.zig diff --git a/build.zig b/build.zig index 4d71b0fb36e6fcd743e7c1acf21ae5995a00f4e0..e2af4ba8ce05ba08b96b50975573b2cd619b9053 100644 --- a/build.zig +++ b/build.zig @@ -72,9 +72,22 @@ pub fn build(b: *Builder) !void { if (!only_install_lib_files) { exe.install(); } + const tracy = b.option([]const u8, "tracy", "Enable Tracy integration. Supply path to Tracy source"); const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false; if (link_libc) exe.linkLibC(); + exe.addBuildOption(bool, "enable_tracy", tracy != null); + if (tracy) |tracy_path| { + const client_cpp = fs.path.join( + b.allocator, + &[_][]const u8{ tracy_path, "TracyClient.cpp" }, + ) catch unreachable; + exe.addIncludeDir(tracy_path); + exe.addCSourceFile(client_cpp, &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" }); + exe.linkSystemLibraryName("c++"); + exe.linkLibC(); + } + b.installDirectory(InstallDirectoryOptions{ .source_dir = "lib", .install_dir = .Lib, diff --git a/lib/std/build.zig b/lib/std/build.zig index d98ef71a59c596799a7e743dc629b1a617589878..3a6585c90ffc2f0fc16e3c98207a4ea25b2cc238 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1905,10 +1905,11 @@ pub const LibExeObjStep = struct { builder.allocator, &[_][]const u8{ builder.cache_root, builder.fmt("{}_build_options.zig", .{self.name}) }, ); - try fs.cwd().writeFile(build_options_file, self.build_options_contents.span()); + const path_from_root = builder.pathFromRoot(build_options_file); + try fs.cwd().writeFile(path_from_root, self.build_options_contents.span()); try zig_args.append("--pkg-begin"); try zig_args.append("build_options"); - try zig_args.append(builder.pathFromRoot(build_options_file)); + try zig_args.append(path_from_root); try zig_args.append("--pkg-end"); } diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 947495f0ae3e2e21abfc215866f012136362ca95..508a9b279981767d082b0df3ea6f67707c114fa0 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -16,6 +16,7 @@ const zir = @import("zir.zig"); const Module = @This(); const Inst = ir.Inst; const ast = std.zig.ast; +const trace = @import("tracy.zig").trace; /// General-purpose allocator. allocator: *Allocator, @@ -796,6 +797,9 @@ pub fn target(self: Module) std.Target { /// Detect changes to source files, perform semantic analysis, and update the output files. pub fn update(self: *Module) !void { + const tracy = trace(@src()); + defer tracy.end(); + self.generation += 1; // TODO Use the cache hash file system to detect which source files changed. @@ -1050,6 +1054,9 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { } fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !void { + const tracy = trace(@src()); + defer tracy.end(); + const file_scope = decl.scope.cast(Scope.File).?; const tree = try self.getAstTree(file_scope); const ast_node = tree.root_node.decls()[decl.src_index]; @@ -1330,6 +1337,9 @@ fn astGenIntegerLiteral(self: *Module, scope: *Scope, int_lit: *ast.Node.Integer } fn astGenBlock(self: *Module, scope: *Scope, block_node: *ast.Node.Block) !void { + const tracy = trace(@src()); + defer tracy.end(); + if (block_node.label) |label| { return self.failTok(scope, label, "TODO implement labeled blocks", .{}); } @@ -1526,6 +1536,9 @@ fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { } fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { + const tracy = trace(@src()); + defer tracy.end(); + switch (root_scope.status) { .never_loaded, .unloaded_success => { try self.failed_files.ensureCapacity(self.failed_files.size + 1); @@ -1739,6 +1752,9 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { } fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { + const tracy = trace(@src()); + defer tracy.end(); + // Use the Decl's arena for function memory. var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); defer decl.typed_value.most_recent.arena.?.* = arena.state; diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index f412b2dad2163440f744b2ef6a7a57080eec41bb..7473ccc43156a333646392cf03319c65054f016a 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -10,6 +10,7 @@ const Module = @import("Module.zig"); const ErrorMsg = Module.ErrorMsg; const Target = std.Target; const Allocator = mem.Allocator; +const trace = @import("tracy.zig").trace; pub const Result = union(enum) { /// The `code` parameter passed to `generateSymbol` has the value appended. @@ -29,6 +30,9 @@ pub fn generateSymbol( /// A Decl that this symbol depends on had a semantic analysis failure. AnalysisFail, }!Result { + const tracy = trace(@src()); + defer tracy.end(); + switch (typed_value.ty.zigTypeTag()) { .Fn => { const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; diff --git a/src-self-hosted/tracy.zig b/src-self-hosted/tracy.zig new file mode 100644 index 0000000000000000000000000000000000000000..1e480d75b0a37d52b01f2aa3e5c622541b32c8f8 --- /dev/null +++ b/src-self-hosted/tracy.zig @@ -0,0 +1,45 @@ +pub const std = @import("std"); + +pub const enable = @import("build_options").enable_tracy; + +extern fn ___tracy_emit_zone_begin_callstack( + srcloc: *const ___tracy_source_location_data, + depth: c_int, + active: c_int, +) ___tracy_c_zone_context; + +extern fn ___tracy_emit_zone_end(ctx: ___tracy_c_zone_context) void; + +pub const ___tracy_source_location_data = extern struct { + name: ?[*:0]const u8, + function: [*:0]const u8, + file: [*:0]const u8, + line: u32, + color: u32, +}; + +pub const ___tracy_c_zone_context = extern struct { + id: u32, + active: c_int, + + pub fn end(self: ___tracy_c_zone_context) void { + ___tracy_emit_zone_end(self); + } +}; + +pub const Ctx = if (enable) ___tracy_c_zone_context else struct { + pub fn end(self: Ctx) void {} +}; + +pub inline fn trace(comptime src: std.builtin.SourceLocation) Ctx { + if (!enable) return .{}; + + const loc: ___tracy_source_location_data = .{ + .name = null, + .function = src.fn_name.ptr, + .file = src.file.ptr, + .line = src.line, + .color = 0, + }; + return ___tracy_emit_zone_begin_callstack(&loc, 1, 1); +} -- 2.54.0 From 237c5429b0e8a089c89d13031ae6004ebac55ba9 Mon Sep 17 00:00:00 2001 From: Haze Booth Date: Thu, 18 Jun 2020 23:02:21 -0400 Subject: [PATCH 063/295] Don't attempt to use io from thin air --- lib/std/log.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/log.zig b/lib/std/log.zig index 706c2b23788c827e7e0853fb35819f1ebea16280..63aeaecf888d38c9d51efe51266e754ab9186fc9 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -109,7 +109,7 @@ fn log( } else if (builtin.mode != .ReleaseSmall) { const held = std.debug.getStderrMutex().acquire(); defer held.release(); - const stderr = io.getStdErr().writer(); + const stderr = std.io.getStdErr().writer(); nosuspend stderr.print(format, args) catch return; } } -- 2.54.0 From 605769ec25923a50fc01d29d5837fbc48abffcf1 Mon Sep 17 00:00:00 2001 From: Eleanor NB Date: Fri, 19 Jun 2020 19:38:22 +1000 Subject: [PATCH 064/295] Replaced all occurrences of std.debug.warn in the docs with std.debug.print --- doc/langref.html.in | 157 ++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 79 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 4a562932082c49690fd50ebf6a89d2da74f6bcb3..e810b36be21cfa290e73f5cfc6bab6ca876f7579 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -236,19 +236,18 @@ pub fn main() !void { } {#code_end#}

- Usually you don't want to write to stdout. You want to write to stderr. And you - don't care if it fails. It's more like a warning message that you want - to emit. For that you can use a simpler API: + Usually you don't want to write to stdout. You want to write to stderr, and you + don't care if it fails. For that you can use a simpler API:

{#code_begin|exe|hello#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; pub fn main() void { - warn("Hello, world!\n", .{}); + print("Hello, world!\n", .{}); } {#code_end#}

- Note that you can leave off the {#syntax#}!{#endsyntax#} from the return type because {#syntax#}warn{#endsyntax#} cannot fail. + Note that you can leave off the {#syntax#}!{#endsyntax#} from the return type because {#syntax#}print{#endsyntax#} cannot fail.

{#see_also|Values|@import|Errors|Root Source File#} {#header_close#} @@ -307,7 +306,7 @@ const Timestamp = struct { {#header_open|Values#} {#code_begin|exe|values#} // Top-level declarations are order-independent: -const warn = std.debug.warn; +const print = std.debug.print; const std = @import("std"); const os = std.os; const assert = std.debug.assert; @@ -315,14 +314,14 @@ const assert = std.debug.assert; pub fn main() void { // integers const one_plus_one: i32 = 1 + 1; - warn("1 + 1 = {}\n", .{one_plus_one}); + print("1 + 1 = {}\n", .{one_plus_one}); // floats const seven_div_three: f32 = 7.0 / 3.0; - warn("7.0 / 3.0 = {}\n", .{seven_div_three}); + print("7.0 / 3.0 = {}\n", .{seven_div_three}); // boolean - warn("{}\n{}\n{}\n", .{ + print("{}\n{}\n{}\n", .{ true and false, true or false, !true, @@ -332,7 +331,7 @@ pub fn main() void { var optional_value: ?[]const u8 = null; assert(optional_value == null); - warn("\noptional 1\ntype: {}\nvalue: {}\n", .{ + print("\noptional 1\ntype: {}\nvalue: {}\n", .{ @typeName(@TypeOf(optional_value)), optional_value, }); @@ -340,7 +339,7 @@ pub fn main() void { optional_value = "hi"; assert(optional_value != null); - warn("\noptional 2\ntype: {}\nvalue: {}\n", .{ + print("\noptional 2\ntype: {}\nvalue: {}\n", .{ @typeName(@TypeOf(optional_value)), optional_value, }); @@ -348,14 +347,14 @@ pub fn main() void { // error union var number_or_error: anyerror!i32 = error.ArgNotFound; - warn("\nerror union 1\ntype: {}\nvalue: {}\n", .{ + print("\nerror union 1\ntype: {}\nvalue: {}\n", .{ @typeName(@TypeOf(number_or_error)), number_or_error, }); number_or_error = 1234; - warn("\nerror union 2\ntype: {}\nvalue: {}\n", .{ + print("\nerror union 2\ntype: {}\nvalue: {}\n", .{ @typeName(@TypeOf(number_or_error)), number_or_error, }); @@ -994,15 +993,15 @@ export fn foo_optimized(x: f64) f64 { which operates in strict mode.

{#code_begin|exe|float_mode#} {#code_link_object|foo#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; extern fn foo_strict(x: f64) f64; extern fn foo_optimized(x: f64) f64; pub fn main() void { const x = 0.001; - warn("optimized = {}\n", .{foo_optimized(x)}); - warn("strict = {}\n", .{foo_strict(x)}); + print("optimized = {}\n", .{foo_optimized(x)}); + print("strict = {}\n", .{foo_strict(x)}); } {#code_end#} {#see_also|@setFloatMode|Division by Zero#} @@ -2668,9 +2667,9 @@ const std = @import("std"); pub fn main() void { const Foo = struct {}; - std.debug.warn("variable: {}\n", .{@typeName(Foo)}); - std.debug.warn("anonymous: {}\n", .{@typeName(struct {})}); - std.debug.warn("function: {}\n", .{@typeName(List(i32))}); + std.debug.print("variable: {}\n", .{@typeName(Foo)}); + std.debug.print("anonymous: {}\n", .{@typeName(struct {})}); + std.debug.print("function: {}\n", .{@typeName(List(i32))}); } fn List(comptime T: type) type { @@ -3869,7 +3868,7 @@ test "if error union" { {#code_begin|test|defer#} const std = @import("std"); const assert = std.debug.assert; -const warn = std.debug.warn; +const print = std.debug.print; // defer will execute an expression at the end of the current scope. fn deferExample() usize { @@ -3892,18 +3891,18 @@ test "defer basics" { // If multiple defer statements are specified, they will be executed in // the reverse order they were run. fn deferUnwindExample() void { - warn("\n", .{}); + print("\n", .{}); defer { - warn("1 ", .{}); + print("1 ", .{}); } defer { - warn("2 ", .{}); + print("2 ", .{}); } if (false) { // defers are not run if they are never executed. defer { - warn("3 ", .{}); + print("3 ", .{}); } } } @@ -3918,15 +3917,15 @@ test "defer unwinding" { // This is especially useful in allowing a function to clean up properly // on error, and replaces goto error handling tactics as seen in c. fn deferErrorExample(is_error: bool) !void { - warn("\nstart of function\n", .{}); + print("\nstart of function\n", .{}); // This will always be executed on exit defer { - warn("end of function\n", .{}); + print("end of function\n", .{}); } errdefer { - warn("encountered an error!\n", .{}); + print("encountered an error!\n", .{}); } if (is_error) { @@ -5925,13 +5924,13 @@ const Node = struct { Putting all of this together, let's see how {#syntax#}printf{#endsyntax#} works in Zig.

{#code_begin|exe|printf#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; const a_number: i32 = 1234; const a_string = "foobar"; pub fn main() void { - warn("here is a string: '{}' here is a number: {}\n", .{a_string, a_number}); + print("here is a string: '{}' here is a number: {}\n", .{a_string, a_number}); } {#code_end#} @@ -6045,13 +6044,13 @@ pub fn printValue(self: *OutStream, value: var) !void { And now, what happens if we give too many arguments to {#syntax#}printf{#endsyntax#}?

{#code_begin|test_err|Unused arguments#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; const a_number: i32 = 1234; const a_string = "foobar"; test "printf too many arguments" { - warn("here is a string: '{}' here is a number: {}\n", .{ + print("here is a string: '{}' here is a number: {}\n", .{ a_string, a_number, a_number, @@ -6066,14 +6065,14 @@ test "printf too many arguments" { only that it is a compile-time known value that can be coerced to a {#syntax#}[]const u8{#endsyntax#}:

{#code_begin|exe|printf#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; const a_number: i32 = 1234; const a_string = "foobar"; const fmt = "here is a string: '{}' here is a number: {}\n"; pub fn main() void { - warn(fmt, .{a_string, a_number}); + print(fmt, .{a_string, a_number}); } {#code_end#}

@@ -6511,7 +6510,7 @@ pub fn main() void { fn amainWrap() void { amain() catch |e| { - std.debug.warn("{}\n", .{e}); + std.debug.print("{}\n", .{e}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } @@ -6541,8 +6540,8 @@ fn amain() !void { const download_text = try await download_frame; defer allocator.free(download_text); - std.debug.warn("download_text: {}\n", .{download_text}); - std.debug.warn("file_text: {}\n", .{file_text}); + std.debug.print("download_text: {}\n", .{download_text}); + std.debug.print("file_text: {}\n", .{file_text}); } var global_download_frame: anyframe = undefined; @@ -6552,7 +6551,7 @@ fn fetchUrl(allocator: *Allocator, url: []const u8) ![]u8 { suspend { global_download_frame = @frame(); } - std.debug.warn("fetchUrl returning\n", .{}); + std.debug.print("fetchUrl returning\n", .{}); return result; } @@ -6563,7 +6562,7 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 { suspend { global_file_frame = @frame(); } - std.debug.warn("readFile returning\n", .{}); + std.debug.print("readFile returning\n", .{}); return result; } {#code_end#} @@ -6581,7 +6580,7 @@ pub fn main() void { fn amainWrap() void { amain() catch |e| { - std.debug.warn("{}\n", .{e}); + std.debug.print("{}\n", .{e}); if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } @@ -6611,21 +6610,21 @@ fn amain() !void { const download_text = try await download_frame; defer allocator.free(download_text); - std.debug.warn("download_text: {}\n", .{download_text}); - std.debug.warn("file_text: {}\n", .{file_text}); + std.debug.print("download_text: {}\n", .{download_text}); + std.debug.print("file_text: {}\n", .{file_text}); } fn fetchUrl(allocator: *Allocator, url: []const u8) ![]u8 { const result = try std.mem.dupe(allocator, u8, "this is the downloaded url contents"); errdefer allocator.free(result); - std.debug.warn("fetchUrl returning\n", .{}); + std.debug.print("fetchUrl returning\n", .{}); return result; } fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 { const result = try std.mem.dupe(allocator, u8, "this is the file contents"); errdefer allocator.free(result); - std.debug.warn("readFile returning\n", .{}); + std.debug.print("readFile returning\n", .{}); return result; } {#code_end#} @@ -7121,7 +7120,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val compile-time executing code.

{#code_begin|test_err|found compile log statement#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; const num1 = blk: { var val1: i32 = 99; @@ -7133,7 +7132,7 @@ const num1 = blk: { test "main" { @compileLog("comptime in main"); - warn("Runtime in main, num1 = {}.\n", .{num1}); + print("Runtime in main, num1 = {}.\n", .{num1}); } {#code_end#}

@@ -7145,7 +7144,7 @@ test "main" { program compiles successfully and the generated executable prints:

{#code_begin|test#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; const num1 = blk: { var val1: i32 = 99; @@ -7154,7 +7153,7 @@ const num1 = blk: { }; test "main" { - warn("Runtime in main, num1 = {}.\n", .{num1}); + print("Runtime in main, num1 = {}.\n", .{num1}); } {#code_end#} {#header_close#} @@ -8555,7 +8554,7 @@ const std = @import("std"); pub fn main() void { var value: i32 = -1; var unsigned = @intCast(u32, value); - std.debug.warn("value: {}\n", .{unsigned}); + std.debug.print("value: {}\n", .{unsigned}); } {#code_end#}

@@ -8577,7 +8576,7 @@ const std = @import("std"); pub fn main() void { var spartan_count: u16 = 300; const byte = @intCast(u8, spartan_count); - std.debug.warn("value: {}\n", .{byte}); + std.debug.print("value: {}\n", .{byte}); } {#code_end#}

@@ -8611,7 +8610,7 @@ const std = @import("std"); pub fn main() void { var byte: u8 = 255; byte += 1; - std.debug.warn("value: {}\n", .{byte}); + std.debug.print("value: {}\n", .{byte}); } {#code_end#} {#header_close#} @@ -8629,16 +8628,16 @@ pub fn main() void {

Example of catching an overflow for addition:

{#code_begin|exe_err#} const math = @import("std").math; -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; pub fn main() !void { var byte: u8 = 255; byte = if (math.add(u8, byte, 1)) |result| result else |err| { - warn("unable to add one: {}\n", .{@errorName(err)}); + print("unable to add one: {}\n", .{@errorName(err)}); return err; }; - warn("result: {}\n", .{byte}); + print("result: {}\n", .{byte}); } {#code_end#} {#header_close#} @@ -8657,15 +8656,15 @@ pub fn main() !void { Example of {#link|@addWithOverflow#}:

{#code_begin|exe#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; pub fn main() void { var byte: u8 = 255; var result: u8 = undefined; if (@addWithOverflow(u8, byte, 10, &result)) { - warn("overflowed result: {}\n", .{result}); + print("overflowed result: {}\n", .{result}); } else { - warn("result: {}\n", .{result}); + print("result: {}\n", .{result}); } } {#code_end#} @@ -8710,7 +8709,7 @@ const std = @import("std"); pub fn main() void { var x: u8 = 0b01010101; var y = @shlExact(x, 2); - std.debug.warn("value: {}\n", .{y}); + std.debug.print("value: {}\n", .{y}); } {#code_end#} {#header_close#} @@ -8728,7 +8727,7 @@ const std = @import("std"); pub fn main() void { var x: u8 = 0b10101010; var y = @shrExact(x, 2); - std.debug.warn("value: {}\n", .{y}); + std.debug.print("value: {}\n", .{y}); } {#code_end#} {#header_close#} @@ -8749,7 +8748,7 @@ pub fn main() void { var a: u32 = 1; var b: u32 = 0; var c = a / b; - std.debug.warn("value: {}\n", .{c}); + std.debug.print("value: {}\n", .{c}); } {#code_end#} {#header_close#} @@ -8770,7 +8769,7 @@ pub fn main() void { var a: u32 = 10; var b: u32 = 0; var c = a % b; - std.debug.warn("value: {}\n", .{c}); + std.debug.print("value: {}\n", .{c}); } {#code_end#} {#header_close#} @@ -8791,7 +8790,7 @@ pub fn main() void { var a: u32 = 10; var b: u32 = 3; var c = @divExact(a, b); - std.debug.warn("value: {}\n", .{c}); + std.debug.print("value: {}\n", .{c}); } {#code_end#} {#header_close#} @@ -8810,20 +8809,20 @@ const std = @import("std"); pub fn main() void { var optional_number: ?i32 = null; var number = optional_number.?; - std.debug.warn("value: {}\n", .{number}); + std.debug.print("value: {}\n", .{number}); } {#code_end#}

One way to avoid this crash is to test for null instead of assuming non-null, with the {#syntax#}if{#endsyntax#} expression:

{#code_begin|exe|test#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; pub fn main() void { const optional_number: ?i32 = null; if (optional_number) |number| { - warn("got number: {}\n", .{number}); + print("got number: {}\n", .{number}); } else { - warn("it's null\n", .{}); + print("it's null\n", .{}); } } {#code_end#} @@ -8846,7 +8845,7 @@ const std = @import("std"); pub fn main() void { const number = getNumberOrFail() catch unreachable; - std.debug.warn("value: {}\n", .{number}); + std.debug.print("value: {}\n", .{number}); } fn getNumberOrFail() !i32 { @@ -8856,15 +8855,15 @@ fn getNumberOrFail() !i32 {

One way to avoid this crash is to test for an error instead of assuming a successful result, with the {#syntax#}if{#endsyntax#} expression:

{#code_begin|exe#} -const warn = @import("std").debug.warn; +const print = @import("std").debug.print; pub fn main() void { const result = getNumberOrFail(); if (result) |number| { - warn("got number: {}\n", .{number}); + print("got number: {}\n", .{number}); } else |err| { - warn("got error: {}\n", .{@errorName(err)}); + print("got error: {}\n", .{@errorName(err)}); } } @@ -8891,7 +8890,7 @@ pub fn main() void { var err = error.AnError; var number = @errorToInt(err) + 500; var invalid_err = @intToError(number); - std.debug.warn("value: {}\n", .{number}); + std.debug.print("value: {}\n", .{number}); } {#code_end#} {#header_close#} @@ -8921,7 +8920,7 @@ const Foo = enum { pub fn main() void { var a: u2 = 3; var b = @intToEnum(Foo, a); - std.debug.warn("value: {}\n", .{@tagName(b)}); + std.debug.print("value: {}\n", .{@tagName(b)}); } {#code_end#} {#header_close#} @@ -8958,7 +8957,7 @@ pub fn main() void { } fn foo(set1: Set1) void { const x = @errSetCast(Set2, set1); - std.debug.warn("value: {}\n", .{x}); + std.debug.print("value: {}\n", .{x}); } {#code_end#} {#header_close#} @@ -9015,7 +9014,7 @@ pub fn main() void { fn bar(f: *Foo) void { f.float = 12.34; - std.debug.warn("value: {}\n", .{f.float}); + std.debug.print("value: {}\n", .{f.float}); } {#code_end#}

@@ -9039,7 +9038,7 @@ pub fn main() void { fn bar(f: *Foo) void { f.* = Foo{ .float = 12.34 }; - std.debug.warn("value: {}\n", .{f.float}); + std.debug.print("value: {}\n", .{f.float}); } {#code_end#}

@@ -9058,7 +9057,7 @@ pub fn main() void { var f = Foo{ .int = 42 }; f = Foo{ .float = undefined }; bar(&f); - std.debug.warn("value: {}\n", .{f.float}); + std.debug.print("value: {}\n", .{f.float}); } fn bar(f: *Foo) void { @@ -9178,7 +9177,7 @@ pub fn main() !void { const allocator = &arena.allocator; const ptr = try allocator.create(i32); - std.debug.warn("ptr={*}\n", .{ptr}); + std.debug.print("ptr={*}\n", .{ptr}); } {#code_end#} When using this kind of allocator, there is no need to free anything manually. Everything @@ -9712,7 +9711,7 @@ pub fn main() !void { defer std.process.argsFree(std.heap.page_allocator, args); for (args) |arg, i| { - std.debug.warn("{}: {}\n", .{i, arg}); + std.debug.print("{}: {}\n", .{i, arg}); } } {#code_end#} @@ -9734,7 +9733,7 @@ pub fn main() !void { try preopens.populate(); for (preopens.asSlice()) |preopen, i| { - std.debug.warn("{}: {}\n", .{ i, preopen }); + std.debug.print("{}: {}\n", .{ i, preopen }); } } {#code_end#} -- 2.54.0 From 85277183504148dd5c14bd153661377e76118d3b Mon Sep 17 00:00:00 2001 From: Sebastian <15335529+Sobeston@users.noreply.github.com> Date: Sat, 20 Jun 2020 12:16:57 +0100 Subject: [PATCH 065/295] langref - document that This works on enums too --- doc/langref.html.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index e810b36be21cfa290e73f5cfc6bab6ca876f7579..aaa15e91d03758be100a0463f6c2f5c6ab3c333a 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8204,7 +8204,7 @@ test "vector @splat" { {#header_open|@This#}

{#syntax#}@This() type{#endsyntax#}

- Returns the innermost struct or union that this function call is inside. + Returns the innermost struct, enum, or union that this function call is inside. This can be useful for an anonymous struct that needs to refer to itself:

{#code_begin|test#} -- 2.54.0 From 5229f6ec68708c3e66393a50ad9f9032ee7f4257 Mon Sep 17 00:00:00 2001 From: data-man Date: Sat, 20 Jun 2020 15:02:48 +0500 Subject: [PATCH 066/295] Use writer in std.fmt --- lib/std/fmt.zig | 350 ++++++++++++++++++++++++------------------------ 1 file changed, 175 insertions(+), 175 deletions(-) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 23066a6963ebc2afbb0ca7f59a3bcc93f30f7807..cf0b21dc3a43826121090f32a2aaa87426d9e788 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -69,14 +69,14 @@ fn peekIsAlign(comptime fmt: []const u8) bool { /// /// If a formatted user type contains a function of the type /// ``` -/// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: var) !void +/// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: var) !void /// ``` /// with `?` being the type formatted, this function will be called instead of the default implementation. /// This allows user types to be formatted in a logical manner instead of dumping all fields of the type. /// /// A user type may be a `struct`, `vector`, `union` or `enum` type. pub fn format( - out_stream: var, + writer: var, comptime fmt: []const u8, args: var, ) !void { @@ -136,7 +136,7 @@ pub fn format( .Start => switch (c) { '{' => { if (start_index < i) { - try out_stream.writeAll(fmt[start_index..i]); + try writer.writeAll(fmt[start_index..i]); } start_index = i; @@ -148,7 +148,7 @@ pub fn format( }, '}' => { if (start_index < i) { - try out_stream.writeAll(fmt[start_index..i]); + try writer.writeAll(fmt[start_index..i]); } state = .CloseBrace; }, @@ -183,7 +183,7 @@ pub fn format( args[arg_to_print], fmt[0..0], options, - out_stream, + writer, default_max_depth, ); @@ -214,7 +214,7 @@ pub fn format( args[arg_to_print], fmt[specifier_start..i], options, - out_stream, + writer, default_max_depth, ); state = .Start; @@ -259,7 +259,7 @@ pub fn format( args[arg_to_print], fmt[specifier_start..specifier_end], options, - out_stream, + writer, default_max_depth, ); state = .Start; @@ -285,7 +285,7 @@ pub fn format( args[arg_to_print], fmt[specifier_start..specifier_end], options, - out_stream, + writer, default_max_depth, ); state = .Start; @@ -306,7 +306,7 @@ pub fn format( } } if (start_index < fmt.len) { - try out_stream.writeAll(fmt[start_index..]); + try writer.writeAll(fmt[start_index..]); } } @@ -314,140 +314,140 @@ pub fn formatType( value: var, comptime fmt: []const u8, options: FormatOptions, - out_stream: var, + writer: var, max_depth: usize, -) @TypeOf(out_stream).Error!void { +) @TypeOf(writer).Error!void { if (comptime std.mem.eql(u8, fmt, "*")) { - try out_stream.writeAll(@typeName(@TypeOf(value).Child)); - try out_stream.writeAll("@"); - try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, out_stream); + try writer.writeAll(@typeName(@TypeOf(value).Child)); + try writer.writeAll("@"); + try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, writer); return; } const T = @TypeOf(value); if (comptime std.meta.trait.hasFn("format")(T)) { - return try value.format(fmt, options, out_stream); + return try value.format(fmt, options, writer); } switch (@typeInfo(T)) { .ComptimeInt, .Int, .ComptimeFloat, .Float => { - return formatValue(value, fmt, options, out_stream); + return formatValue(value, fmt, options, writer); }, .Void => { - return formatBuf("void", options, out_stream); + return formatBuf("void", options, writer); }, .Bool => { - return formatBuf(if (value) "true" else "false", options, out_stream); + return formatBuf(if (value) "true" else "false", options, writer); }, .Optional => { if (value) |payload| { - return formatType(payload, fmt, options, out_stream, max_depth); + return formatType(payload, fmt, options, writer, max_depth); } else { - return formatBuf("null", options, out_stream); + return formatBuf("null", options, writer); } }, .ErrorUnion => { if (value) |payload| { - return formatType(payload, fmt, options, out_stream, max_depth); + return formatType(payload, fmt, options, writer, max_depth); } else |err| { - return formatType(err, fmt, options, out_stream, max_depth); + return formatType(err, fmt, options, writer, max_depth); } }, .ErrorSet => { - try out_stream.writeAll("error."); - return out_stream.writeAll(@errorName(value)); + try writer.writeAll("error."); + return writer.writeAll(@errorName(value)); }, .Enum => |enumInfo| { - try out_stream.writeAll(@typeName(T)); + try writer.writeAll(@typeName(T)); if (enumInfo.is_exhaustive) { - try out_stream.writeAll("."); - try out_stream.writeAll(@tagName(value)); + try writer.writeAll("."); + try writer.writeAll(@tagName(value)); return; } // Use @tagName only if value is one of known fields inline for (enumInfo.fields) |enumField| { if (@enumToInt(value) == enumField.value) { - try out_stream.writeAll("."); - try out_stream.writeAll(@tagName(value)); + try writer.writeAll("."); + try writer.writeAll(@tagName(value)); return; } } - try out_stream.writeAll("("); - try formatType(@enumToInt(value), fmt, options, out_stream, max_depth); - try out_stream.writeAll(")"); + try writer.writeAll("("); + try formatType(@enumToInt(value), fmt, options, writer, max_depth); + try writer.writeAll(")"); }, .Union => { - try out_stream.writeAll(@typeName(T)); + try writer.writeAll(@typeName(T)); if (max_depth == 0) { - return out_stream.writeAll("{ ... }"); + return writer.writeAll("{ ... }"); } const info = @typeInfo(T).Union; if (info.tag_type) |UnionTagType| { - try out_stream.writeAll("{ ."); - try out_stream.writeAll(@tagName(@as(UnionTagType, value))); - try out_stream.writeAll(" = "); + try writer.writeAll("{ ."); + try writer.writeAll(@tagName(@as(UnionTagType, value))); + try writer.writeAll(" = "); inline for (info.fields) |u_field| { if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { - try formatType(@field(value, u_field.name), fmt, options, out_stream, max_depth - 1); + try formatType(@field(value, u_field.name), fmt, options, writer, max_depth - 1); } } - try out_stream.writeAll(" }"); + try writer.writeAll(" }"); } else { - try format(out_stream, "@{x}", .{@ptrToInt(&value)}); + try format(writer, "@{x}", .{@ptrToInt(&value)}); } }, .Struct => |StructT| { - try out_stream.writeAll(@typeName(T)); + try writer.writeAll(@typeName(T)); if (max_depth == 0) { - return out_stream.writeAll("{ ... }"); + return writer.writeAll("{ ... }"); } - try out_stream.writeAll("{"); + try writer.writeAll("{"); inline for (StructT.fields) |f, i| { if (i == 0) { - try out_stream.writeAll(" ."); + try writer.writeAll(" ."); } else { - try out_stream.writeAll(", ."); + try writer.writeAll(", ."); } - try out_stream.writeAll(f.name); - try out_stream.writeAll(" = "); - try formatType(@field(value, f.name), fmt, options, out_stream, max_depth - 1); + try writer.writeAll(f.name); + try writer.writeAll(" = "); + try formatType(@field(value, f.name), fmt, options, writer, max_depth - 1); } - try out_stream.writeAll(" }"); + try writer.writeAll(" }"); }, .Pointer => |ptr_info| switch (ptr_info.size) { .One => switch (@typeInfo(ptr_info.child)) { .Array => |info| { if (info.child == u8) { - return formatText(value, fmt, options, out_stream); + return formatText(value, fmt, options, writer); } - return format(out_stream, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); + return format(writer, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); }, .Enum, .Union, .Struct => { - return formatType(value.*, fmt, options, out_stream, max_depth); + return formatType(value.*, fmt, options, writer, max_depth); }, - else => return format(out_stream, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }), + else => return format(writer, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }), }, .Many, .C => { if (ptr_info.sentinel) |sentinel| { - return formatType(mem.span(value), fmt, options, out_stream, max_depth); + return formatType(mem.span(value), fmt, options, writer, max_depth); } if (ptr_info.child == u8) { if (fmt.len > 0 and fmt[0] == 's') { - return formatText(mem.span(value), fmt, options, out_stream); + return formatText(mem.span(value), fmt, options, writer); } } - return format(out_stream, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); + return format(writer, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); }, .Slice => { if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { - return formatText(value, fmt, options, out_stream); + return formatText(value, fmt, options, writer); } if (ptr_info.child == u8) { - return formatText(value, fmt, options, out_stream); + return formatText(value, fmt, options, writer); } - return format(out_stream, "{}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value.ptr) }); + return format(writer, "{}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value.ptr) }); }, }, .Array => |info| { @@ -462,27 +462,27 @@ pub fn formatType( .sentinel = null, }, }); - return formatType(@as(Slice, &value), fmt, options, out_stream, max_depth); + return formatType(@as(Slice, &value), fmt, options, writer, max_depth); }, .Vector => { const len = @typeInfo(T).Vector.len; - try out_stream.writeAll("{ "); + try writer.writeAll("{ "); var i: usize = 0; while (i < len) : (i += 1) { - try formatValue(value[i], fmt, options, out_stream); + try formatValue(value[i], fmt, options, writer); if (i < len - 1) { - try out_stream.writeAll(", "); + try writer.writeAll(", "); } } - try out_stream.writeAll(" }"); + try writer.writeAll(" }"); }, .Fn => { - return format(out_stream, "{}@{x}", .{ @typeName(T), @ptrToInt(value) }); + return format(writer, "{}@{x}", .{ @typeName(T), @ptrToInt(value) }); }, - .Type => return out_stream.writeAll(@typeName(T)), + .Type => return writer.writeAll(@typeName(T)), .EnumLiteral => { const buffer = [_]u8{'.'} ++ @tagName(value); - return formatType(buffer, fmt, options, out_stream, max_depth); + return formatType(buffer, fmt, options, writer, max_depth); }, else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), } @@ -492,19 +492,19 @@ fn formatValue( value: var, comptime fmt: []const u8, options: FormatOptions, - out_stream: var, + writer: var, ) !void { if (comptime std.mem.eql(u8, fmt, "B")) { - return formatBytes(value, options, 1000, out_stream); + return formatBytes(value, options, 1000, writer); } else if (comptime std.mem.eql(u8, fmt, "Bi")) { - return formatBytes(value, options, 1024, out_stream); + return formatBytes(value, options, 1024, writer); } const T = @TypeOf(value); switch (@typeInfo(T)) { - .Float, .ComptimeFloat => return formatFloatValue(value, fmt, options, out_stream), - .Int, .ComptimeInt => return formatIntValue(value, fmt, options, out_stream), - .Bool => return formatBuf(if (value) "true" else "false", options, out_stream), + .Float, .ComptimeFloat => return formatFloatValue(value, fmt, options, writer), + .Int, .ComptimeInt => return formatIntValue(value, fmt, options, writer), + .Bool => return formatBuf(if (value) "true" else "false", options, writer), else => comptime unreachable, } } @@ -513,7 +513,7 @@ pub fn formatIntValue( value: var, comptime fmt: []const u8, options: FormatOptions, - out_stream: var, + writer: var, ) !void { comptime var radix = 10; comptime var uppercase = false; @@ -529,7 +529,7 @@ pub fn formatIntValue( uppercase = false; } else if (comptime std.mem.eql(u8, fmt, "c")) { if (@TypeOf(int_value).bit_count <= 8) { - return formatAsciiChar(@as(u8, int_value), options, out_stream); + return formatAsciiChar(@as(u8, int_value), options, writer); } else { @compileError("Cannot print integer that is larger than 8 bits as a ascii"); } @@ -546,19 +546,19 @@ pub fn formatIntValue( @compileError("Unknown format string: '" ++ fmt ++ "'"); } - return formatInt(int_value, radix, uppercase, options, out_stream); + return formatInt(int_value, radix, uppercase, options, writer); } fn formatFloatValue( value: var, comptime fmt: []const u8, options: FormatOptions, - out_stream: var, + writer: var, ) !void { if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { - return formatFloatScientific(value, options, out_stream); + return formatFloatScientific(value, options, writer); } else if (comptime std.mem.eql(u8, fmt, "d")) { - return formatFloatDecimal(value, options, out_stream); + return formatFloatDecimal(value, options, writer); } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } @@ -568,13 +568,13 @@ pub fn formatText( bytes: []const u8, comptime fmt: []const u8, options: FormatOptions, - out_stream: var, + writer: var, ) !void { if (comptime std.mem.eql(u8, fmt, "s") or (fmt.len == 0)) { - return formatBuf(bytes, options, out_stream); + return formatBuf(bytes, options, writer); } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { for (bytes) |c| { - try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, out_stream); + try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, writer); } return; } else { @@ -585,38 +585,38 @@ pub fn formatText( pub fn formatAsciiChar( c: u8, options: FormatOptions, - out_stream: var, + writer: var, ) !void { - return out_stream.writeAll(@as(*const [1]u8, &c)); + return writer.writeAll(@as(*const [1]u8, &c)); } pub fn formatBuf( buf: []const u8, options: FormatOptions, - out_stream: var, + writer: var, ) !void { const width = options.width orelse buf.len; var padding = if (width > buf.len) (width - buf.len) else 0; const pad_byte = [1]u8{options.fill}; switch (options.alignment) { .Left => { - try out_stream.writeAll(buf); + try writer.writeAll(buf); while (padding > 0) : (padding -= 1) { - try out_stream.writeAll(&pad_byte); + try writer.writeAll(&pad_byte); } }, .Center => { const padl = padding / 2; var i: usize = 0; - while (i < padl) : (i += 1) try out_stream.writeAll(&pad_byte); - try out_stream.writeAll(buf); - while (i < padding) : (i += 1) try out_stream.writeAll(&pad_byte); + while (i < padl) : (i += 1) try writer.writeAll(&pad_byte); + try writer.writeAll(buf); + while (i < padding) : (i += 1) try writer.writeAll(&pad_byte); }, .Right => { while (padding > 0) : (padding -= 1) { - try out_stream.writeAll(&pad_byte); + try writer.writeAll(&pad_byte); } - try out_stream.writeAll(buf); + try writer.writeAll(buf); }, } } @@ -627,38 +627,38 @@ pub fn formatBuf( pub fn formatFloatScientific( value: var, options: FormatOptions, - out_stream: var, + writer: var, ) !void { var x = @floatCast(f64, value); // Errol doesn't handle these special cases. if (math.signbit(x)) { - try out_stream.writeAll("-"); + try writer.writeAll("-"); x = -x; } if (math.isNan(x)) { - return out_stream.writeAll("nan"); + return writer.writeAll("nan"); } if (math.isPositiveInf(x)) { - return out_stream.writeAll("inf"); + return writer.writeAll("inf"); } if (x == 0.0) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); if (options.precision) |precision| { if (precision != 0) { - try out_stream.writeAll("."); + try writer.writeAll("."); var i: usize = 0; while (i < precision) : (i += 1) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } } } else { - try out_stream.writeAll(".0"); + try writer.writeAll(".0"); } - try out_stream.writeAll("e+00"); + try writer.writeAll("e+00"); return; } @@ -668,50 +668,50 @@ pub fn formatFloatScientific( if (options.precision) |precision| { errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific); - try out_stream.writeAll(float_decimal.digits[0..1]); + try writer.writeAll(float_decimal.digits[0..1]); // {e0} case prints no `.` if (precision != 0) { - try out_stream.writeAll("."); + try writer.writeAll("."); var printed: usize = 0; if (float_decimal.digits.len > 1) { const num_digits = math.min(float_decimal.digits.len, precision + 1); - try out_stream.writeAll(float_decimal.digits[1..num_digits]); + try writer.writeAll(float_decimal.digits[1..num_digits]); printed += num_digits - 1; } while (printed < precision) : (printed += 1) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } } } else { - try out_stream.writeAll(float_decimal.digits[0..1]); - try out_stream.writeAll("."); + try writer.writeAll(float_decimal.digits[0..1]); + try writer.writeAll("."); if (float_decimal.digits.len > 1) { const num_digits = if (@TypeOf(value) == f32) math.min(@as(usize, 9), float_decimal.digits.len) else float_decimal.digits.len; - try out_stream.writeAll(float_decimal.digits[1..num_digits]); + try writer.writeAll(float_decimal.digits[1..num_digits]); } else { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } } - try out_stream.writeAll("e"); + try writer.writeAll("e"); const exp = float_decimal.exp - 1; if (exp >= 0) { - try out_stream.writeAll("+"); + try writer.writeAll("+"); if (exp > -10 and exp < 10) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } - try formatInt(exp, 10, false, FormatOptions{ .width = 0 }, out_stream); + try formatInt(exp, 10, false, FormatOptions{ .width = 0 }, writer); } else { - try out_stream.writeAll("-"); + try writer.writeAll("-"); if (exp > -10 and exp < 10) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } - try formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, out_stream); + try formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, writer); } } @@ -720,34 +720,34 @@ pub fn formatFloatScientific( pub fn formatFloatDecimal( value: var, options: FormatOptions, - out_stream: var, + writer: var, ) !void { var x = @as(f64, value); // Errol doesn't handle these special cases. if (math.signbit(x)) { - try out_stream.writeAll("-"); + try writer.writeAll("-"); x = -x; } if (math.isNan(x)) { - return out_stream.writeAll("nan"); + return writer.writeAll("nan"); } if (math.isPositiveInf(x)) { - return out_stream.writeAll("inf"); + return writer.writeAll("inf"); } if (x == 0.0) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); if (options.precision) |precision| { if (precision != 0) { - try out_stream.writeAll("."); + try writer.writeAll("."); var i: usize = 0; while (i < precision) : (i += 1) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } } else { - try out_stream.writeAll(".0"); + try writer.writeAll(".0"); } } @@ -769,14 +769,14 @@ pub fn formatFloatDecimal( if (num_digits_whole > 0) { // We may have to zero pad, for instance 1e4 requires zero padding. - try out_stream.writeAll(float_decimal.digits[0..num_digits_whole_no_pad]); + try writer.writeAll(float_decimal.digits[0..num_digits_whole_no_pad]); var i = num_digits_whole_no_pad; while (i < num_digits_whole) : (i += 1) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } } else { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } // {.0} special case doesn't want a trailing '.' @@ -784,7 +784,7 @@ pub fn formatFloatDecimal( return; } - try out_stream.writeAll("."); + try writer.writeAll("."); // Keep track of fractional count printed for case where we pre-pad then post-pad with 0's. var printed: usize = 0; @@ -796,7 +796,7 @@ pub fn formatFloatDecimal( var i: usize = 0; while (i < zeros_to_print) : (i += 1) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); printed += 1; } @@ -808,14 +808,14 @@ pub fn formatFloatDecimal( // Remaining fractional portion, zero-padding if insufficient. assert(precision >= printed); if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) { - try out_stream.writeAll(float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); + try writer.writeAll(float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); return; } else { - try out_stream.writeAll(float_decimal.digits[num_digits_whole_no_pad..]); + try writer.writeAll(float_decimal.digits[num_digits_whole_no_pad..]); printed += float_decimal.digits.len - num_digits_whole_no_pad; while (printed < precision) : (printed += 1) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } } } else { @@ -827,14 +827,14 @@ pub fn formatFloatDecimal( if (num_digits_whole > 0) { // We may have to zero pad, for instance 1e4 requires zero padding. - try out_stream.writeAll(float_decimal.digits[0..num_digits_whole_no_pad]); + try writer.writeAll(float_decimal.digits[0..num_digits_whole_no_pad]); var i = num_digits_whole_no_pad; while (i < num_digits_whole) : (i += 1) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } } else { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } // Omit `.` if no fractional portion @@ -842,7 +842,7 @@ pub fn formatFloatDecimal( return; } - try out_stream.writeAll("."); + try writer.writeAll("."); // Zero-fill until we reach significant digits or run out of precision. if (float_decimal.exp < 0) { @@ -850,11 +850,11 @@ pub fn formatFloatDecimal( var i: usize = 0; while (i < zero_digit_count) : (i += 1) { - try out_stream.writeAll("0"); + try writer.writeAll("0"); } } - try out_stream.writeAll(float_decimal.digits[num_digits_whole_no_pad..]); + try writer.writeAll(float_decimal.digits[num_digits_whole_no_pad..]); } } @@ -862,10 +862,10 @@ pub fn formatBytes( value: var, options: FormatOptions, comptime radix: usize, - out_stream: var, + writer: var, ) !void { if (value == 0) { - return out_stream.writeAll("0B"); + return writer.writeAll("0B"); } const is_float = comptime std.meta.trait.is(.Float)(@TypeOf(value)); @@ -885,10 +885,10 @@ pub fn formatBytes( else => unreachable, }; - try formatFloatDecimal(new_value, options, out_stream); + try formatFloatDecimal(new_value, options, writer); if (suffix == ' ') { - return out_stream.writeAll("B"); + return writer.writeAll("B"); } const buf = switch (radix) { @@ -896,7 +896,7 @@ pub fn formatBytes( 1024 => &[_]u8{ suffix, 'i', 'B' }, else => unreachable, }; - return out_stream.writeAll(buf); + return writer.writeAll(buf); } pub fn formatInt( @@ -904,7 +904,7 @@ pub fn formatInt( base: u8, uppercase: bool, options: FormatOptions, - out_stream: var, + writer: var, ) !void { const int_value = if (@TypeOf(value) == comptime_int) blk: { const Int = math.IntFittingRange(value, value); @@ -913,9 +913,9 @@ pub fn formatInt( value; if (@TypeOf(int_value).is_signed) { - return formatIntSigned(int_value, base, uppercase, options, out_stream); + return formatIntSigned(int_value, base, uppercase, options, writer); } else { - return formatIntUnsigned(int_value, base, uppercase, options, out_stream); + return formatIntUnsigned(int_value, base, uppercase, options, writer); } } @@ -924,7 +924,7 @@ fn formatIntSigned( base: u8, uppercase: bool, options: FormatOptions, - out_stream: var, + writer: var, ) !void { const new_options = FormatOptions{ .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, @@ -934,15 +934,15 @@ fn formatIntSigned( const bit_count = @typeInfo(@TypeOf(value)).Int.bits; const Uint = std.meta.Int(false, bit_count); if (value < 0) { - try out_stream.writeAll("-"); + try writer.writeAll("-"); const new_value = math.absCast(value); - return formatIntUnsigned(new_value, base, uppercase, new_options, out_stream); + return formatIntUnsigned(new_value, base, uppercase, new_options, writer); } else if (options.width == null or options.width.? == 0) { - return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, out_stream); + return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, writer); } else { - try out_stream.writeAll("+"); + try writer.writeAll("+"); const new_value = @intCast(Uint, value); - return formatIntUnsigned(new_value, base, uppercase, new_options, out_stream); + return formatIntUnsigned(new_value, base, uppercase, new_options, writer); } } @@ -951,7 +951,7 @@ fn formatIntUnsigned( base: u8, uppercase: bool, options: FormatOptions, - out_stream: var, + writer: var, ) !void { assert(base >= 2); var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined; @@ -976,22 +976,22 @@ fn formatIntUnsigned( const zero_byte: u8 = options.fill; var leftover_padding = padding - index; while (true) { - try out_stream.writeAll(@as(*const [1]u8, &zero_byte)[0..]); + try writer.writeAll(@as(*const [1]u8, &zero_byte)[0..]); leftover_padding -= 1; if (leftover_padding == 0) break; } mem.set(u8, buf[0..index], options.fill); - return out_stream.writeAll(&buf); + return writer.writeAll(&buf); } else { const padded_buf = buf[index - padding ..]; mem.set(u8, padded_buf[0..padding], options.fill); - return out_stream.writeAll(padded_buf); + return writer.writeAll(padded_buf); } } pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize { var fbs = std.io.fixedBufferStream(out_buf); - formatInt(value, base, uppercase, options, fbs.outStream()) catch unreachable; + formatInt(value, base, uppercase, options, fbs.writer()) catch unreachable; return fbs.pos; } @@ -1098,15 +1098,15 @@ pub const BufPrintError = error{ }; pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: var) BufPrintError![]u8 { var fbs = std.io.fixedBufferStream(buf); - try format(fbs.outStream(), fmt, args); + try format(fbs.writer(), fmt, args); return fbs.getWritten(); } // Count the characters needed for format. Useful for preallocating memory pub fn count(comptime fmt: []const u8, args: var) u64 { - var counting_stream = std.io.countingOutStream(std.io.null_out_stream); - format(counting_stream.outStream(), fmt, args) catch |err| switch (err) {}; - return counting_stream.bytes_written; + var counting_writer = std.io.countingWriter(std.io.null_writer); + format(counting_writer.writer(), fmt, args) catch |err| switch (err) {}; + return counting_writer.bytes_written; } pub const AllocPrintError = error{OutOfMemory}; @@ -1215,15 +1215,15 @@ test "buffer" { { var buf1: [32]u8 = undefined; var fbs = std.io.fixedBufferStream(&buf1); - try formatType(1234, "", FormatOptions{}, fbs.outStream(), default_max_depth); + try formatType(1234, "", FormatOptions{}, fbs.writer(), default_max_depth); std.testing.expect(mem.eql(u8, fbs.getWritten(), "1234")); fbs.reset(); - try formatType('a', "c", FormatOptions{}, fbs.outStream(), default_max_depth); + try formatType('a', "c", FormatOptions{}, fbs.writer(), default_max_depth); std.testing.expect(mem.eql(u8, fbs.getWritten(), "a")); fbs.reset(); - try formatType(0b1100, "b", FormatOptions{}, fbs.outStream(), default_max_depth); + try formatType(0b1100, "b", FormatOptions{}, fbs.writer(), default_max_depth); std.testing.expect(mem.eql(u8, fbs.getWritten(), "1100")); } } @@ -1413,12 +1413,12 @@ test "custom" { self: SelfType, comptime fmt: []const u8, options: FormatOptions, - out_stream: var, + writer: var, ) !void { if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { - return std.fmt.format(out_stream, "({d:.3},{d:.3})", .{ self.x, self.y }); + return std.fmt.format(writer, "({d:.3},{d:.3})", .{ self.x, self.y }); } else if (comptime std.mem.eql(u8, fmt, "d")) { - return std.fmt.format(out_stream, "{d:.3}x{d:.3}", .{ self.x, self.y }); + return std.fmt.format(writer, "{d:.3}x{d:.3}", .{ self.x, self.y }); } else { @compileError("Unknown format character: '" ++ fmt ++ "'"); } @@ -1604,7 +1604,7 @@ test "formatIntValue with comptime_int" { var buf: [20]u8 = undefined; var fbs = std.io.fixedBufferStream(&buf); - try formatIntValue(value, "", FormatOptions{}, fbs.outStream()); + try formatIntValue(value, "", FormatOptions{}, fbs.writer()); std.testing.expect(mem.eql(u8, fbs.getWritten(), "123456789123456789")); } @@ -1613,7 +1613,7 @@ test "formatFloatValue with comptime_float" { var buf: [20]u8 = undefined; var fbs = std.io.fixedBufferStream(&buf); - try formatFloatValue(value, "", FormatOptions{}, fbs.outStream()); + try formatFloatValue(value, "", FormatOptions{}, fbs.writer()); std.testing.expect(mem.eql(u8, fbs.getWritten(), "1.0e+00")); try testFmt("1.0e+00", "{}", .{value}); @@ -1630,10 +1630,10 @@ test "formatType max_depth" { self: SelfType, comptime fmt: []const u8, options: FormatOptions, - out_stream: var, + writer: var, ) !void { if (fmt.len == 0) { - return std.fmt.format(out_stream, "({d:.3},{d:.3})", .{ self.x, self.y }); + return std.fmt.format(writer, "({d:.3},{d:.3})", .{ self.x, self.y }); } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } @@ -1669,19 +1669,19 @@ test "formatType max_depth" { var buf: [1000]u8 = undefined; var fbs = std.io.fixedBufferStream(&buf); - try formatType(inst, "", FormatOptions{}, fbs.outStream(), 0); + try formatType(inst, "", FormatOptions{}, fbs.writer(), 0); std.testing.expect(mem.eql(u8, fbs.getWritten(), "S{ ... }")); fbs.reset(); - try formatType(inst, "", FormatOptions{}, fbs.outStream(), 1); + try formatType(inst, "", FormatOptions{}, fbs.writer(), 1); std.testing.expect(mem.eql(u8, fbs.getWritten(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); fbs.reset(); - try formatType(inst, "", FormatOptions{}, fbs.outStream(), 2); + try formatType(inst, "", FormatOptions{}, fbs.writer(), 2); std.testing.expect(mem.eql(u8, fbs.getWritten(), "S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }")); fbs.reset(); - try formatType(inst, "", FormatOptions{}, fbs.outStream(), 3); + try formatType(inst, "", FormatOptions{}, fbs.writer(), 3); std.testing.expect(mem.eql(u8, fbs.getWritten(), "S{ .a = S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ .ptr = TU{ ... } } }, .e = E.Two, .vec = (10.200,2.220) }")); } -- 2.54.0 From bc0ca73887d12ff71e12ad473bab22631a29b1aa Mon Sep 17 00:00:00 2001 From: DrDeano Date: Sat, 20 Jun 2020 15:12:52 +0100 Subject: [PATCH 067/295] Moved the check for formatting a directory The original check for a directory was for the `readAllAlloc` so move the check from open to read. This in turn fixes the fmt step in the build script for directories. --- src-self-hosted/main.zig | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 6c13f8ab004190843d65b0baae5238839deb25b0..3c8107043962e7ee378634ebb843c17f0859494f 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -684,8 +684,21 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { if (fmt.seen.exists(real_path)) return; try fmt.seen.put(real_path); - const source_file = fs.cwd().openFile(real_path, .{}) catch |err| switch (err) { - error.IsDir, error.AccessDenied => { + const source_file = fs.cwd().openFile(real_path, .{}) catch |err| { + std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); + fmt.any_error = true; + return; + }; + defer source_file.close(); + + const stat = source_file.stat() catch |err| { + std.debug.warn("unable to stat '{}': {}\n", .{ file_path, err }); + fmt.any_error = true; + return; + }; + + const source_code = source_file.readAllAlloc(fmt.gpa, stat.size, max_src_size) catch |err| switch (err) { + error.IsDir => { var dir = try fs.cwd().openDir(file_path, .{ .iterate = true }); defer dir.close(); @@ -700,24 +713,11 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { return; }, else => { - std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); + std.debug.warn("unable to read '{}': {}\n", .{ file_path, err }); fmt.any_error = true; return; }, }; - defer source_file.close(); - - const stat = source_file.stat() catch |err| { - std.debug.warn("unable to stat '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }; - - const source_code = source_file.readAllAlloc(fmt.gpa, stat.size, max_src_size) catch |err| { - std.debug.warn("unable to read '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }; defer fmt.gpa.free(source_code); const tree = std.zig.parse(fmt.gpa, source_code) catch |err| { -- 2.54.0 From d87cd06296a759ce398b50a437b8a1444413c6be Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 20 Jun 2020 18:27:37 -0400 Subject: [PATCH 068/295] rework zig fmt to use less syscalls and open fds * `std.fs.Dir.Entry.Kind` is moved to `std.fs.File.Kind` * `std.fs.File.Stat` gains the `kind` field, so performing a stat() on a File now tells what kind of file it is. On Windows this only will distinguish between directories and files. * rework zig fmt logic so that in the case of opening a file and discovering it to be a directory, it closes the file descriptor before re-opening it with O_DIRECTORY, using fewer simultaneous open file descriptors when walking a directory tree. * rework zig fmt logic so that it pays attention to the kind of directory entries, and when it sees a sub-directory it attempts to open it as a directory rather than a file, reducing the number of open() syscalls when walking a directory tree. --- lib/std/fs.zig | 12 +----- lib/std/fs/file.zig | 26 +++++++++++- src-self-hosted/main.zig | 87 ++++++++++++++++++++++++---------------- 3 files changed, 79 insertions(+), 46 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 3ff4819ac5cb29be7c973d5d8e1d7166dd7957e9..fa782b14c00b680d3f70cae363c04d9172f901c4 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -261,17 +261,7 @@ pub const Dir = struct { name: []const u8, kind: Kind, - pub const Kind = enum { - BlockDevice, - CharacterDevice, - Directory, - NamedPipe, - SymLink, - File, - UnixDomainSocket, - Whiteout, - Unknown, - }; + pub const Kind = File.Kind; }; const IteratorError = error{AccessDenied} || os.UnexpectedError; diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 0a3c1b5ab73500838b0b7435b4a0f17f94776bbb..f0304c332aae4718a15a9eff2f3ff04518b30f2e 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -29,6 +29,18 @@ pub const File = struct { pub const Mode = os.mode_t; pub const INode = os.ino_t; + pub const Kind = enum { + BlockDevice, + CharacterDevice, + Directory, + NamedPipe, + SymLink, + File, + UnixDomainSocket, + Whiteout, + Unknown, + }; + pub const default_mode = switch (builtin.os.tag) { .windows => 0, .wasi => 0, @@ -219,13 +231,14 @@ pub const File = struct { /// unique across time, as some file systems may reuse an inode after its file has been deleted. /// Some systems may change the inode of a file over time. /// - /// On Linux, the inode _is_ structure that stores the metadata, and the inode _number_ is what + /// On Linux, the inode is a structure that stores the metadata, and the inode _number_ is what /// you see here: the index number of the inode. /// /// The FileIndex on Windows is similar. It is a number for a file that is unique to each filesystem. inode: INode, size: u64, mode: Mode, + kind: Kind, /// Access time in nanoseconds, relative to UTC 1970-01-01. atime: i128, @@ -254,6 +267,7 @@ pub const File = struct { .inode = info.InternalInformation.IndexNumber, .size = @bitCast(u64, info.StandardInformation.EndOfFile), .mode = 0, + .kind = if (info.StandardInformation.Directory == 0) .File else .Directory, .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), .ctime = windows.fromSysTime(info.BasicInformation.CreationTime), @@ -268,6 +282,16 @@ pub const File = struct { .inode = st.ino, .size = @bitCast(u64, st.size), .mode = st.mode, + .kind = switch (st.mode & os.S_IFMT) { + os.S_IFBLK => .BlockDevice, + os.S_IFCHR => .CharacterDevice, + os.S_IFDIR => .Directory, + os.S_IFIFO => .NamedPipe, + os.S_IFLNK => .SymLink, + os.S_IFREG => .File, + os.S_IFSOCK => .UnixDomainSocket, + else => .Unknown, + }, .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, .ctime = @as(i128, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 3c8107043962e7ee378634ebb843c17f0859494f..ee4cf9bbe3fa47fac3ee3085ef2f847b2d8a653a 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -670,11 +670,12 @@ const FmtError = error{ ReadOnlyFileSystem, LinkQuotaExceeded, FileBusy, + EndOfStream, } || fs.File.OpenError; fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { // get the real path here to avoid Windows failing on relative file paths with . or .. in them - var real_path = fs.realpathAlloc(fmt.gpa, file_path) catch |err| { + const real_path = fs.realpathAlloc(fmt.gpa, file_path) catch |err| { std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); fmt.any_error = true; return; @@ -684,47 +685,65 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { if (fmt.seen.exists(real_path)) return; try fmt.seen.put(real_path); - const source_file = fs.cwd().openFile(real_path, .{}) catch |err| { - std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; + fmtPathFile(fmt, file_path, check_mode, real_path) catch |err| switch (err) { + error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, real_path), + else => { + std.debug.warn("unable to format '{}': {}\n", .{ file_path, err }); + fmt.any_error = true; + return; + }, }; +} + +fn fmtPathDir(fmt: *Fmt, file_path: []const u8, check_mode: bool, parent_real_path: []const u8) FmtError!void { + var dir = try fs.cwd().openDir(parent_real_path, .{ .iterate = true }); + defer dir.close(); + + var dir_it = dir.iterate(); + while (try dir_it.next()) |entry| { + const is_dir = entry.kind == .Directory; + if (is_dir or mem.endsWith(u8, entry.name, ".zig")) { + const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name }); + const sub_real_path = fs.realpathAlloc(fmt.gpa, full_path) catch |err| { + std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); + fmt.any_error = true; + return; + }; + defer fmt.gpa.free(sub_real_path); + + if (fmt.seen.exists(sub_real_path)) return; + try fmt.seen.put(sub_real_path); + + if (is_dir) { + try fmtPathDir(fmt, full_path, check_mode, sub_real_path); + } else { + fmtPathFile(fmt, full_path, check_mode, sub_real_path) catch |err| { + std.debug.warn("unable to format '{}': {}\n", .{ full_path, err }); + fmt.any_error = true; + return; + }; + } + } + } +} + +fn fmtPathFile(fmt: *Fmt, file_path: []const u8, check_mode: bool, real_path: []const u8) FmtError!void { + const source_file = try fs.cwd().openFile(real_path, .{}); defer source_file.close(); - const stat = source_file.stat() catch |err| { - std.debug.warn("unable to stat '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }; + const stat = try source_file.stat(); + + if (stat.kind == .Directory) + return error.IsDir; const source_code = source_file.readAllAlloc(fmt.gpa, stat.size, max_src_size) catch |err| switch (err) { - error.IsDir => { - var dir = try fs.cwd().openDir(file_path, .{ .iterate = true }); - defer dir.close(); - - var dir_it = dir.iterate(); - - while (try dir_it.next()) |entry| { - if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) { - const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name }); - try fmtPath(fmt, full_path, check_mode); - } - } - return; - }, - else => { - std.debug.warn("unable to read '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }, + error.ConnectionResetByPeer => unreachable, + error.ConnectionTimedOut => unreachable, + else => |e| return e, }; defer fmt.gpa.free(source_code); - const tree = std.zig.parse(fmt.gpa, source_code) catch |err| { - std.debug.warn("error parsing file '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }; + const tree = try std.zig.parse(fmt.gpa, source_code); defer tree.deinit(); for (tree.errors) |parse_error| { -- 2.54.0 From da549a72e19d4f24520ca151bbd4cd0e74dc752c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 20 Jun 2020 18:39:15 -0400 Subject: [PATCH 069/295] zig fmt --- lib/std/array_list.zig | 2 +- lib/std/c/tokenizer.zig | 100 ++++++++++++++--------------- lib/std/io/buffered_out_stream.zig | 2 +- lib/std/json.zig | 5 +- lib/std/os/windows/ws2_32.zig | 18 +++--- lib/std/zig/parse.zig | 1 - 6 files changed, 63 insertions(+), 65 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index d42a3c3d73c68bdb37f0faf8f7da2b1062e79f62..452ebe2124da40d9ae2aece7a61bb956722d03b3 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -162,7 +162,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { mem.copy(T, self.items[oldlen..], items); } - pub usingnamespace if (T != u8) struct { } else struct { + pub usingnamespace if (T != u8) struct {} else struct { pub const Writer = std.io.Writer(*Self, error{OutOfMemory}, appendWrite); /// Initializes a Writer which will append to the list. diff --git a/lib/std/c/tokenizer.zig b/lib/std/c/tokenizer.zig index 198c69e2a795aaae162d68d943d0500ef4229b3e..f3fc8b81079670fa5bde2468d05d5c3ad30b391d 100644 --- a/lib/std/c/tokenizer.zig +++ b/lib/std/c/tokenizer.zig @@ -278,62 +278,62 @@ pub const Token = struct { // TODO extensions pub const keywords = std.ComptimeStringMap(Id, .{ - .{"auto", .Keyword_auto}, - .{"break", .Keyword_break}, - .{"case", .Keyword_case}, - .{"char", .Keyword_char}, - .{"const", .Keyword_const}, - .{"continue", .Keyword_continue}, - .{"default", .Keyword_default}, - .{"do", .Keyword_do}, - .{"double", .Keyword_double}, - .{"else", .Keyword_else}, - .{"enum", .Keyword_enum}, - .{"extern", .Keyword_extern}, - .{"float", .Keyword_float}, - .{"for", .Keyword_for}, - .{"goto", .Keyword_goto}, - .{"if", .Keyword_if}, - .{"int", .Keyword_int}, - .{"long", .Keyword_long}, - .{"register", .Keyword_register}, - .{"return", .Keyword_return}, - .{"short", .Keyword_short}, - .{"signed", .Keyword_signed}, - .{"sizeof", .Keyword_sizeof}, - .{"static", .Keyword_static}, - .{"struct", .Keyword_struct}, - .{"switch", .Keyword_switch}, - .{"typedef", .Keyword_typedef}, - .{"union", .Keyword_union}, - .{"unsigned", .Keyword_unsigned}, - .{"void", .Keyword_void}, - .{"volatile", .Keyword_volatile}, - .{"while", .Keyword_while}, + .{ "auto", .Keyword_auto }, + .{ "break", .Keyword_break }, + .{ "case", .Keyword_case }, + .{ "char", .Keyword_char }, + .{ "const", .Keyword_const }, + .{ "continue", .Keyword_continue }, + .{ "default", .Keyword_default }, + .{ "do", .Keyword_do }, + .{ "double", .Keyword_double }, + .{ "else", .Keyword_else }, + .{ "enum", .Keyword_enum }, + .{ "extern", .Keyword_extern }, + .{ "float", .Keyword_float }, + .{ "for", .Keyword_for }, + .{ "goto", .Keyword_goto }, + .{ "if", .Keyword_if }, + .{ "int", .Keyword_int }, + .{ "long", .Keyword_long }, + .{ "register", .Keyword_register }, + .{ "return", .Keyword_return }, + .{ "short", .Keyword_short }, + .{ "signed", .Keyword_signed }, + .{ "sizeof", .Keyword_sizeof }, + .{ "static", .Keyword_static }, + .{ "struct", .Keyword_struct }, + .{ "switch", .Keyword_switch }, + .{ "typedef", .Keyword_typedef }, + .{ "union", .Keyword_union }, + .{ "unsigned", .Keyword_unsigned }, + .{ "void", .Keyword_void }, + .{ "volatile", .Keyword_volatile }, + .{ "while", .Keyword_while }, // ISO C99 - .{"_Bool", .Keyword_bool}, - .{"_Complex", .Keyword_complex}, - .{"_Imaginary", .Keyword_imaginary}, - .{"inline", .Keyword_inline}, - .{"restrict", .Keyword_restrict}, + .{ "_Bool", .Keyword_bool }, + .{ "_Complex", .Keyword_complex }, + .{ "_Imaginary", .Keyword_imaginary }, + .{ "inline", .Keyword_inline }, + .{ "restrict", .Keyword_restrict }, // ISO C11 - .{"_Alignas", .Keyword_alignas}, - .{"_Alignof", .Keyword_alignof}, - .{"_Atomic", .Keyword_atomic}, - .{"_Generic", .Keyword_generic}, - .{"_Noreturn", .Keyword_noreturn}, - .{"_Static_assert", .Keyword_static_assert}, - .{"_Thread_local", .Keyword_thread_local}, + .{ "_Alignas", .Keyword_alignas }, + .{ "_Alignof", .Keyword_alignof }, + .{ "_Atomic", .Keyword_atomic }, + .{ "_Generic", .Keyword_generic }, + .{ "_Noreturn", .Keyword_noreturn }, + .{ "_Static_assert", .Keyword_static_assert }, + .{ "_Thread_local", .Keyword_thread_local }, // Preprocessor directives - .{"include", .Keyword_include}, - .{"define", .Keyword_define}, - .{"ifdef", .Keyword_ifdef}, - .{"ifndef", .Keyword_ifndef}, - .{"error", .Keyword_error}, - .{"pragma", .Keyword_pragma}, + .{ "include", .Keyword_include }, + .{ "define", .Keyword_define }, + .{ "ifdef", .Keyword_ifdef }, + .{ "ifndef", .Keyword_ifndef }, + .{ "error", .Keyword_error }, + .{ "pragma", .Keyword_pragma }, }); // TODO do this in the preprocessor diff --git a/lib/std/io/buffered_out_stream.zig b/lib/std/io/buffered_out_stream.zig index 6b8ede54893a40ba2584623c017e6375d4f86149..6f9efa9575d330b72dac730fc348ae3c3c529878 100644 --- a/lib/std/io/buffered_out_stream.zig +++ b/lib/std/io/buffered_out_stream.zig @@ -2,4 +2,4 @@ pub const BufferedOutStream = @import("./buffered_writer.zig").BufferedWriter; /// Deprecated: use `std.io.buffered_writer.bufferedWriter` -pub const bufferedOutStream = @import("./buffered_writer.zig").bufferedWriter +pub const bufferedOutStream = @import("./buffered_writer.zig").bufferedWriter; diff --git a/lib/std/json.zig b/lib/std/json.zig index 4acdbc7d1ad35a4cadb4f46198ba0cfaa239e79a..ae10dc9559410c4ae4ba1dd4270037ceb8210b66 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -2576,8 +2576,8 @@ pub fn stringify( }, .Array => return stringify(&value, options, out_stream), .Vector => |info| { - const array: [info.len]info.child = value; - return stringify(&array, options, out_stream); + const array: [info.len]info.child = value; + return stringify(&array, options, out_stream); }, else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), } @@ -2770,4 +2770,3 @@ test "stringify struct with custom stringifier" { test "stringify vector" { try teststringify("[1,1]", @splat(2, @as(u32, 1)), StringifyOptions{}); } - diff --git a/lib/std/os/windows/ws2_32.zig b/lib/std/os/windows/ws2_32.zig index 8b16f543612df2601ae59de2e69de9914b182093..1e36a72038d1afd77b5d059b0d9ad832ce59bf4f 100644 --- a/lib/std/os/windows/ws2_32.zig +++ b/lib/std/os/windows/ws2_32.zig @@ -163,16 +163,16 @@ pub const IPPROTO_UDP = 17; pub const IPPROTO_ICMPV6 = 58; pub const IPPROTO_RM = 113; -pub const AI_PASSIVE = 0x00001; -pub const AI_CANONNAME = 0x00002; -pub const AI_NUMERICHOST = 0x00004; -pub const AI_NUMERICSERV = 0x00008; -pub const AI_ADDRCONFIG = 0x00400; -pub const AI_V4MAPPED = 0x00800; -pub const AI_NON_AUTHORITATIVE = 0x04000; -pub const AI_SECURE = 0x08000; +pub const AI_PASSIVE = 0x00001; +pub const AI_CANONNAME = 0x00002; +pub const AI_NUMERICHOST = 0x00004; +pub const AI_NUMERICSERV = 0x00008; +pub const AI_ADDRCONFIG = 0x00400; +pub const AI_V4MAPPED = 0x00800; +pub const AI_NON_AUTHORITATIVE = 0x04000; +pub const AI_SECURE = 0x08000; pub const AI_RETURN_PREFERRED_NAMES = 0x10000; -pub const AI_DISABLE_IDN_ENCODING = 0x80000; +pub const AI_DISABLE_IDN_ENCODING = 0x80000; pub const FIONBIO = -2147195266; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 2a3ff9d9de8f379be23e4ae62243ecf9a8cedc9e..e6cd7a8b6d6b66c68e62c7465bf6efcacde5b5dd 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -937,7 +937,6 @@ const Parser = struct { return node; } - while_prefix.body = try p.expectNode(parseAssignExpr, .{ .ExpectedBlockOrAssignment = .{ .token = p.tok_i }, }); -- 2.54.0 From 0a9672fb86b84658f8780f57e769be45e41f3034 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 20 Jun 2020 19:46:14 -0400 Subject: [PATCH 070/295] rework zig fmt to avoid unnecessary realpath() calls * add `std.fs.Dir.stat` * zig fmt checks for sym link loops using inodes instead of using realpath --- lib/std/fs.zig | 11 +++++++ src-self-hosted/main.zig | 70 ++++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index fa782b14c00b680d3f70cae363c04d9172f901c4..dfd58de50074dea72c39ef05462c5ce259769558 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1545,6 +1545,17 @@ pub const Dir = struct { return AtomicFile.init(dest_path, options.mode, self, false); } } + + pub const Stat = File.Stat; + pub const StatError = File.StatError; + + pub fn stat(self: Dir) StatError!Stat { + const file: File = .{ + .handle = self.fd, + .capable_io_mode = .blocking, + }; + return file.stat(); + } }; /// Returns an handle to the current working directory. It is not opened with iteration capability. diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index ee4cf9bbe3fa47fac3ee3085ef2f847b2d8a653a..f322e455a842e82e88ce21bb57a7f00951cefa18 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -547,7 +547,7 @@ const Fmt = struct { color: Color, gpa: *Allocator, - const SeenMap = std.BufSet; + const SeenMap = std.AutoHashMap(fs.File.INode, void); }; pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { @@ -644,7 +644,14 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { }; for (input_files.span()) |file_path| { - try fmtPath(&fmt, file_path, check_flag); + // Get the real path here to avoid Windows failing on relative file paths with . or .. in them. + const real_path = fs.realpathAlloc(gpa, file_path) catch |err| { + std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); + process.exit(1); + }; + defer gpa.free(real_path); + + try fmtPath(&fmt, file_path, check_flag, fs.cwd(), real_path); } if (fmt.any_error) { process.exit(1); @@ -673,20 +680,9 @@ const FmtError = error{ EndOfStream, } || fs.File.OpenError; -fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { - // get the real path here to avoid Windows failing on relative file paths with . or .. in them - const real_path = fs.realpathAlloc(fmt.gpa, file_path) catch |err| { - std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }; - defer fmt.gpa.free(real_path); - - if (fmt.seen.exists(real_path)) return; - try fmt.seen.put(real_path); - - fmtPathFile(fmt, file_path, check_mode, real_path) catch |err| switch (err) { - error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, real_path), +fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void { + fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { + error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), else => { std.debug.warn("unable to format '{}': {}\n", .{ file_path, err }); fmt.any_error = true; @@ -695,29 +691,30 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void { }; } -fn fmtPathDir(fmt: *Fmt, file_path: []const u8, check_mode: bool, parent_real_path: []const u8) FmtError!void { - var dir = try fs.cwd().openDir(parent_real_path, .{ .iterate = true }); +fn fmtPathDir( + fmt: *Fmt, + file_path: []const u8, + check_mode: bool, + parent_dir: fs.Dir, + parent_sub_path: []const u8, +) FmtError!void { + var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true }); defer dir.close(); + const stat = try dir.stat(); + if (try fmt.seen.put(stat.inode, {})) |_| return; + var dir_it = dir.iterate(); while (try dir_it.next()) |entry| { const is_dir = entry.kind == .Directory; if (is_dir or mem.endsWith(u8, entry.name, ".zig")) { const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name }); - const sub_real_path = fs.realpathAlloc(fmt.gpa, full_path) catch |err| { - std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); - fmt.any_error = true; - return; - }; - defer fmt.gpa.free(sub_real_path); - - if (fmt.seen.exists(sub_real_path)) return; - try fmt.seen.put(sub_real_path); + defer fmt.gpa.free(full_path); if (is_dir) { - try fmtPathDir(fmt, full_path, check_mode, sub_real_path); + try fmtPathDir(fmt, full_path, check_mode, dir, entry.name); } else { - fmtPathFile(fmt, full_path, check_mode, sub_real_path) catch |err| { + fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| { std.debug.warn("unable to format '{}': {}\n", .{ full_path, err }); fmt.any_error = true; return; @@ -727,8 +724,14 @@ fn fmtPathDir(fmt: *Fmt, file_path: []const u8, check_mode: bool, parent_real_pa } } -fn fmtPathFile(fmt: *Fmt, file_path: []const u8, check_mode: bool, real_path: []const u8) FmtError!void { - const source_file = try fs.cwd().openFile(real_path, .{}); +fn fmtPathFile( + fmt: *Fmt, + file_path: []const u8, + check_mode: bool, + dir: fs.Dir, + sub_path: []const u8, +) FmtError!void { + const source_file = try dir.openFile(sub_path, .{}); defer source_file.close(); const stat = try source_file.stat(); @@ -743,6 +746,9 @@ fn fmtPathFile(fmt: *Fmt, file_path: []const u8, check_mode: bool, real_path: [] }; defer fmt.gpa.free(source_code); + // Add to set after no longer possible to get error.IsDir. + if (try fmt.seen.put(stat.inode, {})) |_| return; + const tree = try std.zig.parse(fmt.gpa, source_code); defer tree.deinit(); @@ -761,7 +767,7 @@ fn fmtPathFile(fmt: *Fmt, file_path: []const u8, check_mode: bool, real_path: [] fmt.any_error = true; } } else { - const baf = try io.BufferedAtomicFile.create(fmt.gpa, fs.cwd(), real_path, .{ .mode = stat.mode }); + const baf = try io.BufferedAtomicFile.create(fmt.gpa, dir, sub_path, .{ .mode = stat.mode }); defer baf.destroy(); const anything_changed = try std.zig.render(fmt.gpa, baf.stream(), tree); -- 2.54.0 From 64dfd1883eb2b9ab175af9b07c84bd4b53b7e904 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 20 Jun 2020 20:14:33 -0400 Subject: [PATCH 071/295] zig fmt: avoid unnecessary file system access zig fmt previously would write a temp file, and then either rename it into place if necessary, or unlink it if nothing was changed. Now zig fmt renders into a memory buffer, and only writes the temp file and renames it into place if anything changed. Based on the performance testing I did this actually did not have much of an impact, however it's likely that on other operating systems and other hard drives this could make a big difference. --- src-self-hosted/main.zig | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index f322e455a842e82e88ce21bb57a7f00951cefa18..29930270ebf01f46d6a08704170968d25f466bfc 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -546,6 +546,7 @@ const Fmt = struct { any_error: bool, color: Color, gpa: *Allocator, + out_buffer: std.ArrayList(u8), const SeenMap = std.AutoHashMap(fs.File.INode, void); }; @@ -641,7 +642,10 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { .seen = Fmt.SeenMap.init(gpa), .any_error = false, .color = color, + .out_buffer = std.ArrayList(u8).init(gpa), }; + defer fmt.seen.deinit(); + defer fmt.out_buffer.deinit(); for (input_files.span()) |file_path| { // Get the real path here to avoid Windows failing on relative file paths with . or .. in them. @@ -767,14 +771,19 @@ fn fmtPathFile( fmt.any_error = true; } } else { - const baf = try io.BufferedAtomicFile.create(fmt.gpa, dir, sub_path, .{ .mode = stat.mode }); - defer baf.destroy(); + // As a heuristic, we make enough capacity for the same as the input source. + try fmt.out_buffer.ensureCapacity(source_code.len); + fmt.out_buffer.items.len = 0; + const anything_changed = try std.zig.render(fmt.gpa, fmt.out_buffer.writer(), tree); + if (!anything_changed) + return; // Good thing we didn't waste any file system access on this. - const anything_changed = try std.zig.render(fmt.gpa, baf.stream(), tree); - if (anything_changed) { - std.debug.warn("{}\n", .{file_path}); - try baf.finish(); - } + var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode }); + defer af.deinit(); + + try af.file.writeAll(fmt.out_buffer.items); + try af.finish(); + std.debug.warn("{}\n", .{file_path}); } } -- 2.54.0 From 225f1968426d333834465dae526b3d2ae242cd8f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 20 Jun 2020 20:43:56 -0400 Subject: [PATCH 072/295] std.fs: fix shadowing `stat` with a local variable --- lib/std/fs.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index dfd58de50074dea72c39ef05462c5ce259769558..10422b9d546cd5a23a40c12603c62dc93e5ffddc 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1517,9 +1517,9 @@ pub const Dir = struct { var size: ?u64 = null; const mode = options.override_mode orelse blk: { - const stat = try in_file.stat(); - size = stat.size; - break :blk stat.mode; + const st = try in_file.stat(); + size = st.size; + break :blk st.mode; }; var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode }); -- 2.54.0 From faf783e5959a09fb2a1680f8bb7558db96dcbed9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 20 Jun 2020 22:09:47 -0400 Subject: [PATCH 073/295] implement new stat functionality for WASI --- lib/std/fs/file.zig | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index f0304c332aae4718a15a9eff2f3ff04518b30f2e..160f8314b9c6b378045ee29c24bd5b7ea0fbacc2 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -282,15 +282,26 @@ pub const File = struct { .inode = st.ino, .size = @bitCast(u64, st.size), .mode = st.mode, - .kind = switch (st.mode & os.S_IFMT) { - os.S_IFBLK => .BlockDevice, - os.S_IFCHR => .CharacterDevice, - os.S_IFDIR => .Directory, - os.S_IFIFO => .NamedPipe, - os.S_IFLNK => .SymLink, - os.S_IFREG => .File, - os.S_IFSOCK => .UnixDomainSocket, - else => .Unknown, + .kind = switch (builtin.os.tag) { + .wasi => switch (st.filetype) { + os.FILETYPE_BLOCK_DEVICE => Kind.BlockDevice, + os.FILETYPE_CHARACTER_DEVICE => Kind.CharacterDevice, + os.FILETYPE_DIRECTORY => Kind.Directory, + os.FILETYPE_SYMBOLIC_LINK => Kind.SymLink, + os.FILETYPE_REGULAR_FILE => Kind.File, + os.FILETYPE_SOCKET_STREAM, os.FILETYPE_SOCKET_DGRAM => Kind.UnixDomainSocket, + else => Kind.Unknown, + }, + else => switch (st.mode & os.S_IFMT) { + os.S_IFBLK => Kind.BlockDevice, + os.S_IFCHR => Kind.CharacterDevice, + os.S_IFDIR => Kind.Directory, + os.S_IFIFO => Kind.NamedPipe, + os.S_IFLNK => Kind.SymLink, + os.S_IFREG => Kind.File, + os.S_IFSOCK => Kind.UnixDomainSocket, + else => Kind.Unknown, + }, }, .atime = @as(i128, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, .mtime = @as(i128, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, -- 2.54.0 From 56220449abcbbb0e308e0eadeb2e0e7c8a9e4cba Mon Sep 17 00:00:00 2001 From: Nameless Date: Fri, 19 Jun 2020 18:25:22 -0500 Subject: [PATCH 074/295] Add errors to windows.WSAStartup and WSACleanup --- lib/std/os/windows.zig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 953a16a2eae294edc4dfd06cbb898f4d9daf6b4a..1ed1ef1f54ca39357aefb1f553f18c582fc03c33 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -901,7 +901,13 @@ pub fn WSAStartup(majorVersion: u8, minorVersion: u8) !ws2_32.WSADATA { var wsadata: ws2_32.WSADATA = undefined; return switch (ws2_32.WSAStartup((@as(WORD, minorVersion) << 8) | majorVersion, &wsadata)) { 0 => wsadata, - else => |err| unexpectedWSAError(@intToEnum(ws2_32.WinsockError, @intCast(u16, err))), + else => |err_int| switch (@intToEnum(ws2_32.WinsockError, @intCast(u16, err_int))) { + .WSASYSNOTREADY => return error.SystemNotAvailable, + .WSAVERNOTSUPPORTED => return error.VersionNotSupported, + .WSAEINPROGRESS => return error.BlockingOperationInProgress, + .WSAEPROCLIM => return error.SystemResources, + else => |err| return unexpectedWSAError(err), + }, }; } @@ -909,6 +915,9 @@ pub fn WSACleanup() !void { return switch (ws2_32.WSACleanup()) { 0 => {}, ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) { + .WSANOTINITIALISED => return error.NotInitialized, + .WSAENETDOWN => return error.NetworkNotAvailable, + .WSAEINPROGRESS => return error.BlockingOperationInProgress, else => |err| return unexpectedWSAError(err), }, else => unreachable, -- 2.54.0 From ca9d8a1337554a5e117a59d96345d763ef1ad18e Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Jun 2020 20:49:45 -0700 Subject: [PATCH 075/295] Add zig fmt test to cli tests for both files and directories Should catch basic `zig fmt` regressions that were previously going uncaught and breaking things --- test/cli.zig | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/cli.zig b/test/cli.zig index 3af78e185700af59594eec8068daf49475d43343..4c28827ba88ef3b60589bc6904f935ef42f5b04a 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -34,6 +34,7 @@ pub fn main() !void { testZigInitExe, testGodboltApi, testMissingOutputPath, + testZigFmt, }; for (test_fns) |testFn| { try fs.cwd().deleteTree(dir_path); @@ -143,3 +144,29 @@ fn testMissingOutputPath(zig_exe: []const u8, dir_path: []const u8) !void { zig_exe, "build-exe", source_path, "--output-dir", output_path, }); } + +fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void { + _ = try exec(dir_path, &[_][]const u8{ zig_exe, "init-exe" }); + + const unformatted_code = + \\fn square(num: i32) i32 { + \\return num * num; + \\} + ; + + const fmt1_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt1.zig" }); + try fs.cwd().writeFile(fmt1_zig_path, unformatted_code); + + const run_result1 = try exec(dir_path, &[_][]const u8{ zig_exe, "fmt", fmt1_zig_path }); + // stderr should be file path + \n + testing.expect(std.mem.startsWith(u8, run_result1.stderr, fmt1_zig_path)); + testing.expect(run_result1.stderr.len == fmt1_zig_path.len + 1 and run_result1.stderr[run_result1.stderr.len - 1] == '\n'); + + const fmt2_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt2.zig" }); + try fs.cwd().writeFile(fmt2_zig_path, unformatted_code); + + const run_result2 = try exec(dir_path, &[_][]const u8{ zig_exe, "fmt", dir_path }); + // running it on the dir, only the new file should be changed + testing.expect(std.mem.startsWith(u8, run_result2.stderr, fmt2_zig_path)); + testing.expect(run_result2.stderr.len == fmt2_zig_path.len + 1 and run_result2.stderr[run_result2.stderr.len - 1] == '\n'); +} -- 2.54.0 From b5f90244a436b74470caedcbfbb459cb2ad90203 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Jun 2020 20:50:28 -0700 Subject: [PATCH 076/295] temporary: Add test-cli step for only running cli tests --- build.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 4d71b0fb36e6fcd743e7c1acf21ae5995a00f4e0..2b5863a2b9f9e50820ec9e4628d78cf1c36e5b10 100644 --- a/build.zig +++ b/build.zig @@ -126,7 +126,10 @@ pub fn build(b: *Builder) !void { test_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes)); test_step.dependOn(tests.addStandaloneTests(b, test_filter, modes)); test_step.dependOn(tests.addStackTraceTests(b, test_filter, modes)); - test_step.dependOn(tests.addCliTests(b, test_filter, modes)); + const test_cli = tests.addCliTests(b, test_filter, modes); + const test_cli_step = b.step("test-cli", "Run zig cli tests"); + test_cli_step.dependOn(test_cli); + test_step.dependOn(test_cli); test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, modes)); test_step.dependOn(tests.addRuntimeSafetyTests(b, test_filter, modes)); test_step.dependOn(tests.addTranslateCTests(b, test_filter)); -- 2.54.0 From b216d8de886b1d3b3cebb279864ec6bc91216d3e Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Jun 2020 22:20:23 -0700 Subject: [PATCH 077/295] Simplify unformatted code in zig fmt cli test --- test/cli.zig | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/cli.zig b/test/cli.zig index 4c28827ba88ef3b60589bc6904f935ef42f5b04a..410fa97c8f2e30ff60990296f75a22bac278affc 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -148,11 +148,7 @@ fn testMissingOutputPath(zig_exe: []const u8, dir_path: []const u8) !void { fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void { _ = try exec(dir_path, &[_][]const u8{ zig_exe, "init-exe" }); - const unformatted_code = - \\fn square(num: i32) i32 { - \\return num * num; - \\} - ; + const unformatted_code = " // no reason for indent"; const fmt1_zig_path = try fs.path.join(a, &[_][]const u8{ dir_path, "fmt1.zig" }); try fs.cwd().writeFile(fmt1_zig_path, unformatted_code); -- 2.54.0 From 399f6b77c40d5ebd1d54ad193a70f1935c1ebdfc Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sat, 20 Jun 2020 22:21:23 -0700 Subject: [PATCH 078/295] Add 'no changes' test to zig fmt cli test --- test/cli.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/cli.zig b/test/cli.zig index 410fa97c8f2e30ff60990296f75a22bac278affc..77d79ed98ed8f33b27bcb8f8f4f4f50d9e47dd8d 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -165,4 +165,8 @@ fn testZigFmt(zig_exe: []const u8, dir_path: []const u8) !void { // running it on the dir, only the new file should be changed testing.expect(std.mem.startsWith(u8, run_result2.stderr, fmt2_zig_path)); testing.expect(run_result2.stderr.len == fmt2_zig_path.len + 1 and run_result2.stderr[run_result2.stderr.len - 1] == '\n'); + + const run_result3 = try exec(dir_path, &[_][]const u8{ zig_exe, "fmt", dir_path }); + // both files have been formatted, nothing should change now + testing.expect(run_result3.stderr.len == 0); } -- 2.54.0 From b70c38c33cd00d63783c51e440a9d30035b9eee3 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Sun, 21 Jun 2020 17:03:02 +0300 Subject: [PATCH 079/295] Close source file after reading it in zig fmt --- src-self-hosted/main.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 29930270ebf01f46d6a08704170968d25f466bfc..085e644d7f041b84f09ef653be39e47f9221e480 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -736,7 +736,8 @@ fn fmtPathFile( sub_path: []const u8, ) FmtError!void { const source_file = try dir.openFile(sub_path, .{}); - defer source_file.close(); + var file_closed = false; + errdefer if (!file_closed) source_file.close(); const stat = try source_file.stat(); @@ -748,6 +749,8 @@ fn fmtPathFile( error.ConnectionTimedOut => unreachable, else => |e| return e, }; + source_file.close(); + file_closed = true; defer fmt.gpa.free(source_code); // Add to set after no longer possible to get error.IsDir. -- 2.54.0 From 6f475130098a5913b485828c72cb47ab8146f9ea Mon Sep 17 00:00:00 2001 From: Charlie Stanton Date: Sun, 21 Jun 2020 18:24:59 +0100 Subject: [PATCH 080/295] Adds std.meta.cast and uses it to simplify translate-c --- lib/std/meta.zig | 95 ++++++++++++++++++ src-self-hosted/translate_c.zig | 166 +++----------------------------- test/translate_c.zig | 16 +-- 3 files changed, 119 insertions(+), 158 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index f93f03542922077a4fb9e4648426e416f36ad3bd..f27da1a51297ff2a2fcaccfc6617d9121eb16e8a 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -693,3 +693,98 @@ pub fn Vector(comptime len: u32, comptime child: type) type { }, }); } + +/// Given a type and value, cast the value to the type as c would +pub fn cast(comptime DestType: type, target: var) DestType { + const TargetType = @TypeOf(target); + switch (@typeInfo(DestType)) { + .Pointer => |_| { + switch (@typeInfo(TargetType)) { + .Int => |_| { + return @intToPtr(DestType, target); + }, + .ComptimeInt => |_| { + return @intToPtr(DestType, target); + }, + .Pointer => |ptr| { + return @ptrCast(DestType, @alignCast(ptr.alignment, target)); + }, + .Optional => |opt| { + if (@typeInfo(opt.child) == .Pointer) { + return @ptrCast(DestType, @alignCast(@alignOf(opt.child.Child), target)); + } + }, + else => {}, + } + }, + .Optional => |opt| { + if (@typeInfo(opt.child) == .Pointer) { + switch (@typeInfo(TargetType)) { + .Int => |_| { + return @intToPtr(DestType, target); + }, + .ComptimeInt => |_| { + return @intToPtr(DestType, target); + }, + .Pointer => |ptr| { + return @ptrCast(DestType, @alignCast(ptr.alignment, target)); + }, + .Optional => |target_opt| { + if (@typeInfo(target_opt.child) == .Pointer) { + return @ptrCast(DestType, @alignCast(@alignOf(target_opt.child.Child), target)); + } + }, + else => {}, + } + } + }, + .Enum => |_| { + if (@typeInfo(TargetType) == .Int or @typeInfo(TargetType) == .ComptimeInt) { + return @intToEnum(DestType, target); + } + }, + .EnumLiteral => |_| { + if (@typeInfo(TargetType) == .Int or @typeInfo(TargetType) == .ComptimeInt) { + return @intToEnum(DestType, target); + } + }, + .Int => |_| { + switch (@typeInfo(TargetType)) { + .Pointer => |_| { + return @as(DestType, @ptrToInt(target)); + }, + .Optional => |opt| { + if (@typeInfo(opt.child) == .Pointer) { + return @as(DestType, @ptrToInt(target)); + } + }, + .Enum => |_| { + return @as(DestType, @enumToInt(target)); + }, + .EnumLiteral => |_| { + return @as(DestType, @enumToInt(target)); + }, + else => {}, + } + }, + else => {}, + } + return @as(DestType, target); +} + +test "std.meta.cast" { + const E = enum(u2) { + Zero, + One, + Two, + }; + + var i = @as(i64, 10); + + testing.expect(cast(?*c_void, 0) == @intToPtr(?*c_void, 0)); + testing.expect(cast(*u8, 16) == @intToPtr(*u8, 16)); + testing.expect(cast(u64, @as(u32, 10)) == @as(u64, 10)); + testing.expect(cast(E, 1) == .One); + testing.expect(cast(u8, E.Two) == 2); + testing.expect(cast(*u64, &i).* == @as(u64, 10)); +} diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 3b1a91b28c3276e4b9760b6e32eaaba1a4f945f6..acce0e7f1de23a2620c140e6b503e7cf5667794d 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -5668,161 +5668,27 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, const lparen = try appendToken(c, .LParen, "("); - if (saw_integer_literal) { - //( if (@typeInfo(dest) == .Pointer)) - // @intToPtr(dest, x) - //else - // @as(dest, x) ) - const if_node = try transCreateNodeIf(c); - const type_info_node = try c.createBuiltinCall("@typeInfo", 1); - type_info_node.params()[0] = inner_node; - type_info_node.rparen_token = try appendToken(c, .LParen, ")"); - const cmp_node = try c.arena.create(ast.Node.InfixOp); - cmp_node.* = .{ - .op_token = try appendToken(c, .EqualEqual, "=="), - .lhs = &type_info_node.base, - .op = .EqualEqual, - .rhs = try transCreateNodeEnumLiteral(c, "Pointer"), - }; - if_node.condition = &cmp_node.base; - _ = try appendToken(c, .RParen, ")"); - - const int_to_ptr = try c.createBuiltinCall("@intToPtr", 2); - int_to_ptr.params()[0] = inner_node; - int_to_ptr.params()[1] = node_to_cast; - int_to_ptr.rparen_token = try appendToken(c, .RParen, ")"); - if_node.body = &int_to_ptr.base; - - const else_node = try transCreateNodeElse(c); - if_node.@"else" = else_node; - - const as_node = try c.createBuiltinCall("@as", 2); - as_node.params()[0] = inner_node; - as_node.params()[1] = node_to_cast; - as_node.rparen_token = try appendToken(c, .RParen, ")"); - else_node.body = &as_node.base; - - const group_node = try c.arena.create(ast.Node.GroupedExpression); - group_node.* = .{ - .lparen = lparen, - .expr = &if_node.base, - .rparen = try appendToken(c, .RParen, ")"), - }; - return &group_node.base; - } - - //( if (@typeInfo(@TypeOf(x)) == .Pointer) - // @ptrCast(dest, @alignCast(@alignOf(dest.Child), x)) - //else if (@typeInfo(@TypeOf(x)) == .Int and @typeInfo(dest) == .Pointer)) - // @intToPtr(dest, x) - //else - // @as(dest, x) ) - - const if_1 = try transCreateNodeIf(c); - const type_info_1 = try c.createBuiltinCall("@typeInfo", 1); - const type_of_1 = try c.createBuiltinCall("@TypeOf", 1); - type_info_1.params()[0] = &type_of_1.base; - type_of_1.params()[0] = node_to_cast; - type_of_1.rparen_token = try appendToken(c, .RParen, ")"); - type_info_1.rparen_token = try appendToken(c, .RParen, ")"); - - const cmp_1 = try c.arena.create(ast.Node.InfixOp); - cmp_1.* = .{ - .op_token = try appendToken(c, .EqualEqual, "=="), - .lhs = &type_info_1.base, - .op = .EqualEqual, - .rhs = try transCreateNodeEnumLiteral(c, "Pointer"), - }; - if_1.condition = &cmp_1.base; - _ = try appendToken(c, .RParen, ")"); - - const period_tok = try appendToken(c, .Period, "."); - const child_ident = try transCreateNodeIdentifier(c, "Child"); - const inner_node_child = try c.arena.create(ast.Node.InfixOp); - inner_node_child.* = .{ - .op_token = period_tok, - .lhs = inner_node, - .op = .Period, - .rhs = child_ident, + //(@import("std").meta.cast(dest, x)) + const import_fn_call = try c.createBuiltinCall("@import", 1); + const std_token = try appendToken(c, .StringLiteral, "\"std\""); + const std_node = try c.arena.create(ast.Node.StringLiteral); + std_node.* = .{ + .token = std_token, }; - - const align_of = try c.createBuiltinCall("@alignOf", 1); - align_of.params()[0] = &inner_node_child.base; - align_of.rparen_token = try appendToken(c, .RParen, ")"); - // hack to get zig fmt to render a comma in builtin calls - _ = try appendToken(c, .Comma, ","); - - const align_cast = try c.createBuiltinCall("@alignCast", 2); - align_cast.params()[0] = &align_of.base; - align_cast.params()[1] = node_to_cast; - align_cast.rparen_token = try appendToken(c, .RParen, ")"); - - const ptr_cast = try c.createBuiltinCall("@ptrCast", 2); - ptr_cast.params()[0] = inner_node; - ptr_cast.params()[1] = &align_cast.base; - ptr_cast.rparen_token = try appendToken(c, .RParen, ")"); - if_1.body = &ptr_cast.base; - - const else_1 = try transCreateNodeElse(c); - if_1.@"else" = else_1; - - const if_2 = try transCreateNodeIf(c); - const type_info_2 = try c.createBuiltinCall("@typeInfo", 1); - const type_of_2 = try c.createBuiltinCall("@TypeOf", 1); - type_info_2.params()[0] = &type_of_2.base; - type_of_2.params()[0] = node_to_cast; - type_of_2.rparen_token = try appendToken(c, .RParen, ")"); - type_info_2.rparen_token = try appendToken(c, .RParen, ")"); - - const cmp_2 = try c.arena.create(ast.Node.InfixOp); - cmp_2.* = .{ - .op_token = try appendToken(c, .EqualEqual, "=="), - .lhs = &type_info_2.base, - .op = .EqualEqual, - .rhs = try transCreateNodeEnumLiteral(c, "Int"), - }; - if_2.condition = &cmp_2.base; - const cmp_4 = try c.arena.create(ast.Node.InfixOp); - cmp_4.* = .{ - .op_token = try appendToken(c, .Keyword_and, "and"), - .lhs = &cmp_2.base, - .op = .BoolAnd, - .rhs = undefined, - }; - const type_info_3 = try c.createBuiltinCall("@typeInfo", 1); - type_info_3.params()[0] = inner_node; - type_info_3.rparen_token = try appendToken(c, .LParen, ")"); - const cmp_3 = try c.arena.create(ast.Node.InfixOp); - cmp_3.* = .{ - .op_token = try appendToken(c, .EqualEqual, "=="), - .lhs = &type_info_3.base, - .op = .EqualEqual, - .rhs = try transCreateNodeEnumLiteral(c, "Pointer"), - }; - cmp_4.rhs = &cmp_3.base; - if_2.condition = &cmp_4.base; - else_1.body = &if_2.base; - _ = try appendToken(c, .RParen, ")"); - - const int_to_ptr = try c.createBuiltinCall("@intToPtr", 2); - int_to_ptr.params()[0] = inner_node; - int_to_ptr.params()[1] = node_to_cast; - int_to_ptr.rparen_token = try appendToken(c, .RParen, ")"); - if_2.body = &int_to_ptr.base; - - const else_2 = try transCreateNodeElse(c); - if_2.@"else" = else_2; - - const as = try c.createBuiltinCall("@as", 2); - as.params()[0] = inner_node; - as.params()[1] = node_to_cast; - as.rparen_token = try appendToken(c, .RParen, ")"); - else_2.body = &as.base; + import_fn_call.params()[0] = &std_node.base; + import_fn_call.rparen_token = try appendToken(c, .RParen, ")"); + const inner_field_access = try transCreateNodeFieldAccess(c, &import_fn_call.base, "meta"); + const outer_field_access = try transCreateNodeFieldAccess(c, inner_field_access, "cast"); + + const cast_fn_call = try c.createCall(outer_field_access, 2); + cast_fn_call.params()[0] = inner_node; + cast_fn_call.params()[1] = node_to_cast; + cast_fn_call.rtoken = try appendToken(c, .RParen, ")"); const group_node = try c.arena.create(ast.Node.GroupedExpression); group_node.* = .{ .lparen = lparen, - .expr = &if_1.base, + .expr = &cast_fn_call.base, .rparen = try appendToken(c, .RParen, ")"), }; return &group_node.base; diff --git a/test/translate_c.zig b/test/translate_c.zig index 1c23afdb302e93afd34904b060c1947816d158c2..a4e2a330005b7c4a041dc9bd5c72eab14aa19be2 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1473,7 +1473,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("macro pointer cast", \\#define NRF_GPIO ((NRF_GPIO_Type *) NRF_GPIO_BASE) , &[_][]const u8{ - \\pub const NRF_GPIO = (if (@typeInfo(@TypeOf(NRF_GPIO_BASE)) == .Pointer) @ptrCast([*c]NRF_GPIO_Type, @alignCast(@alignOf([*c]NRF_GPIO_Type.Child), NRF_GPIO_BASE)) else if (@typeInfo(@TypeOf(NRF_GPIO_BASE)) == .Int and @typeInfo([*c]NRF_GPIO_Type) == .Pointer) @intToPtr([*c]NRF_GPIO_Type, NRF_GPIO_BASE) else @as([*c]NRF_GPIO_Type, NRF_GPIO_BASE)); + \\pub const NRF_GPIO = (@import("std").meta.cast([*c]NRF_GPIO_Type, NRF_GPIO_BASE)); }); cases.add("basic macro function", @@ -2683,11 +2683,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define FOO(bar) baz((void *)(baz)) \\#define BAR (void*) a , &[_][]const u8{ - \\pub inline fn FOO(bar: var) @TypeOf(baz((if (@typeInfo(@TypeOf(baz)) == .Pointer) @ptrCast(?*c_void, @alignCast(@alignOf(?*c_void.Child), baz)) else if (@typeInfo(@TypeOf(baz)) == .Int and @typeInfo(?*c_void) == .Pointer) @intToPtr(?*c_void, baz) else @as(?*c_void, baz)))) { - \\ return baz((if (@typeInfo(@TypeOf(baz)) == .Pointer) @ptrCast(?*c_void, @alignCast(@alignOf(?*c_void.Child), baz)) else if (@typeInfo(@TypeOf(baz)) == .Int and @typeInfo(?*c_void) == .Pointer) @intToPtr(?*c_void, baz) else @as(?*c_void, baz))); + \\pub inline fn FOO(bar: var) @TypeOf(baz((@import("std").meta.cast(?*c_void, baz)))) { + \\ return baz((@import("std").meta.cast(?*c_void, baz))); \\} , - \\pub const BAR = (if (@typeInfo(@TypeOf(a)) == .Pointer) @ptrCast(?*c_void, @alignCast(@alignOf(?*c_void.Child), a)) else if (@typeInfo(@TypeOf(a)) == .Int and @typeInfo(?*c_void) == .Pointer) @intToPtr(?*c_void, a) else @as(?*c_void, a)); + \\pub const BAR = (@import("std").meta.cast(?*c_void, a)); }); cases.add("macro conditional operator", @@ -2905,8 +2905,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define DefaultScreen(dpy) (((_XPrivDisplay)(dpy))->default_screen) \\ , &[_][]const u8{ - \\pub inline fn DefaultScreen(dpy: var) @TypeOf((if (@typeInfo(@TypeOf(dpy)) == .Pointer) @ptrCast(_XPrivDisplay, @alignCast(@alignOf(_XPrivDisplay.Child), dpy)) else if (@typeInfo(@TypeOf(dpy)) == .Int and @typeInfo(_XPrivDisplay) == .Pointer) @intToPtr(_XPrivDisplay, dpy) else @as(_XPrivDisplay, dpy)).*.default_screen) { - \\ return (if (@typeInfo(@TypeOf(dpy)) == .Pointer) @ptrCast(_XPrivDisplay, @alignCast(@alignOf(_XPrivDisplay.Child), dpy)) else if (@typeInfo(@TypeOf(dpy)) == .Int and @typeInfo(_XPrivDisplay) == .Pointer) @intToPtr(_XPrivDisplay, dpy) else @as(_XPrivDisplay, dpy)).*.default_screen; + \\pub inline fn DefaultScreen(dpy: var) @TypeOf((@import("std").meta.cast(_XPrivDisplay, dpy)).*.default_screen) { + \\ return (@import("std").meta.cast(_XPrivDisplay, dpy)).*.default_screen; \\} }); @@ -2914,9 +2914,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define NULL ((void*)0) \\#define FOO ((int)0x8000) , &[_][]const u8{ - \\pub const NULL = (if (@typeInfo(?*c_void) == .Pointer) @intToPtr(?*c_void, 0) else @as(?*c_void, 0)); + \\pub const NULL = (@import("std").meta.cast(?*c_void, 0)); , - \\pub const FOO = (if (@typeInfo(c_int) == .Pointer) @intToPtr(c_int, 0x8000) else @as(c_int, 0x8000)); + \\pub const FOO = (@import("std").meta.cast(c_int, 0x8000)); }); if (std.Target.current.abi == .msvc) { -- 2.54.0 From 8696e52a3d617ce30ec6202adc89cb10c67bcc43 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 21 Jun 2020 20:55:44 +0200 Subject: [PATCH 081/295] Make unary minus for unsigned types a compile error (#5654) * Make unary minus for unsigned types a compile error * Add unreachable when generating unsigned negate --- src/codegen.cpp | 12 ++++++------ src/ir.cpp | 37 ++++++++++++++++++++++--------------- test/compile_errors.zig | 9 +++++++++ 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index e20d6d60f5f17779f2a13880a4bd04056030afb7..f7c3575f86f5410efd3b20257689451ad56fd052 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3540,7 +3540,7 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutableGen *executabl for (size_t field_i = 0; field_i < field_count; field_i += 1) { TypeEnumField *type_enum_field = &wanted_type->data.enumeration.fields[field_i]; - + Buf *name = type_enum_field->name; auto entry = occupied_tag_values.put_unique(type_enum_field->value, name); if (entry != nullptr) { @@ -3654,7 +3654,7 @@ static LLVMValueRef ir_gen_negation(CodeGen *g, IrInstGen *inst, IrInstGen *oper } else if (scalar_type->data.integral.is_signed) { return LLVMBuildNSWNeg(g->builder, llvm_operand, ""); } else { - return LLVMBuildNUWNeg(g->builder, llvm_operand, ""); + zig_unreachable(); } } else { zig_unreachable(); @@ -3984,7 +3984,7 @@ static LLVMValueRef ir_render_elem_ptr(CodeGen *g, IrExecutableGen *executable, assert(array_type->data.pointer.child_type->id == ZigTypeIdArray); array_type = array_type->data.pointer.child_type; } - + assert(array_type->data.array.len != 0 || array_type->data.array.sentinel != nullptr); if (safety_check_on) { @@ -5258,7 +5258,7 @@ static LLVMValueRef get_enum_tag_name_function(CodeGen *g, ZigType *enum_type) { for (size_t field_i = 0; field_i < field_count; field_i += 1) { TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_i]; - + Buf *name = type_enum_field->name; auto entry = occupied_tag_values.put_unique(type_enum_field->value, name); if (entry != nullptr) { @@ -5471,7 +5471,7 @@ static LLVMTypeRef get_atomic_abi_type(CodeGen *g, IrInstGen *instruction) { } auto bit_count = operand_type->data.integral.bit_count; bool is_signed = operand_type->data.integral.is_signed; - + ir_assert(bit_count != 0, instruction); if (bit_count == 1 || !is_power_of_2(bit_count)) { return get_llvm_type(g, get_int_type(g, is_signed, operand_type->abi_size * 8)); @@ -9265,7 +9265,7 @@ static void init(CodeGen *g) { abi_name = (g->zig_target->arch == ZigLLVM_riscv32) ? "ilp32" : "lp64"; } } - + g->target_machine = ZigLLVMCreateTargetMachine(target_ref, buf_ptr(&g->llvm_triple_str), target_specific_cpu_args, target_specific_features, opt_level, reloc_mode, to_llvm_code_model(g), g->function_sections, float_abi, abi_name); diff --git a/src/ir.cpp b/src/ir.cpp index 635af397c4e1c3067b8e285a605c0989ddafa1fe..bb6ca554df12780b8800a714d15da790ef11d49f 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -12602,28 +12602,28 @@ static ZigType *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, ZigT if (prev_type->id == ZigTypeIdPointer && prev_type->data.pointer.ptr_len == PtrLenSingle && prev_type->data.pointer.child_type->id == ZigTypeIdArray && - ((cur_type->id == ZigTypeIdPointer && cur_type->data.pointer.ptr_len == PtrLenUnknown))) + ((cur_type->id == ZigTypeIdPointer && cur_type->data.pointer.ptr_len == PtrLenUnknown))) { - prev_inst = cur_inst; + prev_inst = cur_inst; if (prev_type->data.pointer.is_const && !cur_type->data.pointer.is_const) { // const array pointer and non-const unknown pointer make_the_pointer_const = true; } - continue; + continue; } // *[N]T to [*]T if (cur_type->id == ZigTypeIdPointer && cur_type->data.pointer.ptr_len == PtrLenSingle && cur_type->data.pointer.child_type->id == ZigTypeIdArray && - ((prev_type->id == ZigTypeIdPointer && prev_type->data.pointer.ptr_len == PtrLenUnknown))) + ((prev_type->id == ZigTypeIdPointer && prev_type->data.pointer.ptr_len == PtrLenUnknown))) { if (cur_type->data.pointer.is_const && !prev_type->data.pointer.is_const) { // const array pointer and non-const unknown pointer make_the_pointer_const = true; } - continue; + continue; } // *[N]T to []T @@ -20987,17 +20987,24 @@ static IrInstGen *ir_analyze_negation(IrAnalyze *ira, IrInstSrcUnOp *instruction if (type_is_invalid(expr_type)) return ira->codegen->invalid_inst_gen; - if (!(expr_type->id == ZigTypeIdInt || expr_type->id == ZigTypeIdComptimeInt || - expr_type->id == ZigTypeIdFloat || expr_type->id == ZigTypeIdComptimeFloat || - expr_type->id == ZigTypeIdVector)) - { - ir_add_error(ira, &instruction->base.base, - buf_sprintf("negation of type '%s'", buf_ptr(&expr_type->name))); - return ira->codegen->invalid_inst_gen; - } - bool is_wrap_op = (instruction->op_id == IrUnOpNegationWrap); + switch (expr_type->id) { + case ZigTypeIdComptimeInt: + case ZigTypeIdFloat: + case ZigTypeIdComptimeFloat: + case ZigTypeIdVector: + break; + case ZigTypeIdInt: + if (is_wrap_op || expr_type->data.integral.is_signed) + break; + ZIG_FALLTHROUGH; + default: + ir_add_error(ira, &instruction->base.base, + buf_sprintf("negation of type '%s'", buf_ptr(&expr_type->name))); + return ira->codegen->invalid_inst_gen; + } + ZigType *scalar_type = (expr_type->id == ZigTypeIdVector) ? expr_type->data.vector.elem_type : expr_type; if (instr_is_comptime(value)) { @@ -30380,7 +30387,7 @@ static ErrorMsg *ir_eval_float_op(IrAnalyze *ira, IrInst* source_instr, BuiltinF case BuiltinFnIdTrunc: f128M_trunc(in, out); break; - case BuiltinFnIdRound: + case BuiltinFnIdRound: f128M_roundToInt(in, softfloat_round_near_maxMag, false, out); break; case BuiltinFnIdNearbyInt: diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 3f898cc337730baf5acbf02b90f39e980be18283..e3dd1f0d8f4161fc09ac7f132faba42bfae1c2f9 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -7530,4 +7530,13 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { , &[_][]const u8{ "tmp.zig:2:9: error: @wasmMemoryGrow is a wasm32 feature only", }); + + cases.add("Issue #5586: Make unary minus for unsigned types a compile error", + \\export fn f(x: u32) u32 { + \\ const y = -%x; + \\ return -y; + \\} + , &[_][]const u8{ + "tmp.zig:3:12: error: negation of type 'u32'" + }); } -- 2.54.0 From d907f574e02aadf8196e616bcc2fb2813cf2c82c Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Sat, 20 Jun 2020 18:35:01 +0200 Subject: [PATCH 082/295] stage1: fix concat of sliced str literals --- src/ir.cpp | 3 +-- test/stage1/behavior/slice.zig | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index bb6ca554df12780b8800a714d15da790ef11d49f..3fa138ed8e1cf270176562507f8a87e40d094236 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -826,12 +826,11 @@ static ZigValue *const_ptr_pointee_unchecked_no_isf(CodeGen *g, ZigValue *const_ ZigValue *array_val = const_val->data.x_ptr.data.base_array.array_val; size_t elem_index = const_val->data.x_ptr.data.base_array.elem_index; - // TODO handle sentinel terminated arrays expand_undef_array(g, array_val); result = g->pass1_arena->create(); result->special = array_val->special; result->type = get_array_type(g, array_val->type->data.array.child_type, - array_val->type->data.array.len - elem_index, nullptr); + array_val->type->data.array.len - elem_index, array_val->type->data.array.sentinel); result->data.x_array.special = ConstArraySpecialNone; result->data.x_array.data.s_none.elements = &array_val->data.x_array.data.s_none.elements[elem_index]; result->parent.id = ConstParentIdArray; diff --git a/test/stage1/behavior/slice.zig b/test/stage1/behavior/slice.zig index 1faefe680037b38cfb196e487558c87c8a9f4838..8aa3bdb7c187668b4d757cc1021bd31eb4274b58 100644 --- a/test/stage1/behavior/slice.zig +++ b/test/stage1/behavior/slice.zig @@ -280,6 +280,11 @@ test "slice syntax resulting in pointer-to-array" { expect(slice[0] == 5); comptime expect(@TypeOf(src_slice[0..2]) == *align(4) [2]u8); } + + fn testConcatStrLiterals() void { + expectEqualSlices("a"[0..] ++ "b"[0..], "ab"); + expectEqualSlices("a"[0..:0] ++ "b"[0..:0], "ab"); + } }; S.doTheTest(); -- 2.54.0 From 8c15cfe3dabe736754b138f16de905e6487bd8b5 Mon Sep 17 00:00:00 2001 From: Charlie Stanton Date: Sun, 21 Jun 2020 21:48:12 +0100 Subject: [PATCH 083/295] Compacts switch statements and string literal --- lib/std/meta.zig | 31 +++++++++---------------------- src-self-hosted/translate_c.zig | 8 ++------ 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index f27da1a51297ff2a2fcaccfc6617d9121eb16e8a..6c10941aa774e86be55655233cd8b2c091ab9e7f 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -694,16 +694,14 @@ pub fn Vector(comptime len: u32, comptime child: type) type { }); } -/// Given a type and value, cast the value to the type as c would +/// Given a type and value, cast the value to the type as c would. +/// This is for translate-c and is not intended for general use. pub fn cast(comptime DestType: type, target: var) DestType { const TargetType = @TypeOf(target); switch (@typeInfo(DestType)) { - .Pointer => |_| { + .Pointer => { switch (@typeInfo(TargetType)) { - .Int => |_| { - return @intToPtr(DestType, target); - }, - .ComptimeInt => |_| { + .Int, .ComptimeInt => { return @intToPtr(DestType, target); }, .Pointer => |ptr| { @@ -720,10 +718,7 @@ pub fn cast(comptime DestType: type, target: var) DestType { .Optional => |opt| { if (@typeInfo(opt.child) == .Pointer) { switch (@typeInfo(TargetType)) { - .Int => |_| { - return @intToPtr(DestType, target); - }, - .ComptimeInt => |_| { + .Int, .ComptimeInt => { return @intToPtr(DestType, target); }, .Pointer => |ptr| { @@ -738,19 +733,14 @@ pub fn cast(comptime DestType: type, target: var) DestType { } } }, - .Enum => |_| { + .Enum, .EnumLiteral => { if (@typeInfo(TargetType) == .Int or @typeInfo(TargetType) == .ComptimeInt) { return @intToEnum(DestType, target); } }, - .EnumLiteral => |_| { - if (@typeInfo(TargetType) == .Int or @typeInfo(TargetType) == .ComptimeInt) { - return @intToEnum(DestType, target); - } - }, - .Int => |_| { + .Int, .ComptimeInt => { switch (@typeInfo(TargetType)) { - .Pointer => |_| { + .Pointer => { return @as(DestType, @ptrToInt(target)); }, .Optional => |opt| { @@ -758,10 +748,7 @@ pub fn cast(comptime DestType: type, target: var) DestType { return @as(DestType, @ptrToInt(target)); } }, - .Enum => |_| { - return @as(DestType, @enumToInt(target)); - }, - .EnumLiteral => |_| { + .Enum, .EnumLiteral => { return @as(DestType, @enumToInt(target)); }, else => {}, diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index acce0e7f1de23a2620c140e6b503e7cf5667794d..e492edb3795fc9e424b535529303e55873f4a2da 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -5670,12 +5670,8 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, //(@import("std").meta.cast(dest, x)) const import_fn_call = try c.createBuiltinCall("@import", 1); - const std_token = try appendToken(c, .StringLiteral, "\"std\""); - const std_node = try c.arena.create(ast.Node.StringLiteral); - std_node.* = .{ - .token = std_token, - }; - import_fn_call.params()[0] = &std_node.base; + const std_node = try transCreateNodeStringLiteral(c, "\"std\""); + import_fn_call.params()[0] = std_node; import_fn_call.rparen_token = try appendToken(c, .RParen, ")"); const inner_field_access = try transCreateNodeFieldAccess(c, &import_fn_call.base, "meta"); const outer_field_access = try transCreateNodeFieldAccess(c, inner_field_access, "cast"); -- 2.54.0 From 64078ca92439d4f01d4a6e60a6ad33025da5a36a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 22 Jun 2020 09:14:51 +0200 Subject: [PATCH 084/295] Enhance std.os.symlinkat coverage Fixes `std.os.symlinkat` compile errors, adds Windows stub (still needs to be implemented), adds WASI implementation. --- lib/std/c.zig | 1 + lib/std/os.zig | 62 +++++++++++++++++++++++++++++++++++++++++---- lib/std/os/test.zig | 13 ++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index fe9fc7ac403adecdc236cbd0092db63583267741..97d6bf521565413943bb13c75c31b2f94eed0a14 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -102,6 +102,7 @@ pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int; pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int; pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int; +pub extern "c" fn symlinkat(oldpath: [*:0]const u8, newdirfd: fd_t, newpath: [*:0]const u8) c_int; pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int; pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, new: [*:0]const u8) c_int; pub extern "c" fn chdir(path: [*:0]const u8) c_int; diff --git a/lib/std/os.zig b/lib/std/os.zig index 0558390b9e9a8e8bbe1c64054189668ab7597645..16ab314c14371c05b0203ce2ba748ea0f4fd7970 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1520,15 +1520,17 @@ pub const SymLinkError = error{ /// If `sym_link_path` exists, it will not be overwritten. /// See also `symlinkC` and `symlinkW`. pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { + if (builtin.os.tag == .wasi) { + @compileError("symlink is not supported in WASI; use symlinkat instead"); + } if (builtin.os.tag == .windows) { const target_path_w = try windows.sliceToPrefixedFileW(target_path); const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path); return windows.CreateSymbolicLinkW(sym_link_path_w.span().ptr, target_path_w.span().ptr, 0); - } else { - const target_path_c = try toPosixPath(target_path); - const sym_link_path_c = try toPosixPath(sym_link_path); - return symlinkZ(&target_path_c, &sym_link_path_c); } + const target_path_c = try toPosixPath(target_path); + const sym_link_path_c = try toPosixPath(sym_link_path); + return symlinkZ(&target_path_c, &sym_link_path_c); } pub const symlinkC = @compileError("deprecated: renamed to symlinkZ"); @@ -1561,15 +1563,65 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin } } +/// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string +/// `target_path` **relative** to `newdirfd` directory handle. +/// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent +/// one; the latter case is known as a dangling link. +/// If `sym_link_path` exists, it will not be overwritten. +/// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { + if (builtin.os.tag == .wasi) { + return symlinkatWasi(target_path, newdirfd, sym_link_path); + } + if (builtin.os.tag == .windows) { + const target_path_w = try windows.sliceToPrefixedFileW(target_path); + const sym_link_path_w = try windows.sliceToPrefixedFileW(sym_link_path); + return symlinkatW(target_path_w.span().ptr, newdirfd, sym_link_path_w.span().ptr); + } const target_path_c = try toPosixPath(target_path); const sym_link_path_c = try toPosixPath(sym_link_path); - return symlinkatZ(target_path_c, newdirfd, sym_link_path_c); + return symlinkatZ(&target_path_c, newdirfd, &sym_link_path_c); } pub const symlinkatC = @compileError("deprecated: renamed to symlinkatZ"); +/// WASI-only. The same as `symlinkat` but targeting WASI. +/// See also `symlinkat`. +pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { + switch (wasi.path_symlink(sym_link_path.ptr, sym_link_path.len, newdirfd, target_path.ptr, target_path.len)) { + wasi.ESUCCESS => {}, + wasi.EFAULT => unreachable, + wasi.EINVAL => unreachable, + wasi.EACCES => return error.AccessDenied, + wasi.EPERM => return error.AccessDenied, + wasi.EDQUOT => return error.DiskQuota, + wasi.EEXIST => return error.PathAlreadyExists, + wasi.EIO => return error.FileSystem, + wasi.ELOOP => return error.SymLinkLoop, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOTDIR => return error.NotDir, + wasi.ENOMEM => return error.SystemResources, + wasi.ENOSPC => return error.NoSpaceLeft, + wasi.EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. The same as `symlinkat` except the paths are null-terminated, WTF-16 encoded. +/// See also `symlinkat`. +pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymlinkError!void { + @compileError("TODO implement on Windows"); +} + +/// The same as `symlinkat` except the parameters are null-terminated pointers. +/// See also `symlinkat`. pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { + if (builtin.os.tag == .windows) { + const target_path_w = try windows.cStrToPrefixedFileW(target_path); + const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); + return symlinkatW(target_path_w.span().ptr, newdirfd, sym_link_path.span().ptr); + } switch (errno(system.symlinkat(target_path, newdirfd, sym_link_path))) { 0 => return, EFAULT => unreachable, diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index cc3b4f574152587f6a85c8fa6da5048d5c86310d..d4624929ee664f3d49c906903b7993a1737e54cc 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -18,6 +18,19 @@ const AtomicOrder = builtin.AtomicOrder; const tmpDir = std.testing.tmpDir; const Dir = std.fs.Dir; +test "readlinkat" { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + // create file + try tmp.dir.writeFile("file.txt", "nonsense"); + + // create a symbolic link + try os.symlinkat("file.txt", tmp.dir.fd, "link"); + + // TODO read the link +} + test "makePath, put some files in it, deleteTree" { var tmp = tmpDir(.{}); defer tmp.cleanup(); -- 2.54.0 From c950f0c6c3d5472a635ba8b971f46200d41221fd Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 22 Jun 2020 09:40:06 +0200 Subject: [PATCH 085/295] Enhance std.os.readlinkat coverage Adds Windows stub (still needs to be implemented on Windows), adds WASI implementation, adds unit test testing basic chain of ops: create file -> symlink -> readlink. --- lib/std/os.zig | 46 +++++++++++++++++++++++++++++++++++++++++++-- lib/std/os/test.zig | 8 +++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 16ab314c14371c05b0203ce2ba748ea0f4fd7970..59cde5d6067ce1209f797d82a5afa8770e3b0037 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1588,7 +1588,7 @@ pub const symlinkatC = @compileError("deprecated: renamed to symlinkatZ"); /// WASI-only. The same as `symlinkat` but targeting WASI. /// See also `symlinkat`. pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { - switch (wasi.path_symlink(sym_link_path.ptr, sym_link_path.len, newdirfd, target_path.ptr, target_path.len)) { + switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) { wasi.ESUCCESS => {}, wasi.EFAULT => unreachable, wasi.EINVAL => unreachable, @@ -2343,12 +2343,54 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 } } +/// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle. +/// The return value is a slice of `out_buffer` from index 0. +/// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. +pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (builtin.os.tag == .wasi) { + return readlinkatWasi(dirfd, file_path, out_buffer); + } + if (builtin.os.tag == .windows) { + const file_path_w = try windows.cStrToPrefixedFileW(file_path); + return readlinkatW(dirfd, file_path.span().ptr, out_buffer); + } + const file_path_c = try toPosixPath(file_path); + return readlinkatZ(dirfd, &file_path_c, out_buffer); +} + pub const readlinkatC = @compileError("deprecated: renamed to readlinkatZ"); +/// WASI-only. Same as `readlinkat` but targets WASI. +/// See also `readlinkat`. +pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + var bufused: usize = undefined; + switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) { + wasi.ESUCCESS => return out_buffer[0..bufused], + wasi.EACCES => return error.AccessDenied, + wasi.EFAULT => unreachable, + wasi.EINVAL => unreachable, + wasi.EIO => return error.FileSystem, + wasi.ELOOP => return error.SymLinkLoop, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOMEM => return error.SystemResources, + wasi.ENOTDIR => return error.NotDir, + else => |err| return unexpectedErrno(err), + } +} + +/// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. +/// See also `readlinkat`. +pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { + @compileError("TODO implement on Windows"); +} + +/// Same as `readlinkat` except `file_path` is null-terminated. +/// See also `readlinkat`. pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); - @compileError("TODO implement readlink for Windows"); + return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer); } const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); switch (errno(rc)) { diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index d4624929ee664f3d49c906903b7993a1737e54cc..c298a0a2033a349078b37d6d879e6b085c5973c9 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -19,6 +19,9 @@ const tmpDir = std.testing.tmpDir; const Dir = std.fs.Dir; test "readlinkat" { + // enable when `readlinkat` and `symlinkat` are implemented on Windows + if (builtin.os.tag == .windows) return error.SkipZigTest; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -28,7 +31,10 @@ test "readlinkat" { // create a symbolic link try os.symlinkat("file.txt", tmp.dir.fd, "link"); - // TODO read the link + // read the link + var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; + const read_link = try os.readlinkat(tmp.dir.fd, "link", buffer[0..]); + expect(mem.eql(u8, "file.txt", read_link)); } test "makePath, put some files in it, deleteTree" { -- 2.54.0 From 8faa85ac19e1bbbe962d69b283c94526e35f5338 Mon Sep 17 00:00:00 2001 From: Carter Sande Date: Mon, 22 Jun 2020 02:45:23 -0700 Subject: [PATCH 086/295] ArgIteratorWindows: don't treat unclosed quotes like they're escaped --- lib/std/process.zig | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index a65f6da3afa487e54efe2c2044747a030e24914d..57b7888e19088cf8d5070a51dbd9e7b46047ddb5 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -282,7 +282,6 @@ pub const ArgIteratorWindows = struct { index: usize, cmd_line: [*]const u8, in_quote: bool, - quote_count: usize, seen_quote_count: usize, pub const NextError = error{OutOfMemory}; @@ -296,7 +295,6 @@ pub const ArgIteratorWindows = struct { .index = 0, .cmd_line = cmd_line, .in_quote = false, - .quote_count = countQuotes(cmd_line), .seen_quote_count = 0, }; } @@ -342,7 +340,7 @@ pub const ArgIteratorWindows = struct { backslash_count += 1; }, ' ', '\t' => { - if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) { + if (self.seen_quote_count % 2 == 0) { return true; } backslash_count = 0; @@ -371,9 +369,6 @@ pub const ArgIteratorWindows = struct { if (quote_is_real) { self.seen_quote_count += 1; - if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) { - try buf.append('"'); - } } else { try buf.append('"'); } @@ -384,7 +379,7 @@ pub const ArgIteratorWindows = struct { ' ', '\t' => { try self.emitBackslashes(&buf, backslash_count); backslash_count = 0; - if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) { + if (self.seen_quote_count % 2 == 1) { try buf.append(byte); } else { return buf.toOwnedSlice(); @@ -405,26 +400,6 @@ pub const ArgIteratorWindows = struct { try buf.append('\\'); } } - - fn countQuotes(cmd_line: [*]const u8) usize { - var result: usize = 0; - var backslash_count: usize = 0; - var index: usize = 0; - while (true) : (index += 1) { - const byte = cmd_line[index]; - switch (byte) { - 0 => return result, - '\\' => backslash_count += 1, - '"' => { - result += 1 - (backslash_count % 2); - backslash_count = 0; - }, - else => { - backslash_count = 0; - }, - } - } - } }; pub const ArgIterator = struct { @@ -578,7 +553,7 @@ test "windows arg parsing" { testWindowsCmdLine("a\\\\\\b d\"e f\"g h", &[_][]const u8{ "a\\\\\\b", "de fg", "h" }); testWindowsCmdLine("a\\\\\\\"b c d", &[_][]const u8{ "a\\\"b", "c", "d" }); testWindowsCmdLine("a\\\\\\\\\"b c\" d e", &[_][]const u8{ "a\\\\b c", "d", "e" }); - testWindowsCmdLine("a b\tc \"d f", &[_][]const u8{ "a", "b", "c", "\"d", "f" }); + testWindowsCmdLine("a b\tc \"d f", &[_][]const u8{ "a", "b", "c", "d f" }); testWindowsCmdLine("\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", &[_][]const u8{ ".\\..\\zig-cache\\build", -- 2.54.0 From 7cb41a415a6eca7d13980a62ee5727b4adcad8b0 Mon Sep 17 00:00:00 2001 From: Carter Sande Date: Mon, 22 Jun 2020 02:53:14 -0700 Subject: [PATCH 087/295] ArgIteratorWindows: simplify quote state tracking --- lib/std/process.zig | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index 57b7888e19088cf8d5070a51dbd9e7b46047ddb5..520b219f979e290a2ec08478abf1fb2f3db79d15 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -281,8 +281,6 @@ pub const ArgIteratorWasi = struct { pub const ArgIteratorWindows = struct { index: usize, cmd_line: [*]const u8, - in_quote: bool, - seen_quote_count: usize, pub const NextError = error{OutOfMemory}; @@ -294,8 +292,6 @@ pub const ArgIteratorWindows = struct { return ArgIteratorWindows{ .index = 0, .cmd_line = cmd_line, - .in_quote = false, - .seen_quote_count = 0, }; } @@ -326,6 +322,7 @@ pub const ArgIteratorWindows = struct { } var backslash_count: usize = 0; + var in_quote = false; while (true) : (self.index += 1) { const byte = self.cmd_line[self.index]; switch (byte) { @@ -333,14 +330,14 @@ pub const ArgIteratorWindows = struct { '"' => { const quote_is_real = backslash_count % 2 == 0; if (quote_is_real) { - self.seen_quote_count += 1; + in_quote = !in_quote; } }, '\\' => { backslash_count += 1; }, ' ', '\t' => { - if (self.seen_quote_count % 2 == 0) { + if (!in_quote) { return true; } backslash_count = 0; @@ -358,6 +355,7 @@ pub const ArgIteratorWindows = struct { defer buf.deinit(); var backslash_count: usize = 0; + var in_quote = false; while (true) : (self.index += 1) { const byte = self.cmd_line[self.index]; switch (byte) { @@ -368,7 +366,7 @@ pub const ArgIteratorWindows = struct { backslash_count = 0; if (quote_is_real) { - self.seen_quote_count += 1; + in_quote = !in_quote; } else { try buf.append('"'); } @@ -379,7 +377,7 @@ pub const ArgIteratorWindows = struct { ' ', '\t' => { try self.emitBackslashes(&buf, backslash_count); backslash_count = 0; - if (self.seen_quote_count % 2 == 1) { + if (in_quote) { try buf.append(byte); } else { return buf.toOwnedSlice(); -- 2.54.0 From 65ef74e2cdc87888d16ad207371cfd3ff8c0dce1 Mon Sep 17 00:00:00 2001 From: prime31 Date: Mon, 22 Jun 2020 07:30:02 -0700 Subject: [PATCH 088/295] `try` allocation of pointer type when parsing (#5665) * `try` allocation of pointer type when parsing * fixes pointer destroy compile error --- lib/std/json.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/json.zig b/lib/std/json.zig index ae10dc9559410c4ae4ba1dd4270037ceb8210b66..6377b69a80445338193f5dac9a16ef4f6ab897d4 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -1535,7 +1535,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: const allocator = options.allocator orelse return error.AllocatorRequired; switch (ptrInfo.size) { .One => { - const r: T = allocator.create(ptrInfo.child); + const r: T = try allocator.create(ptrInfo.child); r.* = try parseInternal(ptrInfo.child, token, tokens, options); return r; }, @@ -1629,7 +1629,7 @@ pub fn parseFree(comptime T: type, value: T, options: ParseOptions) void { switch (ptrInfo.size) { .One => { parseFree(ptrInfo.child, value.*, options); - allocator.destroy(v); + allocator.destroy(value); }, .Slice => { for (value) |v| { -- 2.54.0 From 923c0feda1e42dcaf65ab80bafd7400996b39861 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 21 Jun 2020 15:18:50 +0200 Subject: [PATCH 089/295] Add std.fs.File.readAllAlloc tests This commit adds some unit tests for `std.fs.File.readAllAlloc` function. It also updates the docs of `Reader.readNoEof` which were outdated, and swaps `inStream()` for `reader()` in `File.readAllAlloc` with the former being deprecated. --- lib/std/fs/file.zig | 2 +- lib/std/fs/test.zig | 43 ++++++++++++++++++++++++++++++++++++++++--- lib/std/io/reader.zig | 3 +-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 160f8314b9c6b378045ee29c24bd5b7ea0fbacc2..cffc8cf87ebe8b9701fd90580443012309d83578 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -364,7 +364,7 @@ pub const File = struct { const buf = try allocator.allocWithOptions(u8, size, alignment, optional_sentinel); errdefer allocator.free(buf); - try self.inStream().readNoEof(buf); + try self.reader().readNoEof(buf); return buf; } diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 875565fc54f13d7faeb300fa2699b9da397d5b6e..23a9c96ff5eeb72518696ba7467cc783259fcfef 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1,7 +1,44 @@ const std = @import("../std.zig"); +const testing = std.testing; const builtin = std.builtin; const fs = std.fs; +const mem = std.mem; + const File = std.fs.File; +const tmpDir = testing.tmpDir; + +test "readAllAlloc" { + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + var file = try tmp_dir.dir.createFile("test_file", .{ .read = true }); + defer file.close(); + + const buf1 = try file.readAllAlloc(testing.allocator, 0, 1024); + defer testing.allocator.free(buf1); + testing.expect(buf1.len == 0); + + const write_buf: []const u8 = "this is a test.\nthis is a test.\nthis is a test.\nthis is a test.\n"; + try file.writeAll(write_buf); + try file.seekTo(0); + const file_size = try file.getEndPos(); + + // max_bytes > file_size + const buf2 = try file.readAllAlloc(testing.allocator, file_size, 1024); + defer testing.allocator.free(buf2); + testing.expectEqual(write_buf.len, buf2.len); + testing.expect(std.mem.eql(u8, write_buf, buf2)); + try file.seekTo(0); + + // max_bytes == file_size + const buf3 = try file.readAllAlloc(testing.allocator, file_size, write_buf.len); + defer testing.allocator.free(buf3); + testing.expectEqual(write_buf.len, buf3.len); + testing.expect(std.mem.eql(u8, write_buf, buf3)); + + // max_bytes < file_size + testing.expectError(error.FileTooBig, file.readAllAlloc(testing.allocator, file_size, write_buf.len - 1)); +} test "openSelfExe" { if (builtin.os.tag == .wasi) return error.SkipZigTest; @@ -116,7 +153,7 @@ test "create file, lock and read from multiple process at once" { test "open file with exclusive nonblocking lock twice (absolute paths)" { if (builtin.os.tag == .wasi) return error.SkipZigTest; - const allocator = std.testing.allocator; + const allocator = testing.allocator; const file_paths: [1][]const u8 = .{"zig-test-absolute-paths.txt"}; const filename = try fs.path.resolve(allocator, &file_paths); @@ -126,7 +163,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { const file2 = fs.createFileAbsolute(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); file1.close(); - std.testing.expectError(error.WouldBlock, file2); + testing.expectError(error.WouldBlock, file2); try fs.deleteFileAbsolute(filename); } @@ -187,7 +224,7 @@ const FileLockTestContext = struct { }; fn run_lock_file_test(contexts: []FileLockTestContext) !void { - var threads = std.ArrayList(*std.Thread).init(std.testing.allocator); + var threads = std.ArrayList(*std.Thread).init(testing.allocator); defer { for (threads.items) |thread| { thread.wait(); diff --git a/lib/std/io/reader.zig b/lib/std/io/reader.zig index 03744e4da4eb5cc07320e65d0ff5de1d378a9190..4c682e8abaf80d8cef5eb2b100b5217f8a199046 100644 --- a/lib/std/io/reader.zig +++ b/lib/std/io/reader.zig @@ -40,8 +40,7 @@ pub fn Reader( return index; } - /// Returns the number of bytes read. If the number read would be smaller than buf.len, - /// error.EndOfStream is returned instead. + /// If the number read would be smaller than `buf.len`, `error.EndOfStream` is returned instead. pub fn readNoEof(self: Self, buf: []u8) !void { const amt_read = try self.readAll(buf); if (amt_read < buf.len) return error.EndOfStream; -- 2.54.0 From d98aed6eff61628f97597e1fa68335db5b2fd466 Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 19 Jun 2020 20:45:57 +0300 Subject: [PATCH 090/295] self-hosted: generalize `astGenBuiltinCall` --- src-self-hosted/Module.zig | 35 ++++++++++++++++++++++++++--------- src-self-hosted/zir.zig | 1 + 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 508a9b279981767d082b0df3ea6f67707c114fa0..6da637cd58dad561dd7dec98cdf0e9253f2526be 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1384,18 +1384,35 @@ fn astGenAsm(self: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!* fn astGenBuiltinCall(self: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { const tree = scope.tree(); const builtin_name = tree.tokenSlice(call.builtin_token); + const src = tree.token_locs[call.builtin_token].start; - if (mem.eql(u8, builtin_name, "@ptrToInt")) { - if (call.params_len != 1) { - return self.failTok(scope, call.builtin_token, "expected 1 parameter, found {}", .{call.params_len}); + inline for (std.meta.declarations(zir.Inst)) |inst| { + if (inst.data != .Type) continue; + const T = inst.data.Type; + if (!@hasDecl(T, "builtin_name")) continue; + if (std.mem.eql(u8, builtin_name, T.builtin_name)) { + var value: T = undefined; + const positionals = @typeInfo(std.meta.fieldInfo(T, "positionals").field_type).Struct; + if (positionals.fields.len == 0) { + return self.addZIRInst(scope, src, T, value.positionals, value.kw_args); + } + const arg_count: ?usize = if (positionals.fields[0].field_type == []*zir.Inst) null else positionals.fields.len; + if (arg_count) |some| { + if (call.params_len != some) { + return self.failTok(scope, call.builtin_token, "expected {} parameter, found {}", .{ some, call.params_len }); + } + const params = call.params(); + inline for (positionals.fields) |p, i| { + @field(value.positionals, p.name) = try self.astGenExpr(scope, params[i]); + } + } else { + return self.failTok(scope, call.builtin_token, "TODO var args builtin '{}'", .{builtin_name}); + } + + return self.addZIRInst(scope, src, T, value.positionals, .{}); } - const src = tree.token_locs[call.builtin_token].start; - return self.addZIRInst(scope, src, zir.Inst.PtrToInt, .{ - .ptr = try self.astGenExpr(scope, call.params()[0]), - }, .{}); - } else { - return self.failTok(scope, call.builtin_token, "TODO implement builtin call for '{}'", .{builtin_name}); } + return self.failTok(scope, call.builtin_token, "TODO implement builtin call for '{}'", .{builtin_name}); } fn astGenCall(self: *Module, scope: *Scope, call: *ast.Node.Call) InnerError!*zir.Inst { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 90793c51f96f88b7522afa2c82299d3464bc40c9..b885073db0557183f2a6fd8fddd4b1580aedfd50 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -201,6 +201,7 @@ pub const Inst = struct { }; pub const PtrToInt = struct { + pub const builtin_name = "@ptrToInt"; pub const base_tag = Tag.ptrtoint; base: Inst, -- 2.54.0 From 7b68385d7d4448e81cc882d9a5464bf58d10dc0d Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 19 Jun 2020 20:52:06 +0300 Subject: [PATCH 091/295] self-hosted: astGenIntegerLiteral support other bases --- src-self-hosted/Module.zig | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 6da637cd58dad561dd7dec98cdf0e9253f2526be..730954b4318eb7cc7e8bf2e228a1d409c09dfad1 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1314,16 +1314,19 @@ fn astGenStringLiteral(self: *Module, scope: *Scope, str_lit: *ast.Node.StringLi fn astGenIntegerLiteral(self: *Module, scope: *Scope, int_lit: *ast.Node.IntegerLiteral) InnerError!*zir.Inst { const arena = scope.arena(); const tree = scope.tree(); - const bytes = tree.tokenSlice(int_lit.token); + var bytes = tree.tokenSlice(int_lit.token); + const base = if (mem.startsWith(u8, bytes, "0x")) + 16 + else if (mem.startsWith(u8, bytes, "0o")) + 8 + else if (mem.startsWith(u8, bytes, "0b")) + 2 + else + @as(u8, 10); - if (mem.startsWith(u8, bytes, "0x")) { - return self.failTok(scope, int_lit.token, "TODO implement 0x int prefix", .{}); - } else if (mem.startsWith(u8, bytes, "0o")) { - return self.failTok(scope, int_lit.token, "TODO implement 0o int prefix", .{}); - } else if (mem.startsWith(u8, bytes, "0b")) { - return self.failTok(scope, int_lit.token, "TODO implement 0b int prefix", .{}); - } - if (std.fmt.parseInt(u64, bytes, 10)) |small_int| { + if (base != 10) bytes = bytes[2..]; + + if (std.fmt.parseInt(u64, bytes, base)) |small_int| { const int_payload = try arena.create(Value.Payload.Int_u64); int_payload.* = .{ .int = small_int }; const src = tree.token_locs[int_lit.token].start; -- 2.54.0 From 0de35af98b3404f0cf7cd9497f661239d197bbbf Mon Sep 17 00:00:00 2001 From: antlilja Date: Mon, 22 Jun 2020 12:14:52 +0200 Subject: [PATCH 092/295] Add duplicate checking for switch on types * Add compile error tests --- src/ir.cpp | 32 +++++++++++++++++++++++++++++++- test/compile_errors.zig | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index 3fa138ed8e1cf270176562507f8a87e40d094236..01c7936f75277b739db930798d6648166b1a50c1 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -28861,7 +28861,37 @@ static IrInstGen *ir_analyze_instruction_check_switch_prongs(IrAnalyze *ira, ir_add_error(ira, &instruction->base.base, buf_sprintf("else prong required when switching on type '%s'", buf_ptr(&switch_type->name))); return ira->codegen->invalid_inst_gen; - } + } else if(switch_type->id == ZigTypeIdMetaType) { + HashMap prevs; + // HashMap doubles capacity when reaching 60% capacity, + // because we know the size at init we can avoid reallocation by doubling it here + prevs.init(instruction->range_count * 2); + for (size_t range_i = 0; range_i < instruction->range_count; range_i += 1) { + IrInstSrcCheckSwitchProngsRange *range = &instruction->ranges[range_i]; + + IrInstGen *value = range->start->child; + IrInstGen *casted_value = ir_implicit_cast(ira, value, switch_type); + if (type_is_invalid(casted_value->value->type)) { + prevs.deinit(); + return ira->codegen->invalid_inst_gen; + } + + ZigValue *const_expr_val = ir_resolve_const(ira, casted_value, UndefBad); + if (!const_expr_val) { + prevs.deinit(); + return ira->codegen->invalid_inst_gen; + } + + auto entry = prevs.put_unique(const_expr_val->data.x_type, value); + if(entry != nullptr) { + ErrorMsg *msg = ir_add_error(ira, &value->base, buf_sprintf("duplicate switch value")); + add_error_note(ira->codegen, msg, entry->value->base.source_node, buf_sprintf("previous value is here")); + prevs.deinit(); + return ira->codegen->invalid_inst_gen; + } + } + prevs.deinit(); + } return ir_const_void(ira, &instruction->base.base); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index e3dd1f0d8f4161fc09ac7f132faba42bfae1c2f9..e8b7e610ee70e6ce82201eb3369606d6fbcb9acb 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -4362,6 +4362,40 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:5:14: note: previous value is here", }); + cases.add("switch expression - duplicate type", + \\fn foo(comptime T: type, x: T) u8 { + \\ return switch (T) { + \\ u32 => 0, + \\ u64 => 1, + \\ u32 => 2, + \\ else => 3, + \\ }; + \\} + \\export fn entry() usize { return @sizeOf(@TypeOf(foo(u32, 0))); } + , &[_][]const u8{ + "tmp.zig:5:9: error: duplicate switch value", + "tmp.zig:3:9: note: previous value is here", + }); + + cases.add("switch expression - duplicate type (struct alias)", + \\const Test = struct { + \\ bar: i32, + \\}; + \\const Test2 = Test; + \\fn foo(comptime T: type, x: T) u8 { + \\ return switch (T) { + \\ Test => 0, + \\ u64 => 1, + \\ Test2 => 2, + \\ else => 3, + \\ }; + \\} + \\export fn entry() usize { return @sizeOf(@TypeOf(foo(u32, 0))); } + , &[_][]const u8{ + "tmp.zig:9:9: error: duplicate switch value", + "tmp.zig:7:9: note: previous value is here", + }); + cases.add("switch expression - switch on pointer type with no else", \\fn foo(x: *u8) void { \\ switch (x) { -- 2.54.0 From 66e5205047576768f075ec8f016f6fe21ce71bd0 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jun 2020 21:54:36 +0200 Subject: [PATCH 093/295] Refactor PreopenList.find() This commit generalizes `std.fs.wasi.PreopenList.find(...)` allowing search by `std.fs.wasi.PreopenType` union type rather than by dir name. In the future releases of WASI, it is expected to have more preopen types (or capabilities) than just directories. This commit aligns itself with that vision. This is a potentially breaking change. However, since `std.fs.wasi.PreopenList` wasn't made part of any Zig release yet, I think we should be OK to introduce those changes without pointing to any deprecations. --- doc/langref.html.in | 2 +- lib/std/fs/wasi.zig | 77 ++++++++++++++++++++++++--------------------- lib/std/testing.zig | 2 +- 3 files changed, 44 insertions(+), 37 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index aaa15e91d03758be100a0463f6c2f5c6ab3c333a..8974e6d175b6178fa544fe0e538c93aec356696e 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -9738,7 +9738,7 @@ pub fn main() !void { } {#code_end#}
$ wasmtime --dir=. preopens.wasm
-0: { .fd = 3, .Dir = '.' }
+0: Preopen{ .fd = 3, .type = PreopenType{ .Dir = '.' } }
 
{#header_close#} {#header_close#} diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index f08c74c129a36ec7049563e0c2a23b68eac1ada1..fb6e6679fd1ecbd59db8f7f9e2da0e3e4a00e96b 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -5,13 +5,39 @@ const Allocator = mem.Allocator; usingnamespace std.os.wasi; -/// Type of WASI preopen. +/// Type-tag of WASI preopen. /// /// WASI currently offers only `Dir` as a valid preopen resource. -pub const PreopenType = enum { +pub const PreopenTypeTag = enum { Dir, }; +/// Type of WASI preopen. +/// +/// WASI currently offers only `Dir` as a valid preopen resource. +pub const PreopenType = union(PreopenTypeTag) { + /// Preopened directory type. + Dir: []const u8, + + const Self = @This(); + + pub fn eql(self: Self, other: PreopenType) bool { + if (!mem.eql(u8, @tagName(self), @tagName(other))) return false; + + switch (self) { + PreopenTypeTag.Dir => |this_path| return mem.eql(u8, this_path, other.Dir), + } + } + + pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: var) !void { + try out_stream.print("PreopenType{{ ", .{}); + switch (self) { + PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{path}), + } + return out_stream.print(" }}", .{}); + } +}; + /// WASI preopen struct. This struct consists of a WASI file descriptor /// and type of WASI preopen. It can be obtained directly from the WASI /// runtime using `PreopenList.populate()` method. @@ -20,29 +46,15 @@ pub const Preopen = struct { fd: fd_t, /// Type of the preopen. - @"type": union(PreopenType) { - /// Path to a preopened directory. - Dir: []const u8, - }, + @"type": PreopenType, - const Self = @This(); - - /// Construct new `Preopen` instance of type `PreopenType.Dir` from - /// WASI file descriptor and WASI path. - pub fn newDir(fd: fd_t, path: []const u8) Self { - return Self{ + /// Construct new `Preopen` instance. + pub fn new(fd: fd_t, preopen_type: PreopenType) Preopen { + return Preopen{ .fd = fd, - .@"type" = .{ .Dir = path }, + .@"type" = preopen_type, }; } - - pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: var) !void { - try out_stream.print("{{ .fd = {}, ", .{self.fd}); - switch (self.@"type") { - PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{path}), - } - return out_stream.print(" }}", .{}); - } }; /// Dynamically-sized array list of WASI preopens. This struct is a @@ -113,24 +125,18 @@ pub const PreopenList = struct { ESUCCESS => {}, else => |err| return os.unexpectedErrno(err), } - const preopen = Preopen.newDir(fd, path_buf); + const preopen = Preopen.new(fd, PreopenType{ .Dir = path_buf }); try self.buffer.append(preopen); fd += 1; } } - /// Find preopen by path. If the preopen exists, return it. + /// Find preopen by type. If the preopen exists, return it. /// Otherwise, return `null`. - /// - /// TODO make the function more generic by searching by `PreopenType` union. This will - /// be needed in the future when WASI extends its capabilities to resources - /// other than preopened directories. - pub fn find(self: Self, path: []const u8) ?*const Preopen { - for (self.buffer.items) |preopen| { - switch (preopen.@"type") { - PreopenType.Dir => |preopen_path| { - if (mem.eql(u8, path, preopen_path)) return &preopen; - }, + pub fn find(self: Self, preopen_type: PreopenType) ?*const Preopen { + for (self.buffer.items) |*preopen| { + if (preopen.@"type".eql(preopen_type)) { + return preopen; } } return null; @@ -156,7 +162,8 @@ test "extracting WASI preopens" { try preopens.populate(); std.testing.expectEqual(@as(usize, 1), preopens.asSlice().len); - const preopen = preopens.find(".") orelse unreachable; - std.testing.expect(std.mem.eql(u8, ".", preopen.@"type".Dir)); + const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable; + std.debug.print("\n{}\n", .{preopen}); + std.testing.expect(!preopen.@"type".eql(PreopenType{ .Dir = "." })); std.testing.expectEqual(@as(usize, 3), preopen.fd); } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 2d136d56c9a3373e331437f30e0f5398b3369324..117a788e1662b3bee787f9e81c52ad270ddc3454 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -215,7 +215,7 @@ fn getCwdOrWasiPreopen() std.fs.Dir { defer preopens.deinit(); preopens.populate() catch @panic("unable to make tmp dir for testing: unable to populate preopens"); - const preopen = preopens.find(".") orelse + const preopen = preopens.find(std.fs.wasi.PreopenType{ .Dir = "." }) orelse @panic("unable to make tmp dir for testing: didn't find '.' in the preopens"); return std.fs.Dir{ .fd = preopen.fd }; -- 2.54.0 From 5fed725e0a4e8d2617f2b8fc98191103d1ebd3ea Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jun 2020 23:59:32 +0200 Subject: [PATCH 094/295] Remove some leftover debugging checks --- lib/std/fs/wasi.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index fb6e6679fd1ecbd59db8f7f9e2da0e3e4a00e96b..db4317064dc1878405e05e2a6a179640f21dab0a 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -163,7 +163,6 @@ test "extracting WASI preopens" { std.testing.expectEqual(@as(usize, 1), preopens.asSlice().len); const preopen = preopens.find(PreopenType{ .Dir = "." }) orelse unreachable; - std.debug.print("\n{}\n", .{preopen}); - std.testing.expect(!preopen.@"type".eql(PreopenType{ .Dir = "." })); + std.testing.expect(preopen.@"type".eql(PreopenType{ .Dir = "." })); std.testing.expectEqual(@as(usize, 3), preopen.fd); } -- 2.54.0 From d9c1d8fed3e121c4fe91d5aea301574ff763ef95 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Jun 2020 19:53:32 -0400 Subject: [PATCH 095/295] self-hosted: improve handling of anonymous decls * anonymous decls have automatically generated names and symbols, and participate in the same memory management as named decls. * the Ref instruction is deleted * the DeclRef instruction now takes a `[]const u8` and DeclRefStr takes an arbitrary string instruction operand. * introduce a `zir.Decl` type for ZIR Module decls which holds content_hash and name - fields that are not needed for `zir.Inst` which are created as part of semantic analysis. This improves the function signatures of Module.zig and lowers memory usage. * the Str instruction is now defined to create an anonymous Decl and reference it. --- src-self-hosted/Module.zig | 197 +++++++++++++++----------- src-self-hosted/zir.zig | 278 +++++++++++++++++-------------------- 2 files changed, 243 insertions(+), 232 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 730954b4318eb7cc7e8bf2e228a1d409c09dfad1..5bb92fc26ab65e8bba3c23eebdad40569ffec44a 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -63,6 +63,8 @@ failed_exports: std.AutoHashMap(*Export, *ErrorMsg), /// previous analysis. generation: u32 = 0, +next_anon_name_index: usize = 0, + /// Candidates for deletion. After a semantic analysis update completes, this list /// contains Decls that need to be deleted if they end up having no references to them. deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, @@ -193,8 +195,8 @@ pub const Decl = struct { .zir_module => { const zir_module = @fieldParentPtr(Scope.ZIRModule, "base", self.scope); const module = zir_module.contents.module; - const decl_inst = module.decls[self.src_index]; - return decl_inst.src; + const src_decl = module.decls[self.src_index]; + return src_decl.inst.src; }, .block => unreachable, .gen_zir => unreachable, @@ -999,9 +1001,9 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { }; const decl_name = mem.spanZ(decl.name); // We already detected deletions, so we know this will be found. - const src_decl = zir_module.findDecl(decl_name).?; - decl.src_index = src_decl.index; - self.reAnalyzeDecl(decl, src_decl.decl) catch |err| switch (err) { + const src_decl_and_index = zir_module.findDecl(decl_name).?; + decl.src_index = src_decl_and_index.index; + self.reAnalyzeDecl(decl, src_decl_and_index.decl.inst) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => continue, }; @@ -1280,10 +1282,7 @@ fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerE } } - // Decl lookup - const namespace = scope.namespace(); - const name_hash = namespace.fullyQualifiedNameHash(ident_name); - if (self.decl_table.getValue(name_hash)) |decl| { + if (self.lookupDeclName(scope, ident_name)) |decl| { const src = tree.token_locs[ident.token].start; return try self.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); } @@ -1307,24 +1306,26 @@ fn astGenStringLiteral(self: *Module, scope: *Scope, str_lit: *ast.Node.StringLi }; const src = tree.token_locs[str_lit.token].start; - const str_inst = try self.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); - return self.addZIRInst(scope, src, zir.Inst.Ref, .{ .operand = str_inst }, .{}); + return self.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); } fn astGenIntegerLiteral(self: *Module, scope: *Scope, int_lit: *ast.Node.IntegerLiteral) InnerError!*zir.Inst { const arena = scope.arena(); const tree = scope.tree(); - var bytes = tree.tokenSlice(int_lit.token); - const base = if (mem.startsWith(u8, bytes, "0x")) + const prefixed_bytes = tree.tokenSlice(int_lit.token); + const base = if (mem.startsWith(u8, prefixed_bytes, "0x")) 16 - else if (mem.startsWith(u8, bytes, "0o")) + else if (mem.startsWith(u8, prefixed_bytes, "0o")) 8 - else if (mem.startsWith(u8, bytes, "0b")) + else if (mem.startsWith(u8, prefixed_bytes, "0b")) 2 else @as(u8, 10); - if (base != 10) bytes = bytes[2..]; + const bytes = if (base == 10) + prefixed_bytes + else + prefixed_bytes[2..]; if (std.fmt.parseInt(u64, bytes, base)) |small_int| { const int_payload = try arena.create(Value.Payload.Int_u64); @@ -1647,9 +1648,9 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { // appendAssumeCapacity. try self.work_queue.ensureUnusedCapacity(src_module.decls.len); - for (src_module.decls) |decl| { - if (decl.cast(zir.Inst.Export)) |export_inst| { - _ = try self.resolveDecl(&root_scope.base, &export_inst.base); + for (src_module.decls) |src_decl| { + if (src_decl.inst.cast(zir.Inst.Export)) |export_inst| { + _ = try self.resolveDecl(&root_scope.base, src_decl); } } }, @@ -1662,7 +1663,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { => { const src_module = try self.getSrcModule(root_scope); - var exports_to_resolve = std.ArrayList(*zir.Inst).init(self.allocator); + var exports_to_resolve = std.ArrayList(*zir.Decl).init(self.allocator); defer exports_to_resolve.deinit(); // Keep track of the decls that we expect to see in this file so that @@ -1687,8 +1688,8 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { try self.markOutdatedDecl(decl); decl.contents_hash = src_decl.contents_hash; } - } else if (src_decl.cast(zir.Inst.Export)) |export_inst| { - try exports_to_resolve.append(&export_inst.base); + } else if (src_decl.inst.cast(zir.Inst.Export)) |export_inst| { + try exports_to_resolve.append(src_decl); } } { @@ -1700,8 +1701,8 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { try self.deleteDecl(kv.key); } } - for (exports_to_resolve.items) |export_inst| { - _ = try self.resolveDecl(&root_scope.base, export_inst); + for (exports_to_resolve.items) |export_decl| { + _ = try self.resolveDecl(&root_scope.base, export_decl); } }, } @@ -1945,7 +1946,7 @@ fn createNewDecl( return new_decl; } -fn analyzeNewDecl(self: *Module, new_decl: *Decl, old_inst: *zir.Inst) InnerError!void { +fn analyzeNewDecl(self: *Module, new_decl: *Decl, src_decl: *zir.Decl) InnerError!void { var decl_scope: Scope.DeclAnalysis = .{ .decl = new_decl, .arena = std.heap.ArenaAllocator.init(self.allocator), @@ -1954,7 +1955,7 @@ fn analyzeNewDecl(self: *Module, new_decl: *Decl, old_inst: *zir.Inst) InnerErro new_decl.analysis = .in_progress; - const typed_value = self.analyzeConstInst(&decl_scope.base, old_inst) catch |err| switch (err) { + const typed_value = self.analyzeConstInst(&decl_scope.base, src_decl.inst) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => { switch (new_decl.analysis) { @@ -1986,33 +1987,32 @@ fn analyzeNewDecl(self: *Module, new_decl: *Decl, old_inst: *zir.Inst) InnerErro } } -fn resolveDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { - assert(old_inst.name.len == 0); +fn resolveDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { // If the name is empty, then we make this an anonymous Decl. const scope_decl = scope.decl().?; - const new_decl = try self.allocateNewDecl(scope, scope_decl.src_index, old_inst.contents_hash); - try self.analyzeNewDecl(new_decl, old_inst); + const new_decl = try self.allocateNewDecl(scope, scope_decl.src_index, src_decl.contents_hash); + try self.analyzeNewDecl(new_decl, src_decl); return new_decl; - //const name_hash = Decl.hashSimpleName(old_inst.name); + //const name_hash = Decl.hashSimpleName(src_decl.name); //if (self.decl_table.get(name_hash)) |kv| { // const decl = kv.value; - // decl.src = old_inst.src; - // try self.reAnalyzeDecl(decl, old_inst); + // decl.src = src_decl.src; + // try self.reAnalyzeDecl(decl, src_decl); // return decl; - //} else if (old_inst.cast(zir.Inst.DeclVal)) |decl_val| { + //} else if (src_decl.cast(zir.Inst.DeclVal)) |decl_val| { // // This is just a named reference to another decl. // return self.analyzeDeclVal(scope, decl_val); //} else { - // const new_decl = try self.createNewDecl(scope, old_inst.name, old_inst.src, name_hash, old_inst.contents_hash); - // try self.analyzeNewDecl(new_decl, old_inst); + // const new_decl = try self.createNewDecl(scope, src_decl.name, src_decl.src, name_hash, src_decl.contents_hash); + // try self.analyzeNewDecl(new_decl, src_decl); // return new_decl; //} } /// Declares a dependency on the decl. -fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl { - const decl = try self.resolveDecl(scope, old_inst); +fn resolveCompleteDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { + const decl = try self.resolveDecl(scope, src_decl); switch (decl.analysis) { .unreferenced => unreachable, .in_progress => unreachable, @@ -2163,7 +2163,6 @@ fn newZIRInst( inst.* = .{ .base = .{ .tag = T.base_tag, - .name = "", .src = src, }, .positionals = positionals, @@ -2220,19 +2219,6 @@ fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) return &const_inst.base; } -fn constStr(self: *Module, scope: *Scope, src: usize, str: []const u8) !*Inst { - const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); - ty_payload.* = .{ .len = str.len }; - - const bytes_payload = try scope.arena().create(Value.Payload.Bytes); - bytes_payload.* = .{ .data = str }; - - return self.constInst(scope, src, .{ - .ty = Type.initPayload(&ty_payload.base), - .val = Value.initPayload(&bytes_payload.base), - }); -} - fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { return self.constInst(scope, src, .{ .ty = Type.initTag(.type), @@ -2339,15 +2325,10 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In .compileerror => return self.analyzeInstCompileError(scope, old_inst.cast(zir.Inst.CompileError).?), .@"const" => return self.analyzeInstConst(scope, old_inst.cast(zir.Inst.Const).?), .declref => return self.analyzeInstDeclRef(scope, old_inst.cast(zir.Inst.DeclRef).?), + .declref_str => return self.analyzeInstDeclRefStr(scope, old_inst.cast(zir.Inst.DeclRefStr).?), .declval => return self.analyzeInstDeclVal(scope, old_inst.cast(zir.Inst.DeclVal).?), .declval_in_module => return self.analyzeInstDeclValInModule(scope, old_inst.cast(zir.Inst.DeclValInModule).?), - .str => { - const bytes = old_inst.cast(zir.Inst.Str).?.positionals.bytes; - // The bytes references memory inside the ZIR module, which can get deallocated - // after semantic analysis is complete. We need the memory to be in the Decl's arena. - const arena_bytes = try scope.arena().dupe(u8, bytes); - return self.constStr(scope, old_inst.src, arena_bytes); - }, + .str => return self.analyzeInstStr(scope, old_inst.cast(zir.Inst.Str).?), .int => { const big_int = old_inst.cast(zir.Inst.Int).?.positionals.int; return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); @@ -2363,7 +2344,6 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In .@"fn" => return self.analyzeInstFn(scope, old_inst.cast(zir.Inst.Fn).?), .@"export" => return self.analyzeInstExport(scope, old_inst.cast(zir.Inst.Export).?), .primitive => return self.analyzeInstPrimitive(scope, old_inst.cast(zir.Inst.Primitive).?), - .ref => return self.analyzeInstRef(scope, old_inst.cast(zir.Inst.Ref).?), .fntype => return self.analyzeInstFnType(scope, old_inst.cast(zir.Inst.FnType).?), .intcast => return self.analyzeInstIntCast(scope, old_inst.cast(zir.Inst.IntCast).?), .bitcast => return self.analyzeInstBitCast(scope, old_inst.cast(zir.Inst.BitCast).?), @@ -2376,9 +2356,75 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In } } +fn analyzeInstStr(self: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst { + // The bytes references memory inside the ZIR module, which can get deallocated + // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. + var new_decl_arena = std.heap.ArenaAllocator.init(self.allocator); + const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); + + const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); + ty_payload.* = .{ .len = arena_bytes.len }; + + const bytes_payload = try scope.arena().create(Value.Payload.Bytes); + bytes_payload.* = .{ .data = arena_bytes }; + + const new_decl = try self.createAnonymousDecl(scope, &new_decl_arena, .{ + .ty = Type.initPayload(&ty_payload.base), + .val = Value.initPayload(&bytes_payload.base), + }); + return self.analyzeDeclRef(scope, str_inst.base.src, new_decl); +} + +fn createAnonymousDecl( + self: *Module, + scope: *Scope, + decl_arena: *std.heap.ArenaAllocator, + typed_value: TypedValue, +) !*Decl { + var name_buf: [32]u8 = undefined; + const name_index = self.getNextAnonNameIndex(); + const name = std.fmt.bufPrint(&name_buf, "unnamed_{}", .{name_index}) catch unreachable; + const name_hash = scope.namespace().fullyQualifiedNameHash(name); + const scope_decl = scope.decl().?; + const src_hash: std.zig.SrcHash = undefined; + const new_decl = try self.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash); + const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); + + decl_arena_state.* = decl_arena.state; + new_decl.typed_value = .{ + .most_recent = .{ + .typed_value = typed_value, + .arena = decl_arena_state, + }, + }; + new_decl.analysis = .complete; + new_decl.generation = self.generation; + + // TODO: This generates the Decl into the machine code file if it is of a type that is non-zero size. + // We should be able to further improve the compiler to not omit Decls which are only referenced at + // compile-time and not runtime. + if (typed_value.ty.hasCodeGenBits()) { + try self.bin_file.allocateDeclIndexes(new_decl); + try self.work_queue.writeItem(.{ .codegen_decl = new_decl }); + } + + return new_decl; +} + +fn getNextAnonNameIndex(self: *Module) usize { + return @atomicRmw(usize, &self.next_anon_name_index, .Add, 1, .Monotonic); +} + +fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*Decl { + const namespace = scope.namespace(); + const name_hash = namespace.fullyQualifiedNameHash(ident_name); + return self.decl_table.getValue(name_hash); +} + fn analyzeInstExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst { const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name); - const exported_decl = try self.resolveCompleteDecl(scope, export_inst.positionals.value); + const exported_decl = self.lookupDeclName(scope, export_inst.positionals.decl_name) orelse + return self.fail(scope, export_inst.base.src, "decl '{}' not found", .{export_inst.positionals.decl_name}); try self.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl); return self.constVoid(scope, export_inst.base.src); } @@ -2392,26 +2438,13 @@ fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.Breakpoin return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, {}); } -fn analyzeInstRef(self: *Module, scope: *Scope, inst: *zir.Inst.Ref) InnerError!*Inst { - const decl = try self.resolveCompleteDecl(scope, inst.positionals.operand); - return self.analyzeDeclRef(scope, inst.base.src, decl); -} - -fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { +fn analyzeInstDeclRefStr(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { const decl_name = try self.resolveConstString(scope, inst.positionals.name); - // This will need to get more fleshed out when there are proper structs & namespaces. - const namespace = scope.namespace(); - if (namespace.cast(Scope.File)) |scope_file| { - return self.fail(scope, inst.base.src, "TODO implement declref for zig source", .{}); - } else if (namespace.cast(Scope.ZIRModule)) |zir_module| { - const src_decl = zir_module.contents.module.findDecl(decl_name) orelse - return self.fail(scope, inst.positionals.name.src, "use of undeclared identifier '{}'", .{decl_name}); + return self.analyzeDeclRefByName(scope, inst.base.src, decl_name); +} - const decl = try self.resolveCompleteDecl(scope, src_decl.decl); - return self.analyzeDeclRef(scope, inst.base.src, decl); - } else { - unreachable; - } +fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { + return self.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name); } fn analyzeDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl { @@ -2465,6 +2498,12 @@ fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerEr }); } +fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst { + const decl = self.lookupDeclName(scope, decl_name) orelse + return self.fail(scope, src, "decl '{}' not found", .{decl_name}); + return self.analyzeDeclRef(scope, src, decl); +} + fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { const func = try self.resolveInst(scope, inst.positionals.func); if (func.ty.zigTypeTag() != .Fn) diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index b885073db0557183f2a6fd8fddd4b1580aedfd50..dec57933975d9e5fdf8e2d306e19cbf42146d986 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -12,19 +12,23 @@ const TypedValue = @import("TypedValue.zig"); const ir = @import("ir.zig"); const IrModule = @import("Module.zig"); +/// This struct is relevent only for the ZIR Module text format. It is not used for +/// semantic analysis of Zig source code. +pub const Decl = struct { + name: []const u8, + + /// Hash of slice into the source of the part after the = and before the next instruction. + contents_hash: std.zig.SrcHash, + + inst: *Inst, +}; + /// These are instructions that correspond to the ZIR text format. See `ir.Inst` for /// in-memory, analyzed instructions with types and values. -/// TODO Separate into Decl and Inst. Decl will have extra fields, and will make the -/// undefined default field value of contents_hash no longer needed. pub const Inst = struct { tag: Tag, /// Byte offset into the source. src: usize, - name: []const u8, - - /// Hash of slice into the source of the part after the = and before the next instruction. - contents_hash: std.zig.SrcHash = undefined, - /// Pre-allocated field for mapping ZIR text instructions to post-analysis instructions. analyzed_inst: *ir.Inst = undefined, @@ -37,11 +41,14 @@ pub const Inst = struct { @"const", /// Represents a pointer to a global decl by name. declref, + /// Represents a pointer to a global decl by string name. + declref_str, /// The syntax `@foo` is equivalent to `declval("foo")`. /// declval is equivalent to declref followed by deref. declval, /// Same as declval but the parameter is a `*Module.Decl` rather than a name. declval_in_module, + /// String Literal. Makes an anonymous Decl and then takes a pointer to it. str, int, ptrtoint, @@ -56,7 +63,6 @@ pub const Inst = struct { fntype, @"export", primitive, - ref, intcast, bitcast, elemptr, @@ -72,6 +78,7 @@ pub const Inst = struct { .breakpoint => Breakpoint, .call => Call, .declref => DeclRef, + .declref_str => DeclRefStr, .declval => DeclVal, .declval_in_module => DeclValInModule, .compileerror => CompileError, @@ -89,7 +96,6 @@ pub const Inst = struct { .@"fn" => Fn, .@"export" => Export, .primitive => Primitive, - .ref => Ref, .fntype => FnType, .intcast => IntCast, .bitcast => BitCast, @@ -134,6 +140,16 @@ pub const Inst = struct { pub const base_tag = Tag.declref; base: Inst, + positionals: struct { + name: []const u8, + }, + kw_args: struct {}, + }; + + pub const DeclRefStr = struct { + pub const base_tag = Tag.declref_str; + base: Inst, + positionals: struct { name: *Inst, }, @@ -316,17 +332,7 @@ pub const Inst = struct { positionals: struct { symbol_name: *Inst, - value: *Inst, - }, - kw_args: struct {}, - }; - - pub const Ref = struct { - pub const base_tag = Tag.ref; - base: Inst, - - positionals: struct { - operand: *Inst, + decl_name: []const u8, }, kw_args: struct {}, }; @@ -500,7 +506,7 @@ pub const ErrorMsg = struct { }; pub const Module = struct { - decls: []*Inst, + decls: []*Decl, arena: std.heap.ArenaAllocator, error_msg: ?ErrorMsg = null, @@ -519,10 +525,10 @@ pub const Module = struct { self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {}; } - const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize }); + const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize, name: []const u8 }); const DeclAndIndex = struct { - decl: *Inst, + decl: *Decl, index: usize, }; @@ -549,18 +555,18 @@ pub const Module = struct { try inst_table.ensureCapacity(self.decls.len); for (self.decls) |decl, decl_i| { - try inst_table.putNoClobber(decl, .{ .inst = decl, .index = null }); + try inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); - if (decl.cast(Inst.Fn)) |fn_inst| { + if (decl.inst.cast(Inst.Fn)) |fn_inst| { for (fn_inst.positionals.body.instructions) |inst, inst_i| { - try inst_table.putNoClobber(inst, .{ .inst = inst, .index = inst_i }); + try inst_table.putNoClobber(inst, .{ .inst = inst, .index = inst_i, .name = undefined }); } } } for (self.decls) |decl, i| { try stream.print("@{} ", .{decl.name}); - try self.writeInstToStream(stream, decl, &inst_table); + try self.writeInstToStream(stream, decl.inst, &inst_table); try stream.writeByte('\n'); } } @@ -568,41 +574,41 @@ pub const Module = struct { fn writeInstToStream( self: Module, stream: var, - decl: *Inst, + inst: *Inst, inst_table: *const InstPtrTable, ) @TypeOf(stream).Error!void { // TODO I tried implementing this with an inline for loop and hit a compiler bug - switch (decl.tag) { - .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table), - .call => return self.writeInstToStreamGeneric(stream, .call, decl, inst_table), - .declref => return self.writeInstToStreamGeneric(stream, .declref, decl, inst_table), - .declval => return self.writeInstToStreamGeneric(stream, .declval, decl, inst_table), - .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, decl, inst_table), - .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, decl, inst_table), - .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", decl, inst_table), - .str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table), - .int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table), - .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table), - .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, decl, inst_table), - .deref => return self.writeInstToStreamGeneric(stream, .deref, decl, inst_table), - .as => return self.writeInstToStreamGeneric(stream, .as, decl, inst_table), - .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", decl, inst_table), - .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", decl, inst_table), - .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", decl, inst_table), - .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, decl, inst_table), - .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table), - .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table), - .ref => return self.writeInstToStreamGeneric(stream, .ref, decl, inst_table), - .primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table), - .fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table), - .intcast => return self.writeInstToStreamGeneric(stream, .intcast, decl, inst_table), - .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, decl, inst_table), - .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, decl, inst_table), - .add => return self.writeInstToStreamGeneric(stream, .add, decl, inst_table), - .cmp => return self.writeInstToStreamGeneric(stream, .cmp, decl, inst_table), - .condbr => return self.writeInstToStreamGeneric(stream, .condbr, decl, inst_table), - .isnull => return self.writeInstToStreamGeneric(stream, .isnull, decl, inst_table), - .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, decl, inst_table), + switch (inst.tag) { + .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst, inst_table), + .call => return self.writeInstToStreamGeneric(stream, .call, inst, inst_table), + .declref => return self.writeInstToStreamGeneric(stream, .declref, inst, inst_table), + .declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst, inst_table), + .declval => return self.writeInstToStreamGeneric(stream, .declval, inst, inst_table), + .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst, inst_table), + .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst, inst_table), + .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst, inst_table), + .str => return self.writeInstToStreamGeneric(stream, .str, inst, inst_table), + .int => return self.writeInstToStreamGeneric(stream, .int, inst, inst_table), + .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst, inst_table), + .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst, inst_table), + .deref => return self.writeInstToStreamGeneric(stream, .deref, inst, inst_table), + .as => return self.writeInstToStreamGeneric(stream, .as, inst, inst_table), + .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", inst, inst_table), + .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", inst, inst_table), + .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", inst, inst_table), + .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, inst, inst_table), + .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", inst, inst_table), + .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", inst, inst_table), + .primitive => return self.writeInstToStreamGeneric(stream, .primitive, inst, inst_table), + .fntype => return self.writeInstToStreamGeneric(stream, .fntype, inst, inst_table), + .intcast => return self.writeInstToStreamGeneric(stream, .intcast, inst, inst_table), + .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst, inst_table), + .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst, inst_table), + .add => return self.writeInstToStreamGeneric(stream, .add, inst, inst_table), + .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst, inst_table), + .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst, inst_table), + .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst, inst_table), + .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, inst, inst_table), } } @@ -685,7 +691,7 @@ pub const Module = struct { if (info.index) |i| { try stream.print("%{}", .{info.index}); } else { - try stream.print("@{}", .{info.inst.name}); + try stream.print("@{}", .{info.name}); } } else if (inst.cast(Inst.DeclVal)) |decl_val| { try stream.print("@{}", .{decl_val.positionals.name}); @@ -732,7 +738,7 @@ const Parser = struct { arena: std.heap.ArenaAllocator, i: usize, source: [:0]const u8, - decls: std.ArrayListUnmanaged(*Inst), + decls: std.ArrayListUnmanaged(*Decl), global_name_map: *std.StringHashMap(usize), error_msg: ?ErrorMsg = null, unnamed_index: usize, @@ -761,12 +767,12 @@ const Parser = struct { skipSpace(self); try requireEatBytes(self, "="); skipSpace(self); - const inst = try parseInstruction(self, &body_context, ident); + const decl = try parseInstruction(self, &body_context, ident); const ident_index = body_context.instructions.items.len; if (try body_context.name_map.put(ident, ident_index)) |_| { return self.fail("redefinition of identifier '{}'", .{ident}); } - try body_context.instructions.append(inst); + try body_context.instructions.append(decl.inst); continue; }, ' ', '\n' => continue, @@ -916,7 +922,7 @@ const Parser = struct { return error.ParseFailure; } - fn parseInstruction(self: *Parser, body_ctx: ?*Body, name: []const u8) InnerError!*Inst { + fn parseInstruction(self: *Parser, body_ctx: ?*Body, name: []const u8) InnerError!*Decl { const contents_start = self.i; const fn_name = try skipToAndOver(self, '('); inline for (@typeInfo(Inst.Tag).Enum.fields) |field| { @@ -935,10 +941,9 @@ const Parser = struct { body_ctx: ?*Body, inst_name: []const u8, contents_start: usize, - ) InnerError!*Inst { + ) InnerError!*Decl { const inst_specific = try self.arena.allocator.create(InstType); inst_specific.base = .{ - .name = inst_name, .src = self.i, .tag = InstType.base_tag, }; @@ -988,10 +993,15 @@ const Parser = struct { } try requireEatBytes(self, ")"); - inst_specific.base.contents_hash = std.zig.hashSrc(self.source[contents_start..self.i]); + const decl = try self.arena.allocator.create(Decl); + decl.* = .{ + .name = inst_name, + .contents_hash = std.zig.hashSrc(self.source[contents_start..self.i]), + .inst = &inst_specific.base, + }; //std.debug.warn("parsed {} = '{}'\n", .{ inst_specific.base.name, inst_specific.base.contents }); - return &inst_specific.base; + return decl; } fn parseParameterGeneric(self: *Parser, comptime T: type, body_ctx: ?*Body) !T { @@ -1075,7 +1085,6 @@ const Parser = struct { const declval = try self.arena.allocator.create(Inst.DeclVal); declval.* = .{ .base = .{ - .name = try self.generateName(), .src = src, .tag = Inst.DeclVal.base_tag, }, @@ -1088,7 +1097,7 @@ const Parser = struct { if (local_ref) { return body_ctx.?.instructions.items[kv.value]; } else { - return self.decls.items[kv.value]; + return self.decls.items[kv.value].inst; } } @@ -1107,7 +1116,7 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { .old_module = &old_module, .next_auto_name = 0, .names = std.StringHashMap(void).init(allocator), - .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Inst).init(allocator), + .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), }; defer ctx.decls.deinit(allocator); defer ctx.names.deinit(); @@ -1126,10 +1135,10 @@ const EmitZIR = struct { allocator: *Allocator, arena: std.heap.ArenaAllocator, old_module: *const IrModule, - decls: std.ArrayListUnmanaged(*Inst), + decls: std.ArrayListUnmanaged(*Decl), names: std.StringHashMap(void), next_auto_name: usize, - primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Inst), + primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), fn emit(self: *EmitZIR) !void { // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced @@ -1156,22 +1165,20 @@ const EmitZIR = struct { for (src_decls.items) |ir_decl| { if (self.old_module.export_owners.getValue(ir_decl)) |exports| { for (exports) |module_export| { - const declval = try self.emitDeclVal(ir_decl.src(), mem.spanZ(module_export.exported_decl.name)); const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name); const export_inst = try self.arena.allocator.create(Inst.Export); export_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = module_export.src, .tag = Inst.Export.base_tag, }, .positionals = .{ - .symbol_name = symbol_name, - .value = declval, + .symbol_name = symbol_name.inst, + .decl_name = mem.spanZ(module_export.exported_decl.name), }, .kw_args = .{}, }; - try self.decls.append(self.allocator, &export_inst.base); + _ = try self.emitUnnamedDecl(&export_inst.base); } } else { const new_decl = try self.emitTypedValue(ir_decl.src(), ir_decl.typed_value.most_recent.typed_value); @@ -1188,7 +1195,7 @@ const EmitZIR = struct { } else if (const_inst.val.cast(Value.Payload.DeclRef)) |declref| blk: { break :blk try self.emitDeclRef(inst.src, declref.decl); } else blk: { - break :blk try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); + break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst; }; try inst_table.putNoClobber(inst, new_decl); return new_decl; @@ -1201,7 +1208,6 @@ const EmitZIR = struct { const declval = try self.arena.allocator.create(Inst.DeclVal); declval.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.DeclVal.base_tag, }, @@ -1211,12 +1217,11 @@ const EmitZIR = struct { return &declval.base; } - fn emitComptimeIntVal(self: *EmitZIR, src: usize, val: Value) !*Inst { + fn emitComptimeIntVal(self: *EmitZIR, src: usize, val: Value) !*Decl { const big_int_space = try self.arena.allocator.create(Value.BigIntSpace); const int_inst = try self.arena.allocator.create(Inst.Int); int_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.Int.base_tag, }, @@ -1225,34 +1230,29 @@ const EmitZIR = struct { }, .kw_args = .{}, }; - try self.decls.append(self.allocator, &int_inst.base); - return &int_inst.base; + return self.emitUnnamedDecl(&int_inst.base); } - fn emitDeclRef(self: *EmitZIR, src: usize, decl: *IrModule.Decl) !*Inst { - const declval = try self.emitDeclVal(src, mem.spanZ(decl.name)); - const ref_inst = try self.arena.allocator.create(Inst.Ref); - ref_inst.* = .{ + fn emitDeclRef(self: *EmitZIR, src: usize, module_decl: *IrModule.Decl) !*Inst { + const declref_inst = try self.arena.allocator.create(Inst.DeclRef); + declref_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, - .tag = Inst.Ref.base_tag, + .tag = Inst.DeclRef.base_tag, }, .positionals = .{ - .operand = declval, + .name = mem.spanZ(module_decl.name), }, .kw_args = .{}, }; - try self.decls.append(self.allocator, &ref_inst.base); - - return &ref_inst.base; + return &declref_inst.base; } - fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Inst { + fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl { const allocator = &self.arena.allocator; if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| { const decl = decl_ref.decl; - return self.emitDeclRef(src, decl); + return try self.emitUnnamedDecl(try self.emitDeclRef(src, decl)); } switch (typed_value.ty.zigTypeTag()) { .Pointer => { @@ -1279,18 +1279,16 @@ const EmitZIR = struct { const as_inst = try self.arena.allocator.create(Inst.As); as_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.As.base_tag, }, .positionals = .{ - .dest_type = try self.emitType(src, typed_value.ty), - .value = try self.emitComptimeIntVal(src, typed_value.val), + .dest_type = (try self.emitType(src, typed_value.ty)).inst, + .value = (try self.emitComptimeIntVal(src, typed_value.val)).inst, }, .kw_args = .{}, }; - - return &as_inst.base; + return self.emitUnnamedDecl(&as_inst.base); }, .Type => { const ty = typed_value.val.toType(); @@ -1316,7 +1314,6 @@ const EmitZIR = struct { const fail_inst = try self.arena.allocator.create(Inst.CompileError); fail_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.CompileError.base_tag, }, @@ -1331,7 +1328,6 @@ const EmitZIR = struct { const fail_inst = try self.arena.allocator.create(Inst.CompileError); fail_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.CompileError.base_tag, }, @@ -1352,18 +1348,16 @@ const EmitZIR = struct { const fn_inst = try self.arena.allocator.create(Inst.Fn); fn_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.Fn.base_tag, }, .positionals = .{ - .fn_type = fn_type, + .fn_type = fn_type.inst, .body = .{ .instructions = arena_instrs }, }, .kw_args = .{}, }; - try self.decls.append(self.allocator, &fn_inst.base); - return &fn_inst.base; + return self.emitUnnamedDecl(&fn_inst.base); }, .Array => { // TODO more checks to make sure this can be emitted as a string literal @@ -1379,7 +1373,6 @@ const EmitZIR = struct { const str_inst = try self.arena.allocator.create(Inst.Str); str_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.Str.base_tag, }, @@ -1388,8 +1381,7 @@ const EmitZIR = struct { }, .kw_args = .{}, }; - try self.decls.append(self.allocator, &str_inst.base); - return &str_inst.base; + return self.emitUnnamedDecl(&str_inst.base); }, .Void => return self.emitPrimitive(src, .void_value), else => |t| std.debug.panic("TODO implement emitTypedValue for {}", .{@tagName(t)}), @@ -1400,7 +1392,6 @@ const EmitZIR = struct { const new_inst = try self.arena.allocator.create(T); new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = T.base_tag, }, @@ -1429,7 +1420,6 @@ const EmitZIR = struct { } new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = inst.src, .tag = Inst.Call.base_tag, }, @@ -1447,7 +1437,6 @@ const EmitZIR = struct { const new_inst = try self.arena.allocator.create(Inst.Return); new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = inst.src, .tag = Inst.Return.base_tag, }, @@ -1466,12 +1455,12 @@ const EmitZIR = struct { const inputs = try self.arena.allocator.alloc(*Inst, old_inst.args.inputs.len); for (inputs) |*elem, i| { - elem.* = try self.emitStringLiteral(inst.src, old_inst.args.inputs[i]); + elem.* = (try self.emitStringLiteral(inst.src, old_inst.args.inputs[i])).inst; } const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.args.clobbers.len); for (clobbers) |*elem, i| { - elem.* = try self.emitStringLiteral(inst.src, old_inst.args.clobbers[i]); + elem.* = (try self.emitStringLiteral(inst.src, old_inst.args.clobbers[i])).inst; } const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len); @@ -1481,18 +1470,17 @@ const EmitZIR = struct { new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = inst.src, .tag = Inst.Asm.base_tag, }, .positionals = .{ - .asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source), - .return_type = try self.emitType(inst.src, inst.ty), + .asm_source = (try self.emitStringLiteral(inst.src, old_inst.args.asm_source)).inst, + .return_type = (try self.emitType(inst.src, inst.ty)).inst, }, .kw_args = .{ .@"volatile" = old_inst.args.is_volatile, .output = if (old_inst.args.output) |o| - try self.emitStringLiteral(inst.src, o) + (try self.emitStringLiteral(inst.src, o)).inst else null, .inputs = inputs, @@ -1507,7 +1495,6 @@ const EmitZIR = struct { const new_inst = try self.arena.allocator.create(Inst.PtrToInt); new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = inst.src, .tag = Inst.PtrToInt.base_tag, }, @@ -1523,12 +1510,11 @@ const EmitZIR = struct { const new_inst = try self.arena.allocator.create(Inst.BitCast); new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = inst.src, .tag = Inst.BitCast.base_tag, }, .positionals = .{ - .dest_type = try self.emitType(inst.src, inst.ty), + .dest_type = (try self.emitType(inst.src, inst.ty)).inst, .operand = try self.resolveInst(inst_table, old_inst.args.operand), }, .kw_args = .{}, @@ -1540,7 +1526,6 @@ const EmitZIR = struct { const new_inst = try self.arena.allocator.create(Inst.Cmp); new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = inst.src, .tag = Inst.Cmp.base_tag, }, @@ -1568,7 +1553,6 @@ const EmitZIR = struct { const new_inst = try self.arena.allocator.create(Inst.CondBr); new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = inst.src, .tag = Inst.CondBr.base_tag, }, @@ -1586,7 +1570,6 @@ const EmitZIR = struct { const new_inst = try self.arena.allocator.create(Inst.IsNull); new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = inst.src, .tag = Inst.IsNull.base_tag, }, @@ -1602,7 +1585,6 @@ const EmitZIR = struct { const new_inst = try self.arena.allocator.create(Inst.IsNonNull); new_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = inst.src, .tag = Inst.IsNonNull.base_tag, }, @@ -1619,7 +1601,7 @@ const EmitZIR = struct { } } - fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Inst { + fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Decl { switch (ty.tag()) { .isize => return self.emitPrimitive(src, .isize), .usize => return self.emitPrimitive(src, .usize), @@ -1652,26 +1634,24 @@ const EmitZIR = struct { ty.fnParamTypes(param_types); const emitted_params = try self.arena.allocator.alloc(*Inst, param_types.len); for (param_types) |param_type, i| { - emitted_params[i] = try self.emitType(src, param_type); + emitted_params[i] = (try self.emitType(src, param_type)).inst; } const fntype_inst = try self.arena.allocator.create(Inst.FnType); fntype_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.FnType.base_tag, }, .positionals = .{ .param_types = emitted_params, - .return_type = try self.emitType(src, ty.fnReturnType()), + .return_type = (try self.emitType(src, ty.fnReturnType())).inst, }, .kw_args = .{ .cc = ty.fnCallingConvention(), }, }; - try self.decls.append(self.allocator, &fntype_inst.base); - return &fntype_inst.base; + return self.emitUnnamedDecl(&fntype_inst.base); }, else => std.debug.panic("TODO implement emitType for {}", .{ty}), }, @@ -1690,13 +1670,12 @@ const EmitZIR = struct { } } - fn emitPrimitive(self: *EmitZIR, src: usize, tag: Inst.Primitive.Builtin) !*Inst { + fn emitPrimitive(self: *EmitZIR, src: usize, tag: Inst.Primitive.Builtin) !*Decl { const gop = try self.primitive_table.getOrPut(tag); if (!gop.found_existing) { const primitive_inst = try self.arena.allocator.create(Inst.Primitive); primitive_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.Primitive.base_tag, }, @@ -1705,17 +1684,15 @@ const EmitZIR = struct { }, .kw_args = .{}, }; - try self.decls.append(self.allocator, &primitive_inst.base); - gop.kv.value = &primitive_inst.base; + gop.kv.value = try self.emitUnnamedDecl(&primitive_inst.base); } return gop.kv.value; } - fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Inst { + fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Decl { const str_inst = try self.arena.allocator.create(Inst.Str); str_inst.* = .{ .base = .{ - .name = try self.autoName(), .src = src, .tag = Inst.Str.base_tag, }, @@ -1724,22 +1701,17 @@ const EmitZIR = struct { }, .kw_args = .{}, }; - try self.decls.append(self.allocator, &str_inst.base); + return self.emitUnnamedDecl(&str_inst.base); + } - const ref_inst = try self.arena.allocator.create(Inst.Ref); - ref_inst.* = .{ - .base = .{ - .name = try self.autoName(), - .src = src, - .tag = Inst.Ref.base_tag, - }, - .positionals = .{ - .operand = &str_inst.base, - }, - .kw_args = .{}, + fn emitUnnamedDecl(self: *EmitZIR, inst: *Inst) !*Decl { + const decl = try self.arena.allocator.create(Decl); + decl.* = .{ + .name = try self.autoName(), + .contents_hash = undefined, + .inst = inst, }; - try self.decls.append(self.allocator, &ref_inst.base); - - return &ref_inst.base; + try self.decls.append(self.allocator, decl); + return decl; } }; -- 2.54.0 From b1b7708cc8380ad9518715e3e285db3011f5a6a4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 23 Jun 2020 23:29:51 -0400 Subject: [PATCH 096/295] self-hosted: hook up incremental compilation to .zig source code --- src-self-hosted/Module.zig | 416 ++++++++++++++++++------------------- src-self-hosted/main.zig | 4 + 2 files changed, 207 insertions(+), 213 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 5bb92fc26ab65e8bba3c23eebdad40569ffec44a..13d1350566c6763099de6e796b9e7f3d89ab0f46 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -74,9 +74,9 @@ const DeclTable = std.HashMap(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope const WorkItem = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Decl, - /// Decl has been determined to be outdated; perform semantic analysis again. - re_analyze_decl: *Decl, /// The Decl needs to be analyzed and possibly export itself. + /// It may have already be analyzed, or it may have been determined + /// to be outdated; in this case perform semantic analysis again. analyze_decl: *Decl, }; @@ -403,6 +403,17 @@ pub const Scope = struct { } } + /// Asserts the scope is a namespace Scope and removes the Decl from the namespace. + pub fn removeDecl(base: *Scope, child: *Decl) void { + switch (base.tag) { + .file => return @fieldParentPtr(File, "base", base).removeDecl(child), + .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child), + .block => unreachable, + .gen_zir => unreachable, + .decl => unreachable, + } + } + /// Asserts the scope is a File or ZIRModule and deinitializes it, then deallocates it. pub fn destroy(base: *Scope, allocator: *Allocator) void { switch (base.tag) { @@ -462,6 +473,9 @@ pub const Scope = struct { loaded_success, }, + /// Direct children of the file. + decls: ArrayListUnmanaged(*Decl), + pub fn unload(self: *File, allocator: *Allocator) void { switch (self.status) { .never_loaded, @@ -484,10 +498,20 @@ pub const Scope = struct { } pub fn deinit(self: *File, allocator: *Allocator) void { + self.decls.deinit(allocator); self.unload(allocator); self.* = undefined; } + pub fn removeDecl(self: *File, child: *Decl) void { + for (self.decls.items) |item, i| { + if (item == child) { + _ = self.decls.swapRemove(i); + return; + } + } + } + pub fn dumpSrc(self: *File, src: usize) void { const loc = std.zig.findLineColumn(self.source.bytes, src); std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); @@ -540,6 +564,11 @@ pub const Scope = struct { loaded_success, }, + /// Even though .zir files only have 1 module, this set is still needed + /// because of anonymous Decls, which can exist in the global set, but + /// not this one. + decls: ArrayListUnmanaged(*Decl), + pub fn unload(self: *ZIRModule, allocator: *Allocator) void { switch (self.status) { .never_loaded, @@ -569,10 +598,20 @@ pub const Scope = struct { } pub fn deinit(self: *ZIRModule, allocator: *Allocator) void { + self.decls.deinit(allocator); self.unload(allocator); self.* = undefined; } + pub fn removeDecl(self: *ZIRModule, child: *Decl) void { + for (self.decls.items) |item, i| { + if (item == child) { + _ = self.decls.swapRemove(i); + return; + } + } + } + pub fn dumpSrc(self: *ZIRModule, src: usize) void { const loc = std.zig.findLineColumn(self.source.bytes, src); std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 }); @@ -700,6 +739,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .source = .{ .unloaded = {} }, .contents = .{ .not_available = {} }, .status = .never_loaded, + .decls = .{}, }; break :blk &root_scope.base; } else if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zir")) { @@ -709,6 +749,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .source = .{ .unloaded = {} }, .contents = .{ .not_available = {} }, .status = .never_loaded, + .decls = .{}, }; break :blk &root_scope.base; } else { @@ -828,13 +869,14 @@ pub fn update(self: *Module) !void { try self.performAllTheWork(); // Process the deletion set. - while (self.deletion_set.popOrNull()) |decl| { + for (self.deletion_set.items) |decl| { if (decl.dependants.items.len != 0) { decl.deletion_flag = false; continue; } try self.deleteDecl(decl); } + self.deletion_set.shrink(self.allocator, 0); self.link_error_flags = self.bin_file.error_flags; @@ -969,49 +1011,6 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { }; }, }, - .re_analyze_decl => |decl| switch (decl.analysis) { - .unreferenced => unreachable, - .in_progress => unreachable, - - .sema_failure, - .codegen_failure, - .dependency_failure, - .complete, - .codegen_failure_retryable, - .sema_failure_retryable, - => continue, - - .outdated => { - if (decl.scope.cast(Scope.File)) |file_scope| { - @panic("TODO re_analyze_decl for .zig files"); - } else if (decl.scope.cast(Scope.ZIRModule)) |zir_scope| { - const zir_module = self.getSrcModule(zir_scope) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); - self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - self.allocator, - decl.src(), - "unable to load source file '{}': {}", - .{ zir_scope.sub_file_path, @errorName(err) }, - )); - decl.analysis = .codegen_failure_retryable; - continue; - }, - }; - const decl_name = mem.spanZ(decl.name); - // We already detected deletions, so we know this will be found. - const src_decl_and_index = zir_module.findDecl(decl_name).?; - decl.src_index = src_decl_and_index.index; - self.reAnalyzeDecl(decl, src_decl_and_index.decl.inst) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => continue, - }; - } else { - unreachable; - } - }, - }, .analyze_decl => |decl| { self.ensureDeclAnalyzed(decl) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -1022,40 +1021,87 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { } fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { - switch (decl.analysis) { - .in_progress => unreachable, - .outdated => unreachable, - - .sema_failure, - .sema_failure_retryable, - .codegen_failure, - .dependency_failure, - .codegen_failure_retryable, - => return error.AnalysisFail, + const tracy = trace(@src()); + defer tracy.end(); + const subsequent_analysis = switch (decl.analysis) { .complete => return, + .in_progress => unreachable, - .unreferenced => { - self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => return error.AnalysisFail, - else => { - try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); - self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - self.allocator, - decl.src(), - "unable to analyze: {}", - .{@errorName(err)}, - )); - decl.analysis = .sema_failure_retryable; - return error.AnalysisFail; - }, - }; + .sema_failure, + .sema_failure_retryable, + .codegen_failure, + .dependency_failure, + .codegen_failure_retryable, + => return error.AnalysisFail, + + .outdated => blk: { + //std.debug.warn("re-analyzing {}\n", .{decl.name}); + + // The exports this Decl performs will be re-discovered, so we remove them here + // prior to re-analysis. + self.deleteDeclExports(decl); + // Dependencies will be re-discovered, so we remove them here prior to re-analysis. + for (decl.dependencies.items) |dep| { + dep.removeDependant(decl); + if (dep.dependants.items.len == 0) { + // We don't perform a deletion here, because this Decl or another one + // may end up referencing it before the update is complete. + assert(!dep.deletion_flag); + dep.deletion_flag = true; + try self.deletion_set.append(self.allocator, dep); + } + } + decl.dependencies.shrink(self.allocator, 0); + + break :blk true; + }, + + .unreferenced => false, + }; + + const type_changed = self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => return error.AnalysisFail, + else => { + try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); + self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + self.allocator, + decl.src(), + "unable to analyze: {}", + .{@errorName(err)}, + )); + decl.analysis = .sema_failure_retryable; + return error.AnalysisFail; }, + }; + + if (subsequent_analysis) { + // We may need to chase the dependants and re-analyze them. + // However, if the decl is a function, and the type is the same, we do not need to. + if (type_changed or decl.typed_value.most_recent.typed_value.val.tag() != .function) { + for (decl.dependants.items) |dep| { + switch (dep.analysis) { + .unreferenced => unreachable, + .in_progress => unreachable, + .outdated => continue, // already queued for update + + .dependency_failure, + .sema_failure, + .sema_failure_retryable, + .codegen_failure, + .codegen_failure_retryable, + .complete, + => if (dep.generation != self.generation) { + try self.markOutdatedDecl(dep); + }, + } + } + } } } -fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !void { +fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const tracy = trace(@src()); defer tracy.end(); @@ -1170,6 +1216,16 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !void { }; fn_payload.* = .{ .func = new_func }; + var prev_type_has_bits = false; + var type_changed = true; + + if (decl.typedValueManaged()) |tvm| { + prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); + type_changed = !tvm.typed_value.ty.eql(fn_type); + + tvm.deinit(self.allocator); + } + decl_arena_state.* = decl_arena.state; decl.typed_value = .{ .most_recent = .{ @@ -1183,11 +1239,15 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !void { decl.analysis = .complete; decl.generation = self.generation; - // We don't fully codegen the decl until later, but we do need to reserve a global - // offset table index for it. This allows us to codegen decls out of dependency order, - // increasing how many computations can be done in parallel. - try self.bin_file.allocateDeclIndexes(decl); - try self.work_queue.writeItem(.{ .codegen_decl = decl }); + if (fn_type.hasCodeGenBits()) { + // We don't fully codegen the decl until later, but we do need to reserve a global + // offset table index for it. This allows us to codegen decls out of dependency order, + // increasing how many computations can be done in parallel. + try self.bin_file.allocateDeclIndexes(decl); + try self.work_queue.writeItem(.{ .codegen_decl = decl }); + } else if (prev_type_has_bits) { + self.bin_file.freeDecl(decl); + } if (fn_proto.extern_export_inline_token) |maybe_export_token| { if (tree.token_ids[maybe_export_token] == .Keyword_export) { @@ -1198,6 +1258,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !void { try self.analyzeExport(&block_scope.base, export_src, name, decl); } } + return type_changed; }, .VarDecl => @panic("TODO var decl"), .Comptime => @panic("TODO comptime decl"), @@ -1602,40 +1663,63 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { } fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { - switch (root_scope.status) { - .never_loaded => { - const tree = try self.getAstTree(root_scope); - const decls = tree.root_node.decls(); + // We may be analyzing it for the first time, or this may be + // an incremental update. This code handles both cases. + const tree = try self.getAstTree(root_scope); + const decls = tree.root_node.decls(); - try self.work_queue.ensureUnusedCapacity(decls.len); + try self.work_queue.ensureUnusedCapacity(decls.len); + try root_scope.decls.ensureCapacity(self.allocator, decls.len); - for (decls) |src_decl, decl_i| { - if (src_decl.cast(ast.Node.FnProto)) |fn_proto| { - // We will create a Decl for it regardless of analysis status. - const name_tok = fn_proto.name_token orelse - @panic("TODO handle missing function name in the parser"); - const name_loc = tree.token_locs[name_tok]; - const name = tree.tokenSliceLoc(name_loc); - const name_hash = root_scope.fullyQualifiedNameHash(name); - const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); - const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); - if (fn_proto.extern_export_inline_token) |maybe_export_token| { - if (tree.token_ids[maybe_export_token] == .Keyword_export) { - self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); - } + // Keep track of the decls that we expect to see in this file so that + // we know which ones have been deleted. + var deleted_decls = std.AutoHashMap(*Decl, void).init(self.allocator); + defer deleted_decls.deinit(); + try deleted_decls.ensureCapacity(root_scope.decls.items.len); + for (root_scope.decls.items) |file_decl| { + deleted_decls.putAssumeCapacityNoClobber(file_decl, {}); + } + + for (decls) |src_decl, decl_i| { + if (src_decl.cast(ast.Node.FnProto)) |fn_proto| { + // We will create a Decl for it regardless of analysis status. + const name_tok = fn_proto.name_token orelse + @panic("TODO handle missing function name in the parser"); + const name_loc = tree.token_locs[name_tok]; + const name = tree.tokenSliceLoc(name_loc); + const name_hash = root_scope.fullyQualifiedNameHash(name); + const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); + if (self.decl_table.get(name_hash)) |kv| { + const decl = kv.value; + // Update the AST Node index of the decl, even if its contents are unchanged, it may + // have been re-ordered. + decl.src_index = decl_i; + deleted_decls.removeAssertDiscard(decl); + if (!srcHashEql(decl.contents_hash, contents_hash)) { + try self.markOutdatedDecl(decl); + decl.contents_hash = contents_hash; + } + } else { + const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); + root_scope.decls.appendAssumeCapacity(new_decl); + if (fn_proto.extern_export_inline_token) |maybe_export_token| { + if (tree.token_ids[maybe_export_token] == .Keyword_export) { + self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); } } - // TODO also look for global variable declarations - // TODO also look for comptime blocks and exported globals } - }, - - .unloaded_parse_failure, - .unloaded_success, - .loaded_success, - => { - @panic("TODO process update"); - }, + } + // TODO also look for global variable declarations + // TODO also look for comptime blocks and exported globals + } + { + // Handle explicitly deleted decls from the source code. Not to be confused + // with when we delete decls because they are no longer referenced. + var it = deleted_decls.iterator(); + while (it.next()) |kv| { + //std.debug.warn("noticed '{}' deleted from source\n", .{kv.key.name}); + try self.deleteDecl(kv.key); + } } } @@ -1684,7 +1768,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { const decl = kv.value; deleted_decls.removeAssertDiscard(decl); //std.debug.warn("'{}' contents: '{}'\n", .{ src_decl.name, src_decl.contents }); - if (!mem.eql(u8, &src_decl.contents_hash, &decl.contents_hash)) { + if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) { try self.markOutdatedDecl(decl); decl.contents_hash = src_decl.contents_hash; } @@ -1711,6 +1795,10 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { fn deleteDecl(self: *Module, decl: *Decl) !void { try self.deletion_set.ensureCapacity(self.allocator, self.deletion_set.items.len + decl.dependencies.items.len); + // Remove from the namespace it resides in. In the case of an anonymous Decl it will + // not be present in the set, and this does nothing. + decl.scope.removeDecl(decl); + //std.debug.warn("deleting decl '{}'\n", .{decl.name}); const name_hash = decl.fullyQualifiedNameHash(); self.decl_table.removeAssertDiscard(name_hash); @@ -1799,110 +1887,9 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { //std.debug.warn("set {} to success\n", .{decl.name}); } -fn reAnalyzeDecl(self: *Module, decl: *Decl, old_inst: *zir.Inst) InnerError!void { - switch (decl.analysis) { - .unreferenced => unreachable, - .in_progress => unreachable, - .dependency_failure, - .sema_failure, - .sema_failure_retryable, - .codegen_failure, - .codegen_failure_retryable, - .complete, - => return, - - .outdated => {}, // Decl re-analysis - } - //std.debug.warn("re-analyzing {}\n", .{decl.name}); - - // The exports this Decl performs will be re-discovered, so we remove them here - // prior to re-analysis. - self.deleteDeclExports(decl); - // Dependencies will be re-discovered, so we remove them here prior to re-analysis. - for (decl.dependencies.items) |dep| { - dep.removeDependant(decl); - if (dep.dependants.items.len == 0) { - // We don't perform a deletion here, because this Decl or another one - // may end up referencing it before the update is complete. - assert(!dep.deletion_flag); - dep.deletion_flag = true; - try self.deletion_set.append(self.allocator, dep); - } - } - decl.dependencies.shrink(self.allocator, 0); - var decl_scope: Scope.DeclAnalysis = .{ - .decl = decl, - .arena = std.heap.ArenaAllocator.init(self.allocator), - }; - errdefer decl_scope.arena.deinit(); - - const typed_value = self.analyzeConstInst(&decl_scope.base, old_inst) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - switch (decl.analysis) { - .in_progress => decl.analysis = .dependency_failure, - else => {}, - } - decl.generation = self.generation; - return error.AnalysisFail; - }, - }; - const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); - arena_state.* = decl_scope.arena.state; - - var prev_type_has_bits = false; - var type_changed = true; - - if (decl.typedValueManaged()) |tvm| { - prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); - type_changed = !tvm.typed_value.ty.eql(typed_value.ty); - - tvm.deinit(self.allocator); - } - decl.typed_value = .{ - .most_recent = .{ - .typed_value = typed_value, - .arena = arena_state, - }, - }; - decl.analysis = .complete; - decl.generation = self.generation; - if (typed_value.ty.hasCodeGenBits()) { - // We don't fully codegen the decl until later, but we do need to reserve a global - // offset table index for it. This allows us to codegen decls out of dependency order, - // increasing how many computations can be done in parallel. - try self.bin_file.allocateDeclIndexes(decl); - try self.work_queue.writeItem(.{ .codegen_decl = decl }); - } else if (prev_type_has_bits) { - self.bin_file.freeDecl(decl); - } - - // If the decl is a function, and the type is the same, we do not need - // to chase the dependants. - if (type_changed or typed_value.val.tag() != .function) { - for (decl.dependants.items) |dep| { - switch (dep.analysis) { - .unreferenced => unreachable, - .in_progress => unreachable, - .outdated => continue, // already queued for update - - .dependency_failure, - .sema_failure, - .sema_failure_retryable, - .codegen_failure, - .codegen_failure_retryable, - .complete, - => if (dep.generation != self.generation) { - try self.markOutdatedDecl(dep); - }, - } - } - } -} - fn markOutdatedDecl(self: *Module, decl: *Decl) !void { //std.debug.warn("mark {} outdated\n", .{decl.name}); - try self.work_queue.writeItem(.{ .re_analyze_decl = decl }); + try self.work_queue.writeItem(.{ .analyze_decl = decl }); if (self.failed_decls.remove(decl)) |entry| { entry.value.destroy(self.allocator); } @@ -2381,11 +2368,10 @@ fn createAnonymousDecl( decl_arena: *std.heap.ArenaAllocator, typed_value: TypedValue, ) !*Decl { - var name_buf: [32]u8 = undefined; const name_index = self.getNextAnonNameIndex(); - const name = std.fmt.bufPrint(&name_buf, "unnamed_{}", .{name_index}) catch unreachable; + const scope_decl = scope.decl().?; + const name = try std.fmt.allocPrint(self.allocator, "{}${}", .{ scope_decl.name, name_index }); const name_hash = scope.namespace().fullyQualifiedNameHash(name); - const scope_decl = scope.decl().?; const src_hash: std.zig.SrcHash = undefined; const new_decl = try self.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash); const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); @@ -3360,3 +3346,7 @@ pub const ErrorMsg = struct { self.* = undefined; } }; + +fn srcHashEql(a: std.zig.SrcHash, b: std.zig.SrcHash) bool { + return @bitCast(u128, a) == @bitCast(u128, b); +} diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index d4955c33fc3f62e2b1f94ee923bc76ec6e424ca2..04febf26c5533238f8f3a4fdc36b0c9a2c93437b 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -487,7 +487,9 @@ fn buildOutputType( } fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !void { + var timer = try std.time.Timer.start(); try module.update(); + const update_nanos = timer.read(); var errors = try module.getAllErrorsAlloc(); defer errors.deinit(module.allocator); @@ -501,6 +503,8 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo full_err_msg.msg, }); } + } else { + std.debug.print("Update completed in {} ms\n", .{update_nanos / std.time.ns_per_ms}); } if (zir_out_path) |zop| { -- 2.54.0 From 93f0bcb649087b5db5cfcba5de5faaaf59047751 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Jun 2020 01:59:19 -0400 Subject: [PATCH 097/295] building mingw-w64 for windows ARM looks in lib64/lib32 for .def files --- src/link.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/link.cpp b/src/link.cpp index 0f27cee9ab7897eda756eadaac40f54024f9c8c5..df33a5494ec99fd27bc2fd04181aa946741b02fd 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -2377,6 +2377,17 @@ static Error find_mingw_lib_def(LinkJob *lj, const char *name, Buf *out_path) { return err; } + if (!does_exist && target_is_arm(g->zig_target)) { + // Try lib32 or lib64 + const bool is_32 = target_arch_pointer_bit_width(g->zig_target->arch) == 32; + lib_path = is_32 ? "lib32" : "lib64"; + buf_resize(&override_path, 0); + buf_appendf(&override_path, "%s" OS_SEP "libc" OS_SEP "mingw" OS_SEP "%s" OS_SEP "%s.def", buf_ptr(g->zig_lib_dir), lib_path, name); + if ((err = os_file_exists(&override_path, &does_exist)) != ErrorNone) { + return err; + } + } + if (!does_exist) { // Try the generic version buf_resize(&override_path, 0); -- 2.54.0 From 3aab6012c471522e2d1cd5a456f56cb9a7addeb6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Jun 2020 02:08:08 -0400 Subject: [PATCH 098/295] Revert "building mingw-w64 for windows ARM looks in lib64/lib32 for .def files" This reverts commit 93f0bcb649087b5db5cfcba5de5faaaf59047751. This is not correct (from #mingw-w64 IRC): is there no .def file for ntdll on arm 64 bit? or does mingw-w64-crt/lib64/ntdll.def apply to both x86_64 and aarch64? andrewrk: there's none at the moment (as apps rarely link directly against ntdll, and I didn't want to guess needlessly around that one originally when I added arm64 support, before I actually had a real arm64 windows device) andrewrk: but I guess I could/should complete that now (if you need one right now, the libarm32 one probably is the closest match) --- src/link.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/link.cpp b/src/link.cpp index df33a5494ec99fd27bc2fd04181aa946741b02fd..0f27cee9ab7897eda756eadaac40f54024f9c8c5 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -2377,17 +2377,6 @@ static Error find_mingw_lib_def(LinkJob *lj, const char *name, Buf *out_path) { return err; } - if (!does_exist && target_is_arm(g->zig_target)) { - // Try lib32 or lib64 - const bool is_32 = target_arch_pointer_bit_width(g->zig_target->arch) == 32; - lib_path = is_32 ? "lib32" : "lib64"; - buf_resize(&override_path, 0); - buf_appendf(&override_path, "%s" OS_SEP "libc" OS_SEP "mingw" OS_SEP "%s" OS_SEP "%s.def", buf_ptr(g->zig_lib_dir), lib_path, name); - if ((err = os_file_exists(&override_path, &does_exist)) != ErrorNone) { - return err; - } - } - if (!does_exist) { // Try the generic version buf_resize(&override_path, 0); -- 2.54.0 From 14aa08fcd3ca6ef32fff9422969cb684cb81b9d7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Jun 2020 03:46:32 -0400 Subject: [PATCH 099/295] self-hosted: restore ZIR functionality --- src-self-hosted/Module.zig | 292 ++++++++++++++++++++----------------- src-self-hosted/main.zig | 2 +- src-self-hosted/zir.zig | 14 +- test/stage2/zir.zig | 117 +++++---------- 4 files changed, 210 insertions(+), 215 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 13d1350566c6763099de6e796b9e7f3d89ab0f46..9b3d1d23b57b9202bb72d3ad3a12b18a889c817b 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1060,21 +1060,24 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { .unreferenced => false, }; - const type_changed = self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => return error.AnalysisFail, - else => { - try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); - self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - self.allocator, - decl.src(), - "unable to analyze: {}", - .{@errorName(err)}, - )); - decl.analysis = .sema_failure_retryable; - return error.AnalysisFail; - }, - }; + const type_changed = if (self.root_scope.cast(Scope.ZIRModule)) |zir_module| + try self.analyzeZirDecl(decl, zir_module.contents.module.decls[decl.src_index]) + else + self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => return error.AnalysisFail, + else => { + try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); + self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + self.allocator, + decl.src(), + "unable to analyze: {}", + .{@errorName(err)}, + )); + decl.analysis = .sema_failure_retryable; + return error.AnalysisFail; + }, + }; if (subsequent_analysis) { // We may need to chase the dependants and re-analyze them. @@ -1724,71 +1727,63 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { } fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { - switch (root_scope.status) { - .never_loaded => { - const src_module = try self.getSrcModule(root_scope); + // We may be analyzing it for the first time, or this may be + // an incremental update. This code handles both cases. + const src_module = try self.getSrcModule(root_scope); - // Here we ensure enough queue capacity to store all the decls, so that later we can use - // appendAssumeCapacity. - try self.work_queue.ensureUnusedCapacity(src_module.decls.len); + try self.work_queue.ensureUnusedCapacity(src_module.decls.len); + try root_scope.decls.ensureCapacity(self.allocator, src_module.decls.len); - for (src_module.decls) |src_decl| { - if (src_decl.inst.cast(zir.Inst.Export)) |export_inst| { - _ = try self.resolveDecl(&root_scope.base, src_decl); - } - } - }, - - .unloaded_parse_failure, - .unloaded_sema_failure, - .unloaded_success, - .loaded_sema_failure, - .loaded_success, - => { - const src_module = try self.getSrcModule(root_scope); + var exports_to_resolve = std.ArrayList(*zir.Decl).init(self.allocator); + defer exports_to_resolve.deinit(); - var exports_to_resolve = std.ArrayList(*zir.Decl).init(self.allocator); - defer exports_to_resolve.deinit(); + // Keep track of the decls that we expect to see in this file so that + // we know which ones have been deleted. + var deleted_decls = std.AutoHashMap(*Decl, void).init(self.allocator); + defer deleted_decls.deinit(); + try deleted_decls.ensureCapacity(self.decl_table.size); + { + var it = self.decl_table.iterator(); + while (it.next()) |kv| { + deleted_decls.putAssumeCapacityNoClobber(kv.value, {}); + } + } - // Keep track of the decls that we expect to see in this file so that - // we know which ones have been deleted. - var deleted_decls = std.AutoHashMap(*Decl, void).init(self.allocator); - defer deleted_decls.deinit(); - try deleted_decls.ensureCapacity(self.decl_table.size); - { - var it = self.decl_table.iterator(); - while (it.next()) |kv| { - deleted_decls.putAssumeCapacityNoClobber(kv.value, {}); - } - } - - for (src_module.decls) |src_decl| { - const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name); - if (self.decl_table.get(name_hash)) |kv| { - const decl = kv.value; - deleted_decls.removeAssertDiscard(decl); - //std.debug.warn("'{}' contents: '{}'\n", .{ src_decl.name, src_decl.contents }); - if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) { - try self.markOutdatedDecl(decl); - decl.contents_hash = src_decl.contents_hash; - } - } else if (src_decl.inst.cast(zir.Inst.Export)) |export_inst| { - try exports_to_resolve.append(src_decl); - } - } - { - // Handle explicitly deleted decls from the source code. Not to be confused - // with when we delete decls because they are no longer referenced. - var it = deleted_decls.iterator(); - while (it.next()) |kv| { - //std.debug.warn("noticed '{}' deleted from source\n", .{kv.key.name}); - try self.deleteDecl(kv.key); - } + for (src_module.decls) |src_decl, decl_i| { + const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name); + if (self.decl_table.get(name_hash)) |kv| { + const decl = kv.value; + deleted_decls.removeAssertDiscard(decl); + //std.debug.warn("'{}' contents: '{}'\n", .{ src_decl.name, src_decl.contents }); + if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) { + try self.markOutdatedDecl(decl); + decl.contents_hash = src_decl.contents_hash; } - for (exports_to_resolve.items) |export_decl| { - _ = try self.resolveDecl(&root_scope.base, export_decl); + } else { + const new_decl = try self.createNewDecl( + &root_scope.base, + src_decl.name, + decl_i, + name_hash, + src_decl.contents_hash, + ); + root_scope.decls.appendAssumeCapacity(new_decl); + if (src_decl.inst.cast(zir.Inst.Export)) |export_inst| { + try exports_to_resolve.append(src_decl); } - }, + } + } + { + // Handle explicitly deleted decls from the source code. Not to be confused + // with when we delete decls because they are no longer referenced. + var it = deleted_decls.iterator(); + while (it.next()) |kv| { + //std.debug.warn("noticed '{}' deleted from source\n", .{kv.key.name}); + try self.deleteDecl(kv.key); + } + } + for (exports_to_resolve.items) |export_decl| { + _ = try self.resolveZirDecl(&root_scope.base, export_decl); } } @@ -1933,73 +1928,67 @@ fn createNewDecl( return new_decl; } -fn analyzeNewDecl(self: *Module, new_decl: *Decl, src_decl: *zir.Decl) InnerError!void { +fn analyzeZirDecl(self: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { var decl_scope: Scope.DeclAnalysis = .{ - .decl = new_decl, + .decl = decl, .arena = std.heap.ArenaAllocator.init(self.allocator), }; errdefer decl_scope.arena.deinit(); - new_decl.analysis = .in_progress; + decl.analysis = .in_progress; - const typed_value = self.analyzeConstInst(&decl_scope.base, src_decl.inst) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - switch (new_decl.analysis) { - .in_progress => new_decl.analysis = .dependency_failure, - else => {}, - } - new_decl.generation = self.generation; - return error.AnalysisFail; - }, - }; + const typed_value = try self.analyzeConstInst(&decl_scope.base, src_decl.inst); const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); + var prev_type_has_bits = false; + var type_changed = true; + + if (decl.typedValueManaged()) |tvm| { + prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); + type_changed = !tvm.typed_value.ty.eql(typed_value.ty); + + tvm.deinit(self.allocator); + } + arena_state.* = decl_scope.arena.state; - - new_decl.typed_value = .{ + decl.typed_value = .{ .most_recent = .{ .typed_value = typed_value, .arena = arena_state, }, }; - new_decl.analysis = .complete; - new_decl.generation = self.generation; + decl.analysis = .complete; + decl.generation = self.generation; if (typed_value.ty.hasCodeGenBits()) { // We don't fully codegen the decl until later, but we do need to reserve a global // offset table index for it. This allows us to codegen decls out of dependency order, // increasing how many computations can be done in parallel. - try self.bin_file.allocateDeclIndexes(new_decl); - try self.work_queue.writeItem(.{ .codegen_decl = new_decl }); + try self.bin_file.allocateDeclIndexes(decl); + try self.work_queue.writeItem(.{ .codegen_decl = decl }); + } else if (prev_type_has_bits) { + self.bin_file.freeDecl(decl); } + + return type_changed; } -fn resolveDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { - // If the name is empty, then we make this an anonymous Decl. - const scope_decl = scope.decl().?; - const new_decl = try self.allocateNewDecl(scope, scope_decl.src_index, src_decl.contents_hash); - try self.analyzeNewDecl(new_decl, src_decl); - return new_decl; - //const name_hash = Decl.hashSimpleName(src_decl.name); - //if (self.decl_table.get(name_hash)) |kv| { - // const decl = kv.value; - // decl.src = src_decl.src; - // try self.reAnalyzeDecl(decl, src_decl); - // return decl; - //} else if (src_decl.cast(zir.Inst.DeclVal)) |decl_val| { - // // This is just a named reference to another decl. - // return self.analyzeDeclVal(scope, decl_val); - //} else { - // const new_decl = try self.createNewDecl(scope, src_decl.name, src_decl.src, name_hash, src_decl.contents_hash); - // try self.analyzeNewDecl(new_decl, src_decl); +fn resolveZirDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { + const zir_module = self.root_scope.cast(Scope.ZIRModule).?; + const entry = zir_module.contents.module.findDecl(src_decl.name).?; + return self.resolveZirDeclHavingIndex(scope, src_decl, entry.index); +} - // return new_decl; - //} +fn resolveZirDeclHavingIndex(self: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl { + const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name); + const decl = self.decl_table.getValue(name_hash).?; + decl.src_index = src_index; + try self.ensureDeclAnalyzed(decl); + return decl; } /// Declares a dependency on the decl. -fn resolveCompleteDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { - const decl = try self.resolveDecl(scope, src_decl); +fn resolveCompleteZirDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { + const decl = try self.resolveZirDecl(scope, src_decl); switch (decl.analysis) { .unreferenced => unreachable, .in_progress => unreachable, @@ -2014,15 +2003,32 @@ fn resolveCompleteDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerE .complete => {}, } - if (scope.decl()) |scope_decl| { - try self.declareDeclDependency(scope_decl, decl); - } return decl; } -/// TODO look into removing this function +/// TODO Look into removing this function. The body is only needed for .zir files, not .zig files. fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { - return old_inst.analyzed_inst; + if (old_inst.analyzed_inst) |inst| return inst; + + // If this assert trips, the instruction that was referenced did not get properly + // analyzed before it was referenced. + const zir_module = scope.namespace().cast(Scope.ZIRModule).?; + const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: { + const decl_name = declval.positionals.name; + const entry = zir_module.contents.module.findDecl(decl_name) orelse + return self.fail(scope, old_inst.src, "decl '{}' not found", .{decl_name}); + break :blk entry; + } else blk: { + // If this assert trips, the instruction that was referenced did not get + // properly analyzed by a previous instruction analysis before it was + // referenced by the current one. + break :blk zir_module.contents.module.findInstDecl(old_inst).?; + }; + const decl = try self.resolveCompleteZirDecl(scope, entry.decl); + const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl); + const result = try self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); + old_inst.analyzed_inst = result; + return result; } fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { @@ -2071,6 +2077,7 @@ fn resolveType(self: *Module, scope: *Scope, old_inst: *zir.Inst) !Type { } fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const u8, exported_decl: *Decl) !void { + try self.ensureDeclAnalyzed(exported_decl); const typed_value = exported_decl.typed_value.most_recent.typed_value; switch (typed_value.ty.zigTypeTag()) { .Fn => {}, @@ -2439,7 +2446,7 @@ fn analyzeDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerEr const src_decl = zir_module.contents.module.findDecl(decl_name) orelse return self.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name}); - const decl = try self.resolveCompleteDecl(scope, src_decl.decl); + const decl = try self.resolveCompleteZirDecl(scope, src_decl.decl); return decl; } @@ -2555,19 +2562,31 @@ fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerErro } fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { - return self.fail(scope, fn_inst.base.src, "TODO implement ZIR fn inst", .{}); - //const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type); - //const new_func = try scope.arena().create(Fn); - //new_func.* = .{ - // .analysis = .{ .queued = fn_inst }, - // .owner_decl = scope.decl().?, - //}; - //const fn_payload = try scope.arena().create(Value.Payload.Function); - //fn_payload.* = .{ .func = new_func }; - //return self.constInst(scope, fn_inst.base.src, .{ - // .ty = fn_type, - // .val = Value.initPayload(&fn_payload.base), - //}); + const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type); + const fn_zir = blk: { + var fn_arena = std.heap.ArenaAllocator.init(self.allocator); + errdefer fn_arena.deinit(); + + const fn_zir = try scope.arena().create(Fn.ZIR); + fn_zir.* = .{ + .body = .{ + .instructions = fn_inst.positionals.body.instructions, + }, + .arena = fn_arena.state, + }; + break :blk fn_zir; + }; + const new_func = try scope.arena().create(Fn); + new_func.* = .{ + .analysis = .{ .queued = fn_zir }, + .owner_decl = scope.decl().?, + }; + const fn_payload = try scope.arena().create(Value.Payload.Function); + fn_payload.* = .{ .func = new_func }; + return self.constInst(scope, fn_inst.base.src, .{ + .ty = fn_type, + .val = Value.initPayload(&fn_payload.base), + }); } fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { @@ -3277,6 +3296,7 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err .decl => { const decl = scope.cast(Scope.DeclAnalysis).?.decl; decl.analysis = .sema_failure; + decl.generation = self.generation; self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); }, .block => { @@ -3285,12 +3305,14 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err func.analysis = .sema_failure; } else { block.decl.analysis = .sema_failure; + block.decl.generation = self.generation; } self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg); }, .gen_zir => { const gen_zir = scope.cast(Scope.GenZIR).?; gen_zir.decl.analysis = .sema_failure; + gen_zir.decl.generation = self.generation; self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); }, .zir_module => { diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 04febf26c5533238f8f3a4fdc36b0c9a2c93437b..c12f3a86dc535834467419b4f171be9708090116 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -504,7 +504,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo }); } } else { - std.debug.print("Update completed in {} ms\n", .{update_nanos / std.time.ns_per_ms}); + std.log.info(.compiler, "Update completed in {} ms\n", .{update_nanos / std.time.ns_per_ms}); } if (zir_out_path) |zop| { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index dec57933975d9e5fdf8e2d306e19cbf42146d986..a471d35c14562b71a74d78f482bbfefb6b0a6adb 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -30,7 +30,7 @@ pub const Inst = struct { /// Byte offset into the source. src: usize, /// Pre-allocated field for mapping ZIR text instructions to post-analysis instructions. - analyzed_inst: *ir.Inst = undefined, + analyzed_inst: ?*ir.Inst = null, /// These names are used directly as the instruction names in the text format. pub const Tag = enum { @@ -545,6 +545,18 @@ pub const Module = struct { return null; } + pub fn findInstDecl(self: Module, inst: *Inst) ?DeclAndIndex { + for (self.decls) |decl, i| { + if (decl.inst == inst) { + return DeclAndIndex{ + .decl = decl, + .index = i, + }; + } + } + return null; + } + /// The allocator is used for temporary storage, but this function always returns /// with no resources allocated. pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void { diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index d58b30c29db5c17813321afed557b036b05e38f1..a22c770a9a6676f34ae5083390ff316fc08407a3 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -228,17 +228,6 @@ pub fn addCases(ctx: *TestContext) void { \\@2 = int(2) \\@3 = int(3) \\ - \\@syscall_array = str("syscall") - \\@sysoutreg_array = str("={rax}") - \\@rax_array = str("{rax}") - \\@rdi_array = str("{rdi}") - \\@rcx_array = str("rcx") - \\@r11_array = str("r11") - \\@rdx_array = str("{rdx}") - \\@rsi_array = str("{rsi}") - \\@memory_array = str("memory") - \\@len_array = str("len") - \\ \\@msg = str("Hello, world!\n") \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -246,24 +235,23 @@ pub fn addCases(ctx: *TestContext) void { \\ %SYS_exit_group = int(231) \\ %exit_code = as(@usize, @0) \\ - \\ %syscall = ref(@syscall_array) - \\ %sysoutreg = ref(@sysoutreg_array) - \\ %rax = ref(@rax_array) - \\ %rdi = ref(@rdi_array) - \\ %rcx = ref(@rcx_array) - \\ %rdx = ref(@rdx_array) - \\ %rsi = ref(@rsi_array) - \\ %r11 = ref(@r11_array) - \\ %memory = ref(@memory_array) + \\ %syscall = str("syscall") + \\ %sysoutreg = str("={rax}") + \\ %rax = str("{rax}") + \\ %rdi = str("{rdi}") + \\ %rcx = str("rcx") + \\ %rdx = str("{rdx}") + \\ %rsi = str("{rsi}") + \\ %r11 = str("r11") + \\ %memory = str("memory") \\ \\ %SYS_write = as(@usize, @1) \\ %STDOUT_FILENO = as(@usize, @1) \\ - \\ %msg_ptr = ref(@msg) - \\ %msg_addr = ptrtoint(%msg_ptr) + \\ %msg_addr = ptrtoint(@msg) \\ - \\ %len_name = ref(@len_array) - \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name) + \\ %len_name = str("len") + \\ %msg_len_ptr = fieldptr(@msg, %len_name) \\ %msg_len = deref(%msg_len_ptr) \\ %rc_write = asm(%syscall, @usize, \\ volatile=1, @@ -283,8 +271,7 @@ pub fn addCases(ctx: *TestContext) void { \\}); \\ \\@9 = str("_start") - \\@10 = ref(@9) - \\@11 = export(@10, @start) + \\@11 = export(@9, "start") , \\@noreturn = primitive(noreturn) \\@void = primitive(void) @@ -294,17 +281,6 @@ pub fn addCases(ctx: *TestContext) void { \\@2 = int(2) \\@3 = int(3) \\ - \\@syscall_array = str("syscall") - \\@sysoutreg_array = str("={rax}") - \\@rax_array = str("{rax}") - \\@rdi_array = str("{rdi}") - \\@rcx_array = str("rcx") - \\@r11_array = str("r11") - \\@rdx_array = str("{rdx}") - \\@rsi_array = str("{rsi}") - \\@memory_array = str("memory") - \\@len_array = str("len") - \\ \\@msg = str("Hello, world!\n") \\@msg2 = str("HELL WORLD\n") \\ @@ -313,24 +289,23 @@ pub fn addCases(ctx: *TestContext) void { \\ %SYS_exit_group = int(231) \\ %exit_code = as(@usize, @0) \\ - \\ %syscall = ref(@syscall_array) - \\ %sysoutreg = ref(@sysoutreg_array) - \\ %rax = ref(@rax_array) - \\ %rdi = ref(@rdi_array) - \\ %rcx = ref(@rcx_array) - \\ %rdx = ref(@rdx_array) - \\ %rsi = ref(@rsi_array) - \\ %r11 = ref(@r11_array) - \\ %memory = ref(@memory_array) + \\ %syscall = str("syscall") + \\ %sysoutreg = str("={rax}") + \\ %rax = str("{rax}") + \\ %rdi = str("{rdi}") + \\ %rcx = str("rcx") + \\ %rdx = str("{rdx}") + \\ %rsi = str("{rsi}") + \\ %r11 = str("r11") + \\ %memory = str("memory") \\ \\ %SYS_write = as(@usize, @1) \\ %STDOUT_FILENO = as(@usize, @1) \\ - \\ %msg_ptr = ref(@msg2) - \\ %msg_addr = ptrtoint(%msg_ptr) + \\ %msg_addr = ptrtoint(@msg2) \\ - \\ %len_name = ref(@len_array) - \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name) + \\ %len_name = str("len") + \\ %msg_len_ptr = fieldptr(@msg2, %len_name) \\ %msg_len = deref(%msg_len_ptr) \\ %rc_write = asm(%syscall, @usize, \\ volatile=1, @@ -350,8 +325,7 @@ pub fn addCases(ctx: *TestContext) void { \\}); \\ \\@9 = str("_start") - \\@10 = ref(@9) - \\@11 = export(@10, @start) + \\@11 = export(@9, "start") , \\@noreturn = primitive(noreturn) \\@void = primitive(void) @@ -361,17 +335,6 @@ pub fn addCases(ctx: *TestContext) void { \\@2 = int(2) \\@3 = int(3) \\ - \\@syscall_array = str("syscall") - \\@sysoutreg_array = str("={rax}") - \\@rax_array = str("{rax}") - \\@rdi_array = str("{rdi}") - \\@rcx_array = str("rcx") - \\@r11_array = str("r11") - \\@rdx_array = str("{rdx}") - \\@rsi_array = str("{rsi}") - \\@memory_array = str("memory") - \\@len_array = str("len") - \\ \\@msg = str("Hello, world!\n") \\@msg2 = str("Editing the same msg2 decl but this time with a much longer message which will\ncause the data to need to be relocated in virtual address space.\n") \\ @@ -380,24 +343,23 @@ pub fn addCases(ctx: *TestContext) void { \\ %SYS_exit_group = int(231) \\ %exit_code = as(@usize, @0) \\ - \\ %syscall = ref(@syscall_array) - \\ %sysoutreg = ref(@sysoutreg_array) - \\ %rax = ref(@rax_array) - \\ %rdi = ref(@rdi_array) - \\ %rcx = ref(@rcx_array) - \\ %rdx = ref(@rdx_array) - \\ %rsi = ref(@rsi_array) - \\ %r11 = ref(@r11_array) - \\ %memory = ref(@memory_array) + \\ %syscall = str("syscall") + \\ %sysoutreg = str("={rax}") + \\ %rax = str("{rax}") + \\ %rdi = str("{rdi}") + \\ %rcx = str("rcx") + \\ %rdx = str("{rdx}") + \\ %rsi = str("{rsi}") + \\ %r11 = str("r11") + \\ %memory = str("memory") \\ \\ %SYS_write = as(@usize, @1) \\ %STDOUT_FILENO = as(@usize, @1) \\ - \\ %msg_ptr = ref(@msg2) - \\ %msg_addr = ptrtoint(%msg_ptr) + \\ %msg_addr = ptrtoint(@msg2) \\ - \\ %len_name = ref(@len_array) - \\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name) + \\ %len_name = str("len") + \\ %msg_len_ptr = fieldptr(@msg2, %len_name) \\ %msg_len = deref(%msg_len_ptr) \\ %rc_write = asm(%syscall, @usize, \\ volatile=1, @@ -417,8 +379,7 @@ pub fn addCases(ctx: *TestContext) void { \\}); \\ \\@9 = str("_start") - \\@10 = ref(@9) - \\@11 = export(@10, @start) + \\@11 = export(@9, "start") }, &[_][]const u8{ \\Hello, world! -- 2.54.0 From 50b70bd77f31bba6ffca33b6acb90186e739118e Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 24 Jun 2020 14:07:39 +0300 Subject: [PATCH 100/295] @asyncCall now requires an argument tuple --- lib/std/dwarf.zig | 2 +- lib/std/special/test_runner.zig | 2 +- lib/std/start.zig | 2 +- src/all_types.hpp | 15 +++ src/ir.cpp | 165 ++++++++++++++++++++++++++------ src/ir_print.cpp | 109 ++++++++++----------- 6 files changed, 208 insertions(+), 87 deletions(-) diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index ebb4c096f813be32b1763647bcd2a23d50ead337..24792c7ca0468bd2450afc1f87c3b5a6fa753140 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -359,7 +359,7 @@ fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, endia const F = @TypeOf(async parseFormValue(allocator, in_stream, child_form_id, endian, is_64)); var frame = try allocator.create(F); defer allocator.destroy(frame); - return await @asyncCall(frame, {}, parseFormValue, allocator, in_stream, child_form_id, endian, is_64); + return await @asyncCall(frame, {}, parseFormValue, .{ allocator, in_stream, child_form_id, endian, is_64 }); }, else => error.InvalidDebugInfo, }; diff --git a/lib/std/special/test_runner.zig b/lib/std/special/test_runner.zig index 7403cca9c2388f9cad4cc864b62d77e790b9ad73..828d3165db0b29b5205a18f6b5701f669303ef1c 100644 --- a/lib/std/special/test_runner.zig +++ b/lib/std/special/test_runner.zig @@ -35,7 +35,7 @@ pub fn main() anyerror!void { async_frame_buffer = try std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size); } const casted_fn = @ptrCast(fn () callconv(.Async) anyerror!void, test_fn.func); - break :blk await @asyncCall(async_frame_buffer, {}, casted_fn); + break :blk await @asyncCall(async_frame_buffer, {}, casted_fn, .{}); }, .blocking => { skip_count += 1; diff --git a/lib/std/start.zig b/lib/std/start.zig index 604c22101cbf2839af1ab692de3b3652ddc07073..811e05012fd06cbfe4cb245e11ffca38a6a53bfb 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -214,7 +214,7 @@ inline fn initEventLoopAndCallMain() u8 { var result: u8 = undefined; var frame: @Frame(callMainAsync) = undefined; - _ = @asyncCall(&frame, &result, callMainAsync, loop); + _ = @asyncCall(&frame, &result, callMainAsync, .{loop}); loop.run(); return result; } diff --git a/src/all_types.hpp b/src/all_types.hpp index 88c7e96943f9ec70328cefcb363b0042e2fc804b..4465bf674cac123c1e86ba33cf1e2762d141f88d 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -2641,6 +2641,7 @@ enum IrInstSrcId { IrInstSrcIdCall, IrInstSrcIdCallArgs, IrInstSrcIdCallExtra, + IrInstSrcIdAsyncCallExtra, IrInstSrcIdConst, IrInstSrcIdReturn, IrInstSrcIdContainerInitList, @@ -3255,6 +3256,20 @@ struct IrInstSrcCallExtra { ResultLoc *result_loc; }; +// This is a pass1 instruction, used by @asyncCall, when the args node +// is not a literal. +// `args` is expected to be either a struct or a tuple. +struct IrInstSrcAsyncCallExtra { + IrInstSrc base; + + CallModifier modifier; + IrInstSrc *fn_ref; + IrInstSrc *ret_ptr; + IrInstSrc *new_stack; + IrInstSrc *args; + ResultLoc *result_loc; +}; + struct IrInstGenCall { IrInstGen base; diff --git a/src/ir.cpp b/src/ir.cpp index 01c7936f75277b739db930798d6648166b1a50c1..b765ea09ff72b9e948ab6059692046bdcc231f2e 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -310,6 +310,8 @@ static void destroy_instruction_src(IrInstSrc *inst) { return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdCallExtra: return heap::c_allocator.destroy(reinterpret_cast(inst)); + case IrInstSrcIdAsyncCallExtra: + return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdUnOp: return heap::c_allocator.destroy(reinterpret_cast(inst)); case IrInstSrcIdCondBr: @@ -1173,6 +1175,10 @@ static constexpr IrInstSrcId ir_inst_id(IrInstSrcCallExtra *) { return IrInstSrcIdCallExtra; } +static constexpr IrInstSrcId ir_inst_id(IrInstSrcAsyncCallExtra *) { + return IrInstSrcIdAsyncCallExtra; +} + static constexpr IrInstSrcId ir_inst_id(IrInstSrcConst *) { return IrInstSrcIdConst; } @@ -2442,6 +2448,25 @@ static IrInstSrc *ir_build_call_extra(IrBuilderSrc *irb, Scope *scope, AstNode * return &call_instruction->base; } +static IrInstSrc *ir_build_async_call_extra(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, + CallModifier modifier, IrInstSrc *fn_ref, IrInstSrc *ret_ptr, IrInstSrc *new_stack, IrInstSrc *args, ResultLoc *result_loc) +{ + IrInstSrcAsyncCallExtra *call_instruction = ir_build_instruction(irb, scope, source_node); + call_instruction->modifier = modifier; + call_instruction->fn_ref = fn_ref; + call_instruction->ret_ptr = ret_ptr; + call_instruction->new_stack = new_stack; + call_instruction->args = args; + call_instruction->result_loc = result_loc; + + ir_ref_instruction(fn_ref, irb->current_basic_block); + if (ret_ptr != nullptr) ir_ref_instruction(ret_ptr, irb->current_basic_block); + ir_ref_instruction(new_stack, irb->current_basic_block); + ir_ref_instruction(args, irb->current_basic_block); + + return &call_instruction->base; +} + static IrInstSrc *ir_build_call_args(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc *options, IrInstSrc *fn_ref, IrInstSrc **args_ptr, size_t args_len, ResultLoc *result_loc) @@ -6183,11 +6208,10 @@ static IrInstSrc *ir_gen_this(IrBuilderSrc *irb, Scope *orig_scope, AstNode *nod static IrInstSrc *ir_gen_async_call(IrBuilderSrc *irb, Scope *scope, AstNode *await_node, AstNode *call_node, LVal lval, ResultLoc *result_loc) { - size_t arg_offset = 3; - if (call_node->data.fn_call_expr.params.length < arg_offset) { + if (call_node->data.fn_call_expr.params.length != 4) { add_node_error(irb->codegen, call_node, - buf_sprintf("expected at least %" ZIG_PRI_usize " arguments, found %" ZIG_PRI_usize, - arg_offset, call_node->data.fn_call_expr.params.length)); + buf_sprintf("expected 4 arguments, found %" ZIG_PRI_usize, + call_node->data.fn_call_expr.params.length)); return irb->codegen->invalid_inst_src; } @@ -6206,20 +6230,37 @@ static IrInstSrc *ir_gen_async_call(IrBuilderSrc *irb, Scope *scope, AstNode *aw if (fn_ref == irb->codegen->invalid_inst_src) return fn_ref; - size_t arg_count = call_node->data.fn_call_expr.params.length - arg_offset; - IrInstSrc **args = heap::c_allocator.allocate(arg_count); - for (size_t i = 0; i < arg_count; i += 1) { - AstNode *arg_node = call_node->data.fn_call_expr.params.at(i + arg_offset); - IrInstSrc *arg = ir_gen_node(irb, arg_node, scope); - if (arg == irb->codegen->invalid_inst_src) - return arg; - args[i] = arg; - } - CallModifier modifier = (await_node == nullptr) ? CallModifierAsync : CallModifierNone; bool is_async_call_builtin = true; - IrInstSrc *call = ir_build_call_src(irb, scope, call_node, nullptr, fn_ref, arg_count, args, - ret_ptr, modifier, is_async_call_builtin, bytes, result_loc); + AstNode *args_node = call_node->data.fn_call_expr.params.at(3); + if (args_node->type == NodeTypeContainerInitExpr) { + if (args_node->data.container_init_expr.kind == ContainerInitKindArray || + args_node->data.container_init_expr.entries.length == 0) + { + size_t arg_count = args_node->data.container_init_expr.entries.length; + IrInstSrc **args = heap::c_allocator.allocate(arg_count); + for (size_t i = 0; i < arg_count; i += 1) { + AstNode *arg_node = args_node->data.container_init_expr.entries.at(i); + IrInstSrc *arg = ir_gen_node(irb, arg_node, scope); + if (arg == irb->codegen->invalid_inst_src) + return arg; + args[i] = arg; + } + + IrInstSrc *call = ir_build_call_src(irb, scope, call_node, nullptr, fn_ref, arg_count, args, + ret_ptr, modifier, is_async_call_builtin, bytes, result_loc); + return ir_lval_wrap(irb, scope, call, lval, result_loc); + } else { + exec_add_error_node(irb->codegen, irb->exec, args_node, + buf_sprintf("TODO: @asyncCall with anon struct literal")); + return irb->codegen->invalid_inst_src; + } + } + IrInstSrc *args = ir_gen_node(irb, args_node, scope); + if (args == irb->codegen->invalid_inst_src) + return args; + + IrInstSrc *call = ir_build_async_call_extra(irb, scope, call_node, modifier, fn_ref, bytes, ret_ptr, args, result_loc); return ir_lval_wrap(irb, scope, call, lval, result_loc); } @@ -20236,7 +20277,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, // Fork a scope of the function with known values for the parameters. Scope *parent_scope = fn_entry->fndef_scope->base.parent; ZigFn *impl_fn = create_fn(ira->codegen, fn_proto_node); - impl_fn->param_source_nodes = heap::c_allocator.allocate(new_fn_arg_count); + buf_init_from_buf(&impl_fn->symbol_name, &fn_entry->symbol_name); impl_fn->fndef_scope = create_fndef_scope(ira->codegen, impl_fn->body_node, parent_scope, impl_fn); impl_fn->child_scope = &impl_fn->fndef_scope->base; @@ -20719,40 +20760,101 @@ static IrInstGen *ir_analyze_call_extra(IrAnalyze *ira, IrInst* source_instr, modifier, stack, stack_src, false, args_ptr, args_len, nullptr, result_loc); } -static IrInstGen *ir_analyze_instruction_call_extra(IrAnalyze *ira, IrInstSrcCallExtra *instruction) { - IrInstGen *args = instruction->args->child; +static IrInstGen *ir_analyze_async_call_extra(IrAnalyze *ira, IrInst* source_instr, CallModifier modifier, + IrInstSrc *pass1_fn_ref, IrInstSrc *ret_ptr, IrInstSrc *new_stack, IrInstGen **args_ptr, size_t args_len, ResultLoc *result_loc) +{ + IrInstGen *fn_ref = pass1_fn_ref->child; + if (type_is_invalid(fn_ref->value->type)) + return ira->codegen->invalid_inst_gen; + + if (ir_should_inline(ira->old_irb.exec, source_instr->scope)) { + ir_add_error(ira, source_instr, buf_sprintf("TODO: comptime @asyncCall")); + return ira->codegen->invalid_inst_gen; + } + + ZigFn *fn = nullptr; + if (instr_is_comptime(fn_ref)) { + if (fn_ref->value->type->id == ZigTypeIdBoundFn) { + assert(fn_ref->value->special == ConstValSpecialStatic); + fn = fn_ref->value->data.x_bound_fn.fn; + } else { + fn = ir_resolve_fn(ira, fn_ref); + } + } + + IrInstGen *ret_ptr_uncasted = nullptr; + if (ret_ptr != nullptr) { + ret_ptr_uncasted = ret_ptr->child; + if (type_is_invalid(ret_ptr_uncasted->value->type)) + return ira->codegen->invalid_inst_gen; + } + + ZigType *fn_type = (fn != nullptr) ? fn->type_entry : fn_ref->value->type; + IrInstGen *casted_new_stack = analyze_casted_new_stack(ira, source_instr, new_stack->child, + &new_stack->base, true, fn); + if (casted_new_stack != nullptr && type_is_invalid(casted_new_stack->value->type)) + return ira->codegen->invalid_inst_gen; + + IrInstGen *result = ir_analyze_async_call(ira, source_instr, fn, fn_type, fn_ref, args_ptr, args_len, + casted_new_stack, true, ret_ptr_uncasted, result_loc); + return ir_finish_anal(ira, result); +} + +static bool ir_extract_tuple_call_args(IrAnalyze *ira, IrInst *source_instr, IrInstGen *args, IrInstGen ***args_ptr, size_t *args_len) { ZigType *args_type = args->value->type; if (type_is_invalid(args_type)) - return ira->codegen->invalid_inst_gen; + return false; if (args_type->id != ZigTypeIdStruct) { ir_add_error(ira, &args->base, buf_sprintf("expected tuple or struct, found '%s'", buf_ptr(&args_type->name))); - return ira->codegen->invalid_inst_gen; + return false; } - IrInstGen **args_ptr = nullptr; - size_t args_len = 0; - if (is_tuple(args_type)) { - args_len = args_type->data.structure.src_field_count; - args_ptr = heap::c_allocator.allocate(args_len); - for (size_t i = 0; i < args_len; i += 1) { + *args_len = args_type->data.structure.src_field_count; + *args_ptr = heap::c_allocator.allocate(*args_len); + for (size_t i = 0; i < *args_len; i += 1) { TypeStructField *arg_field = args_type->data.structure.fields[i]; - args_ptr[i] = ir_analyze_struct_value_field_value(ira, &instruction->base.base, args, arg_field); - if (type_is_invalid(args_ptr[i]->value->type)) - return ira->codegen->invalid_inst_gen; + (*args_ptr)[i] = ir_analyze_struct_value_field_value(ira, source_instr, args, arg_field); + if (type_is_invalid((*args_ptr)[i]->value->type)) + return false; } } else { ir_add_error(ira, &args->base, buf_sprintf("TODO: struct args")); + return false; + } + return true; +} + +static IrInstGen *ir_analyze_instruction_call_extra(IrAnalyze *ira, IrInstSrcCallExtra *instruction) { + IrInstGen *args = instruction->args->child; + IrInstGen **args_ptr = nullptr; + size_t args_len = 0; + if (!ir_extract_tuple_call_args(ira, &instruction->base.base, args, &args_ptr, &args_len)) { return ira->codegen->invalid_inst_gen; } + IrInstGen *result = ir_analyze_call_extra(ira, &instruction->base.base, instruction->options, instruction->fn_ref, args_ptr, args_len, instruction->result_loc); heap::c_allocator.deallocate(args_ptr, args_len); return result; } +static IrInstGen *ir_analyze_instruction_async_call_extra(IrAnalyze *ira, IrInstSrcAsyncCallExtra *instruction) { + IrInstGen *args = instruction->args->child; + IrInstGen **args_ptr = nullptr; + size_t args_len = 0; + if (!ir_extract_tuple_call_args(ira, &instruction->base.base, args, &args_ptr, &args_len)) { + return ira->codegen->invalid_inst_gen; + } + + IrInstGen *result = ir_analyze_async_call_extra(ira, &instruction->base.base, instruction->modifier, + instruction->fn_ref, instruction->ret_ptr, instruction->new_stack, args_ptr, args_len, instruction->result_loc); + heap::c_allocator.deallocate(args_ptr, args_len); + return result; +} + static IrInstGen *ir_analyze_instruction_call_args(IrAnalyze *ira, IrInstSrcCallArgs *instruction) { IrInstGen **args_ptr = heap::c_allocator.allocate(instruction->args_len); for (size_t i = 0; i < instruction->args_len; i += 1) { @@ -31101,6 +31203,8 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc return ir_analyze_instruction_call_args(ira, (IrInstSrcCallArgs *)instruction); case IrInstSrcIdCallExtra: return ir_analyze_instruction_call_extra(ira, (IrInstSrcCallExtra *)instruction); + case IrInstSrcIdAsyncCallExtra: + return ir_analyze_instruction_async_call_extra(ira, (IrInstSrcAsyncCallExtra *)instruction); case IrInstSrcIdBr: return ir_analyze_instruction_br(ira, (IrInstSrcBr *)instruction); case IrInstSrcIdCondBr: @@ -31610,6 +31714,7 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) { case IrInstSrcIdDeclVar: case IrInstSrcIdStorePtr: case IrInstSrcIdCallExtra: + case IrInstSrcIdAsyncCallExtra: case IrInstSrcIdCall: case IrInstSrcIdCallArgs: case IrInstSrcIdReturn: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 27bedff47f458051486d4b63f61940bb1e39da08..0acde512f6b3a852832c97288e38a07a40e4b76c 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -5,6 +5,7 @@ * See http://opensource.org/licenses/MIT */ +#include "all_types.hpp" #include "analyze.hpp" #include "ir.hpp" #include "ir_print.hpp" @@ -55,6 +56,36 @@ struct IrPrintGen { static void ir_print_other_inst_src(IrPrintSrc *irp, IrInstSrc *inst); static void ir_print_other_inst_gen(IrPrintGen *irp, IrInstGen *inst); +static void ir_print_call_modifier(FILE *f, CallModifier modifier) { + switch (modifier) { + case CallModifierNone: + break; + case CallModifierNoSuspend: + fprintf(f, "nosuspend "); + break; + case CallModifierAsync: + fprintf(f, "async "); + break; + case CallModifierNeverTail: + fprintf(f, "notail "); + break; + case CallModifierNeverInline: + fprintf(f, "noinline "); + break; + case CallModifierAlwaysTail: + fprintf(f, "tail "); + break; + case CallModifierAlwaysInline: + fprintf(f, "inline "); + break; + case CallModifierCompileTime: + fprintf(f, "comptime "); + break; + case CallModifierBuiltin: + zig_unreachable(); + } +} + const char* ir_inst_src_type_str(IrInstSrcId id) { switch (id) { case IrInstSrcIdInvalid: @@ -97,6 +128,8 @@ const char* ir_inst_src_type_str(IrInstSrcId id) { return "SrcVarPtr"; case IrInstSrcIdCallExtra: return "SrcCallExtra"; + case IrInstSrcIdAsyncCallExtra: + return "SrcAsyncCallExtra"; case IrInstSrcIdCall: return "SrcCall"; case IrInstSrcIdCallArgs: @@ -851,6 +884,23 @@ static void ir_print_call_extra(IrPrintSrc *irp, IrInstSrcCallExtra *instruction ir_print_result_loc(irp, instruction->result_loc); } +static void ir_print_async_call_extra(IrPrintSrc *irp, IrInstSrcAsyncCallExtra *instruction) { + fprintf(irp->f, "modifier="); + ir_print_call_modifier(irp->f, instruction->modifier); + fprintf(irp->f, ", fn="); + ir_print_other_inst_src(irp, instruction->fn_ref); + if (instruction->ret_ptr != nullptr) { + fprintf(irp->f, ", ret_ptr="); + ir_print_other_inst_src(irp, instruction->ret_ptr); + } + fprintf(irp->f, ", new_stack="); + ir_print_other_inst_src(irp, instruction->new_stack); + fprintf(irp->f, ", args="); + ir_print_other_inst_src(irp, instruction->args); + fprintf(irp->f, ", result="); + ir_print_result_loc(irp, instruction->result_loc); +} + static void ir_print_call_args(IrPrintSrc *irp, IrInstSrcCallArgs *instruction) { fprintf(irp->f, "opts="); ir_print_other_inst_src(irp, instruction->options); @@ -868,33 +918,7 @@ static void ir_print_call_args(IrPrintSrc *irp, IrInstSrcCallArgs *instruction) } static void ir_print_call_src(IrPrintSrc *irp, IrInstSrcCall *call_instruction) { - switch (call_instruction->modifier) { - case CallModifierNone: - break; - case CallModifierNoSuspend: - fprintf(irp->f, "nosuspend "); - break; - case CallModifierAsync: - fprintf(irp->f, "async "); - break; - case CallModifierNeverTail: - fprintf(irp->f, "notail "); - break; - case CallModifierNeverInline: - fprintf(irp->f, "noinline "); - break; - case CallModifierAlwaysTail: - fprintf(irp->f, "tail "); - break; - case CallModifierAlwaysInline: - fprintf(irp->f, "inline "); - break; - case CallModifierCompileTime: - fprintf(irp->f, "comptime "); - break; - case CallModifierBuiltin: - zig_unreachable(); - } + ir_print_call_modifier(irp->f, call_instruction->modifier); if (call_instruction->fn_entry) { fprintf(irp->f, "%s", buf_ptr(&call_instruction->fn_entry->symbol_name)); } else { @@ -913,33 +937,7 @@ static void ir_print_call_src(IrPrintSrc *irp, IrInstSrcCall *call_instruction) } static void ir_print_call_gen(IrPrintGen *irp, IrInstGenCall *call_instruction) { - switch (call_instruction->modifier) { - case CallModifierNone: - break; - case CallModifierNoSuspend: - fprintf(irp->f, "nosuspend "); - break; - case CallModifierAsync: - fprintf(irp->f, "async "); - break; - case CallModifierNeverTail: - fprintf(irp->f, "notail "); - break; - case CallModifierNeverInline: - fprintf(irp->f, "noinline "); - break; - case CallModifierAlwaysTail: - fprintf(irp->f, "tail "); - break; - case CallModifierAlwaysInline: - fprintf(irp->f, "inline "); - break; - case CallModifierCompileTime: - fprintf(irp->f, "comptime "); - break; - case CallModifierBuiltin: - zig_unreachable(); - } + ir_print_call_modifier(irp->f, call_instruction->modifier); if (call_instruction->fn_entry) { fprintf(irp->f, "%s", buf_ptr(&call_instruction->fn_entry->symbol_name)); } else { @@ -2619,6 +2617,9 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai case IrInstSrcIdCallExtra: ir_print_call_extra(irp, (IrInstSrcCallExtra *)instruction); break; + case IrInstSrcIdAsyncCallExtra: + ir_print_async_call_extra(irp, (IrInstSrcAsyncCallExtra *)instruction); + break; case IrInstSrcIdCall: ir_print_call_src(irp, (IrInstSrcCall *)instruction); break; -- 2.54.0 From eefcd044628ea080d8fe3346ae4d01e8ed4008e6 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 24 Jun 2020 16:56:24 +0300 Subject: [PATCH 101/295] Small fixes, fixed tests, added test for argument tuple type --- src/ir.cpp | 15 ++++++++++----- test/compile_errors.zig | 19 ++++++++++++++++--- test/stage1/behavior/async_fn.zig | 26 +++++++++++++------------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index b765ea09ff72b9e948ab6059692046bdcc231f2e..5f44e5306b7b24b1f5db51c90aefbecba15145da 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -6260,7 +6260,7 @@ static IrInstSrc *ir_gen_async_call(IrBuilderSrc *irb, Scope *scope, AstNode *aw if (args == irb->codegen->invalid_inst_src) return args; - IrInstSrc *call = ir_build_async_call_extra(irb, scope, call_node, modifier, fn_ref, bytes, ret_ptr, args, result_loc); + IrInstSrc *call = ir_build_async_call_extra(irb, scope, call_node, modifier, fn_ref, ret_ptr, bytes, args, result_loc); return ir_lval_wrap(irb, scope, call, lval, result_loc); } @@ -20277,7 +20277,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, // Fork a scope of the function with known values for the parameters. Scope *parent_scope = fn_entry->fndef_scope->base.parent; ZigFn *impl_fn = create_fn(ira->codegen, fn_proto_node); - + impl_fn->param_source_nodes = heap::c_allocator.allocate(new_fn_arg_count); buf_init_from_buf(&impl_fn->symbol_name, &fn_entry->symbol_name); impl_fn->fndef_scope = create_fndef_scope(ira->codegen, impl_fn->body_node, parent_scope, impl_fn); impl_fn->child_scope = &impl_fn->fndef_scope->base; @@ -20772,11 +20772,17 @@ static IrInstGen *ir_analyze_async_call_extra(IrAnalyze *ira, IrInst* source_ins return ira->codegen->invalid_inst_gen; } + IrInstGen *first_arg_ptr = nullptr; + IrInst *first_arg_ptr_src = nullptr; ZigFn *fn = nullptr; if (instr_is_comptime(fn_ref)) { if (fn_ref->value->type->id == ZigTypeIdBoundFn) { assert(fn_ref->value->special == ConstValSpecialStatic); fn = fn_ref->value->data.x_bound_fn.fn; + first_arg_ptr = fn_ref->value->data.x_bound_fn.first_arg; + first_arg_ptr_src = fn_ref->value->data.x_bound_fn.first_arg_src; + if (type_is_invalid(first_arg_ptr->value->type)) + return ira->codegen->invalid_inst_gen; } else { fn = ir_resolve_fn(ira, fn_ref); } @@ -20795,9 +20801,8 @@ static IrInstGen *ir_analyze_async_call_extra(IrAnalyze *ira, IrInst* source_ins if (casted_new_stack != nullptr && type_is_invalid(casted_new_stack->value->type)) return ira->codegen->invalid_inst_gen; - IrInstGen *result = ir_analyze_async_call(ira, source_instr, fn, fn_type, fn_ref, args_ptr, args_len, - casted_new_stack, true, ret_ptr_uncasted, result_loc); - return ir_finish_anal(ira, result); + return ir_analyze_fn_call(ira, source_instr, fn, fn_type, fn_ref, first_arg_ptr, first_arg_ptr_src, + modifier, casted_new_stack, &new_stack->base, true, args_ptr, args_len, ret_ptr_uncasted, result_loc); } static bool ir_extract_tuple_call_args(IrAnalyze *ira, IrInst *source_instr, IrInstGen *args, IrInstGen ***args_ptr, size_t *args_len) { diff --git a/test/compile_errors.zig b/test/compile_errors.zig index e8b7e610ee70e6ce82201eb3369606d6fbcb9acb..90c970c842fd0ed6f5885e3ee691481a2b55a470 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1144,13 +1144,26 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:2:15: error: @Type not available for 'TypeInfo.Struct'", }); + cases.add("wrong type for argument tuple to @asyncCall", + \\export fn entry1() void { + \\ var frame: @Frame(foo) = undefined; + \\ @asyncCall(&frame, {}, foo, {}); + \\} + \\ + \\fn foo() i32 { + \\ return 0; + \\} + , &[_][]const u8{ + "tmp.zig:3:33: error: expected tuple or struct, found 'void'", + }); + cases.add("wrong type for result ptr to @asyncCall", \\export fn entry() void { \\ _ = async amain(); \\} \\fn amain() i32 { \\ var frame: @Frame(foo) = undefined; - \\ return await @asyncCall(&frame, false, foo); + \\ return await @asyncCall(&frame, false, foo, .{}); \\} \\fn foo() i32 { \\ return 1234; @@ -1291,7 +1304,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\export fn entry() void { \\ var ptr: fn () callconv(.Async) void = func; \\ var bytes: [64]u8 = undefined; - \\ _ = @asyncCall(&bytes, {}, ptr); + \\ _ = @asyncCall(&bytes, {}, ptr, .{}); \\} \\fn func() callconv(.Async) void {} , &[_][]const u8{ @@ -1467,7 +1480,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\export fn entry() void { \\ var ptr = afunc; \\ var bytes: [100]u8 align(16) = undefined; - \\ _ = @asyncCall(&bytes, {}, ptr); + \\ _ = @asyncCall(&bytes, {}, ptr, .{}); \\} \\fn afunc() void { } , &[_][]const u8{ diff --git a/test/stage1/behavior/async_fn.zig b/test/stage1/behavior/async_fn.zig index 30df7f64aa4be6b157dd4f87a9a1af6a5d3866c2..4214ed84d23ae96fe1d4312f043ad655887aac52 100644 --- a/test/stage1/behavior/async_fn.zig +++ b/test/stage1/behavior/async_fn.zig @@ -282,7 +282,7 @@ test "async fn pointer in a struct field" { }; var foo = Foo{ .bar = simpleAsyncFn2 }; var bytes: [64]u8 align(16) = undefined; - const f = @asyncCall(&bytes, {}, foo.bar, &data); + const f = @asyncCall(&bytes, {}, foo.bar, .{&data}); comptime expect(@TypeOf(f) == anyframe->void); expect(data == 2); resume f; @@ -318,7 +318,7 @@ test "@asyncCall with return type" { var foo = Foo{ .bar = Foo.middle }; var bytes: [150]u8 align(16) = undefined; var aresult: i32 = 0; - _ = @asyncCall(&bytes, &aresult, foo.bar); + _ = @asyncCall(&bytes, &aresult, foo.bar, .{}); expect(aresult == 0); resume Foo.global_frame; expect(aresult == 1234); @@ -332,7 +332,7 @@ test "async fn with inferred error set" { var frame: [1]@Frame(middle) = undefined; var fn_ptr = middle; var result: @TypeOf(fn_ptr).ReturnType.ErrorSet!void = undefined; - _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, fn_ptr); + _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, fn_ptr, .{}); resume global_frame; std.testing.expectError(error.Fail, result); } @@ -827,7 +827,7 @@ test "cast fn to async fn when it is inferred to be async" { ptr = func; var buf: [100]u8 align(16) = undefined; var result: i32 = undefined; - const f = @asyncCall(&buf, &result, ptr); + const f = @asyncCall(&buf, &result, ptr, .{}); _ = await f; expect(result == 1234); ok = true; @@ -855,7 +855,7 @@ test "cast fn to async fn when it is inferred to be async, awaited directly" { ptr = func; var buf: [100]u8 align(16) = undefined; var result: i32 = undefined; - _ = await @asyncCall(&buf, &result, ptr); + _ = await @asyncCall(&buf, &result, ptr, .{}); expect(result == 1234); ok = true; } @@ -951,7 +951,7 @@ test "@asyncCall with comptime-known function, but not awaited directly" { fn doTheTest() void { var frame: [1]@Frame(middle) = undefined; var result: @TypeOf(middle).ReturnType.ErrorSet!void = undefined; - _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, middle); + _ = @asyncCall(std.mem.sliceAsBytes(frame[0..]), &result, middle, .{}); resume global_frame; std.testing.expectError(error.Fail, result); } @@ -982,7 +982,7 @@ test "@asyncCall with actual frame instead of byte buffer" { }; var frame: @Frame(S.func) = undefined; var result: i32 = undefined; - const ptr = @asyncCall(&frame, &result, S.func); + const ptr = @asyncCall(&frame, &result, S.func, .{}); resume ptr; expect(result == 1234); } @@ -1005,7 +1005,7 @@ test "@asyncCall using the result location inside the frame" { }; var foo = Foo{ .bar = S.simple2 }; var bytes: [64]u8 align(16) = undefined; - const f = @asyncCall(&bytes, {}, foo.bar, &data); + const f = @asyncCall(&bytes, {}, foo.bar, .{&data}); comptime expect(@TypeOf(f) == anyframe->i32); expect(data == 2); resume f; @@ -1042,7 +1042,7 @@ test "using @TypeOf on a generic function call" { } const F = @TypeOf(async amain(x - 1)); const frame = @intToPtr(*F, @ptrToInt(&buf)); - return await @asyncCall(frame, {}, amain, x - 1); + return await @asyncCall(frame, {}, amain, .{x - 1}); } }; _ = async S.amain(@as(u32, 1)); @@ -1067,7 +1067,7 @@ test "recursive call of await @asyncCall with struct return type" { } const F = @TypeOf(async amain(x - 1)); const frame = @intToPtr(*F, @ptrToInt(&buf)); - return await @asyncCall(frame, {}, amain, x - 1); + return await @asyncCall(frame, {}, amain, .{x - 1}); } const Foo = struct { @@ -1078,7 +1078,7 @@ test "recursive call of await @asyncCall with struct return type" { }; var res: S.Foo = undefined; var frame: @TypeOf(async S.amain(@as(u32, 1))) = undefined; - _ = @asyncCall(&frame, &res, S.amain, @as(u32, 1)); + _ = @asyncCall(&frame, &res, S.amain, .{@as(u32, 1)}); resume S.global_frame; expect(S.global_ok); expect(res.x == 1); @@ -1377,7 +1377,7 @@ test "async function call resolves target fn frame, comptime func" { fn foo() anyerror!void { const stack_size = 1000; var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; - return await @asyncCall(&stack_frame, {}, bar); + return await @asyncCall(&stack_frame, {}, bar, .{}); } fn bar() anyerror!void { @@ -1400,7 +1400,7 @@ test "async function call resolves target fn frame, runtime func" { const stack_size = 1000; var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; var func: fn () callconv(.Async) anyerror!void = bar; - return await @asyncCall(&stack_frame, {}, func); + return await @asyncCall(&stack_frame, {}, func, .{}); } fn bar() anyerror!void { -- 2.54.0 From 7f342451b6f2339da15fec199814365391500c11 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 24 Jun 2020 18:43:11 +0300 Subject: [PATCH 102/295] Fixed @asyncCall in runtime safety test --- test/runtime_safety.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index 2e50c3a84c439c0d9217c13ce24b433a1147e690..e60d115a006e72c8f7b74ad704560f75f14b7df2 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -280,7 +280,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\pub fn main() void { \\ var bytes: [1]u8 align(16) = undefined; \\ var ptr = other; - \\ var frame = @asyncCall(&bytes, {}, ptr); + \\ var frame = @asyncCall(&bytes, {}, ptr, .{}); \\} \\fn other() callconv(.Async) void { \\ suspend; -- 2.54.0 From ff2ddcf38d7a2f0fbed40f645278a09ca940a68a Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 24 Jun 2020 19:01:38 +0300 Subject: [PATCH 103/295] Updated @asyncCall docs --- doc/langref.html.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 8974e6d175b6178fa544fe0e538c93aec356696e..95b7171c44a0d8692cb3721d69701bbb5767eb44 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -6689,7 +6689,7 @@ comptime { {#header_close#} {#header_open|@asyncCall#} -
{#syntax#}@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: ...) anyframe->T{#endsyntax#}
+
{#syntax#}@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: var) anyframe->T{#endsyntax#}

{#syntax#}@asyncCall{#endsyntax#} performs an {#syntax#}async{#endsyntax#} call on a function pointer, which may or may not be an {#link|async function|Async Functions#}. @@ -6716,7 +6716,7 @@ test "async fn pointer in a struct field" { }; var foo = Foo{ .bar = func }; var bytes: [64]u8 align(@alignOf(@Frame(func))) = undefined; - const f = @asyncCall(&bytes, {}, foo.bar, &data); + const f = @asyncCall(&bytes, {}, foo.bar, .{&data}); assert(data == 2); resume f; assert(data == 4); -- 2.54.0 From 3a2cc5decf5674b2d85c15b7d051c2f3bcf30904 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Tue, 23 Jun 2020 23:56:27 -0600 Subject: [PATCH 104/295] azure-pipelines: fix msys2 install --- ci/azure/pipelines.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ci/azure/pipelines.yml b/ci/azure/pipelines.yml index fdf9d92d7d30d0125c3b3cb839612b450c937606..388d6db0449a1ce9539ead32fede0cda0deb0534 100644 --- a/ci/azure/pipelines.yml +++ b/ci/azure/pipelines.yml @@ -40,12 +40,20 @@ jobs: timeoutInMinutes: 360 steps: + - powershell: | + (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2020-06-02/msys2-base-x86_64-20200602.sfx.exe", "sfx.exe") + .\sfx.exe -y -o\ + del sfx.exe + displayName: Download/Extract/Install MSYS2 - script: | - git clone https://github.com/msys2/msys2-ci-base.git %CD:~0,2%\msys64 - %CD:~0,2%\msys64\usr\bin\rm -rf %CD:~0,2%\msys64\.git - set PATH=%CD:~0,2%\msys64\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem - %CD:~0,2%\msys64\usr\bin\pacman --noconfirm -Syyuu - displayName: Install and Update MSYS2 + @REM install updated filesystem package first without dependency checking + @REM because of: https://github.com/msys2/MSYS2-packages/issues/2021 + %CD:~0,2%\msys64\usr\bin\bash -lc "pacman --noconfirm -Sydd filesystem" + displayName: Workaround filesystem dash MSYS2 dependency issue + - script: | + %CD:~0,2%\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" + %CD:~0,2%\msys64\usr\bin\bash -lc "pacman --noconfirm -Syuu" + displayName: Update MSYS2 - task: DownloadSecureFile@1 inputs: secureFile: s3cfg -- 2.54.0 From 489c31b6f5ad74bfa0ff1d91c049d0c11f931d94 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Jun 2020 14:36:10 -0400 Subject: [PATCH 105/295] azure ci: install tar and xz with pacman --- ci/azure/windows_msvc_install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/azure/windows_msvc_install b/ci/azure/windows_msvc_install index ece3bbde114da94f1abe8ee5d1f90e30658a182f..8ac5181cab07d9de6d406fd1c61a018a0de98cdc 100644 --- a/ci/azure/windows_msvc_install +++ b/ci/azure/windows_msvc_install @@ -4,7 +4,7 @@ set -x set -e pacman -Su --needed --noconfirm -pacman -S --needed --noconfirm wget p7zip python3-pip +pacman -S --needed --noconfirm wget p7zip python3-pip tar xz pip install s3cmd wget -nv "https://ziglang.org/deps/llvm%2bclang%2blld-10.0.0-x86_64-windows-msvc-release-mt.tar.xz" tar xf llvm+clang+lld-10.0.0-x86_64-windows-msvc-release-mt.tar.xz -- 2.54.0 From be78b7b648fdda7e0b052aa96eb4551edc64ef18 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jun 2020 09:59:05 +0200 Subject: [PATCH 106/295] Implement fstatat targeting WASI Also, add more informative `@compileError` in a few `std.os` functions that would otherwise yield a cryptic compile error when targeting WASI. Finally, enhance docs in a few places and add test case for `fstatat`. --- lib/std/os.zig | 92 ++++++++++++++++++++++++++++++++++----------- lib/std/os/test.zig | 22 +++++++++++ 2 files changed, 92 insertions(+), 22 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 59cde5d6067ce1209f797d82a5afa8770e3b0037..aa85f9483b3e824265a6aeb0650f3c2eda79d808 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1665,13 +1665,15 @@ pub const UnlinkError = error{ /// Delete a name and possibly the file it refers to. /// See also `unlinkC`. pub fn unlink(file_path: []const u8) UnlinkError!void { + if (builtin.os.tag == .wasi) { + @compileError("unlink is not supported in WASI; use unlinkat instead"); + } if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return windows.DeleteFileW(file_path_w.span().ptr); - } else { - const file_path_c = try toPosixPath(file_path); - return unlinkZ(&file_path_c); } + const file_path_c = try toPosixPath(file_path); + return unlinkZ(&file_path_c); } pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); @@ -1722,6 +1724,8 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo pub const unlinkatC = @compileError("deprecated: renamed to unlinkatZ"); +/// WASI-only. Same as `unlinkat` but targeting WASI. +/// See also `unlinkat`. pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { const remove_dir = (flags & AT_REMOVEDIR) != 0; const res = if (remove_dir) @@ -1868,15 +1872,17 @@ const RenameError = error{ /// Change the name or location of a file. pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { + if (builtin.os.tag == .wasi) { + @compileError("rename is not supported in WASI; use renameat instead"); + } if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); return renameW(old_path_w.span().ptr, new_path_w.span().ptr); - } else { - const old_path_c = try toPosixPath(old_path); - const new_path_c = try toPosixPath(new_path); - return renameZ(&old_path_c, &new_path_c); } + const old_path_c = try toPosixPath(old_path); + const new_path_c = try toPosixPath(new_path); + return renameZ(&old_path_c, &new_path_c); } pub const renameC = @compileError("deprecated: renamed to renameZ"); @@ -1939,7 +1945,8 @@ pub fn renameat( } } -/// Same as `renameat` expect only WASI. +/// WASI-only. Same as `renameat` expect targeting WASI. +/// See also `renameat`. pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void { switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) { wasi.ESUCCESS => return, @@ -2144,14 +2151,16 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirErro /// Create a directory. /// `mode` is ignored on Windows. pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { + if (builtin.os.tag == .wasi) { + @compileError("mkdir is not supported in WASI; use mkdirat instead"); + } if (builtin.os.tag == .windows) { const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); windows.CloseHandle(sub_dir_handle); return; - } else { - const dir_path_c = try toPosixPath(dir_path); - return mkdirZ(&dir_path_c, mode); } + const dir_path_c = try toPosixPath(dir_path); + return mkdirZ(&dir_path_c, mode); } /// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. @@ -2197,13 +2206,15 @@ pub const DeleteDirError = error{ /// Deletes an empty directory. pub fn rmdir(dir_path: []const u8) DeleteDirError!void { + if (builtin.os.tag == .wasi) { + @compileError("rmdir is not supported in WASI; use unlinkat instead"); + } if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); return windows.RemoveDirectoryW(dir_path_w.span().ptr); - } else { - const dir_path_c = try toPosixPath(dir_path); - return rmdirZ(&dir_path_c); } + const dir_path_c = try toPosixPath(dir_path); + return rmdirZ(&dir_path_c); } pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); @@ -2246,13 +2257,15 @@ pub const ChangeCurDirError = error{ /// Changes the current working directory of the calling process. /// `dir_path` is recommended to be a UTF-8 encoded string. pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { + if (builtin.os.tag == .wasi) { + @compileError("chdir is not supported in WASI"); + } if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); @compileError("TODO implement chdir for Windows"); - } else { - const dir_path_c = try toPosixPath(dir_path); - return chdirZ(&dir_path_c); } + const dir_path_c = try toPosixPath(dir_path); + return chdirZ(&dir_path_c); } pub const chdirC = @compileError("deprecated: renamed to chdirZ"); @@ -2310,22 +2323,30 @@ pub const ReadLinkError = error{ /// Read value of a symbolic link. /// The return value is a slice of `out_buffer` from index 0. pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { + if (builtin.os.tag == .wasi) { + @compileError("readlink is not supported in WASI; use readlinkat instead"); + } if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); - @compileError("TODO implement readlink for Windows"); - } else { - const file_path_c = try toPosixPath(file_path); - return readlinkZ(&file_path_c, out_buffer); + return readlinkW(file_path_w.span().ptr, out_buffer); } + const file_path_c = try toPosixPath(file_path); + return readlinkZ(&file_path_c, out_buffer); } pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); +/// Windows-only. Same as `readlink` expecte `file_path` is null-terminated, WTF16 encoded. +/// Seel also `readlinkZ`. +pub fn readlinkW(file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { + @compileError("TODO implement readlink for Windows"); +} + /// Same as `readlink` except `file_path` is null-terminated. pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); - @compileError("TODO implement readlink for Windows"); + return readlinkW(file_path_w.span().ptr, out_buffer); } const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); switch (errno(rc)) { @@ -3055,6 +3076,7 @@ pub const FStatError = error{ AccessDenied, } || UnexpectedError; +/// Return information about a file descriptor. pub fn fstat(fd: fd_t) FStatError!Stat { if (builtin.os.tag == .wasi) { var stat: wasi.filestat_t = undefined; @@ -3081,13 +3103,39 @@ pub fn fstat(fd: fd_t) FStatError!Stat { pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound }; +/// Similar to `fstat`, but returns stat of a resource pointed to by `pathname` +/// which is relative to `dirfd` handle. +/// See also `fstatatZ` and `fstatatWasi`. pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { + if (builtin.os.tag == .wasi) { + return fstatatWasi(dirfd, pathname, flags); + } const pathname_c = try toPosixPath(pathname); return fstatatZ(dirfd, &pathname_c, flags); } pub const fstatatC = @compileError("deprecated: renamed to fstatatZ"); +/// WASI-only. Same as `fstatat` but targeting WASI. +/// See also `fstatat`. +pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { + var stat: wasi.filestat_t = undefined; + switch (wasi.path_filestat_get(dirfd, flags, pathname.ptr, pathname.len, &stat)) { + wasi.ESUCCESS => return Stat.fromFilestat(stat), + wasi.EINVAL => unreachable, + wasi.EBADF => unreachable, // Always a race condition. + wasi.ENOMEM => return error.SystemResources, + wasi.EACCES => return error.AccessDenied, + wasi.EFAULT => unreachable, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOTDIR => return error.FileNotFound, + else => |err| return unexpectedErrno(err), + } +} + +/// Same as `fstatat` but `pathname` is null-terminated. +/// See also `fstatat`. pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!Stat { var stat: Stat = undefined; switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) { diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index c298a0a2033a349078b37d6d879e6b085c5973c9..34747518e4c40693c085c7f9b87bfe0ad4256f47 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -18,6 +18,28 @@ const AtomicOrder = builtin.AtomicOrder; const tmpDir = std.testing.tmpDir; const Dir = std.fs.Dir; +test "fstatat" { + // enable when `fstat` and `fstatat` are implemented on Windows + if (builtin.os.tag == .windows) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + // create dummy file + const contents = "nonsense"; + try tmp.dir.writeFile("file.txt", contents); + + // fetch file's info on the opened fd directly + const file = try tmp.dir.openFile("file.txt", .{}); + const stat = try os.fstat(file.handle); + defer file.close(); + + // now repeat but using `fstatat` instead + const flags = if (builtin.os.tag == .wasi) 0x0 else os.AT_SYMLINK_NOFOLLOW; + const statat = try os.fstatat(tmp.dir.fd, "file.txt", flags); + expectEqual(stat, statat); +} + test "readlinkat" { // enable when `readlinkat` and `symlinkat` are implemented on Windows if (builtin.os.tag == .windows) return error.SkipZigTest; -- 2.54.0 From c63b23d684ded6ad7e740df87b1aedde0bec399d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 23 Jun 2020 17:45:31 +0200 Subject: [PATCH 107/295] Use fstatat on macOS (otherwise uses 32bit) --- lib/std/c.zig | 3 ++- lib/std/c/darwin.zig | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 97d6bf521565413943bb13c75c31b2f94eed0a14..d4c70a9fe4abfc820104a785fe81057b2cac669b 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -73,7 +73,6 @@ pub extern "c" fn abort() noreturn; pub extern "c" fn exit(code: c_int) noreturn; pub extern "c" fn isatty(fd: fd_t) c_int; pub extern "c" fn close(fd: fd_t) c_int; -pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int; pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: c_int) off_t; pub extern "c" fn open(path: [*:0]const u8, oflag: c_uint, ...) c_int; pub extern "c" fn openat(fd: c_int, path: [*:0]const u8, oflag: c_uint, ...) c_int; @@ -116,9 +115,11 @@ pub extern "c" fn readlinkat(dirfd: fd_t, noalias path: [*:0]const u8, noalias b pub usingnamespace switch (builtin.os.tag) { .macosx, .ios, .watchos, .tvos => struct { pub const realpath = @"realpath$DARWIN_EXTSN"; + pub const fstatat = @"fstatat$INODE64"; }, else => struct { pub extern "c" fn realpath(noalias file_name: [*:0]const u8, noalias resolved_name: [*]u8) ?[*:0]u8; + pub extern "c" fn fstatat(dirfd: fd_t, path: [*:0]const u8, stat_buf: *Stat, flags: u32) c_int; }, }; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index f827eb6863daacc564962ee35784e2c5a7ccb9db..242663856965d4d7a65613497686316fc6684e7a 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -16,6 +16,7 @@ pub extern "c" fn @"realpath$DARWIN_EXTSN"(noalias file_name: [*:0]const u8, noa pub extern "c" fn __getdirentries64(fd: c_int, buf_ptr: [*]u8, buf_len: usize, basep: *i64) isize; pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int; +pub extern "c" fn @"fstatat$INODE64"(dirfd: fd_t, path_name: [*:0]const u8, buf: *Stat, flags: u32) c_int; pub extern "c" fn mach_absolute_time() u64; pub extern "c" fn mach_timebase_info(tinfo: ?*mach_timebase_info_data) void; -- 2.54.0 From d40e367b73c130c49b9a7b57adcac8df52a13aa7 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 24 Jun 2020 08:43:15 +0200 Subject: [PATCH 108/295] Reformat using if-else where appropriate --- lib/std/os.zig | 60 +++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index aa85f9483b3e824265a6aeb0650f3c2eda79d808..99d66db2bb648a195a780083016618b0a14f68a8 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1667,13 +1667,13 @@ pub const UnlinkError = error{ pub fn unlink(file_path: []const u8) UnlinkError!void { if (builtin.os.tag == .wasi) { @compileError("unlink is not supported in WASI; use unlinkat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return windows.DeleteFileW(file_path_w.span().ptr); + } else { + const file_path_c = try toPosixPath(file_path); + return unlinkZ(&file_path_c); } - const file_path_c = try toPosixPath(file_path); - return unlinkZ(&file_path_c); } pub const unlinkC = @compileError("deprecated: renamed to unlinkZ"); @@ -1874,15 +1874,15 @@ const RenameError = error{ pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { if (builtin.os.tag == .wasi) { @compileError("rename is not supported in WASI; use renameat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); return renameW(old_path_w.span().ptr, new_path_w.span().ptr); + } else { + const old_path_c = try toPosixPath(old_path); + const new_path_c = try toPosixPath(new_path); + return renameZ(&old_path_c, &new_path_c); } - const old_path_c = try toPosixPath(old_path); - const new_path_c = try toPosixPath(new_path); - return renameZ(&old_path_c, &new_path_c); } pub const renameC = @compileError("deprecated: renamed to renameZ"); @@ -2153,14 +2153,14 @@ pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirErro pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .wasi) { @compileError("mkdir is not supported in WASI; use mkdirat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const sub_dir_handle = try windows.CreateDirectory(null, dir_path, null); windows.CloseHandle(sub_dir_handle); return; + } else { + const dir_path_c = try toPosixPath(dir_path); + return mkdirZ(&dir_path_c, mode); } - const dir_path_c = try toPosixPath(dir_path); - return mkdirZ(&dir_path_c, mode); } /// Same as `mkdir` but the parameter is a null-terminated UTF8-encoded string. @@ -2208,13 +2208,13 @@ pub const DeleteDirError = error{ pub fn rmdir(dir_path: []const u8) DeleteDirError!void { if (builtin.os.tag == .wasi) { @compileError("rmdir is not supported in WASI; use unlinkat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); return windows.RemoveDirectoryW(dir_path_w.span().ptr); + } else { + const dir_path_c = try toPosixPath(dir_path); + return rmdirZ(&dir_path_c); } - const dir_path_c = try toPosixPath(dir_path); - return rmdirZ(&dir_path_c); } pub const rmdirC = @compileError("deprecated: renamed to rmdirZ"); @@ -2259,13 +2259,13 @@ pub const ChangeCurDirError = error{ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { if (builtin.os.tag == .wasi) { @compileError("chdir is not supported in WASI"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(dir_path); @compileError("TODO implement chdir for Windows"); + } else { + const dir_path_c = try toPosixPath(dir_path); + return chdirZ(&dir_path_c); } - const dir_path_c = try toPosixPath(dir_path); - return chdirZ(&dir_path_c); } pub const chdirC = @compileError("deprecated: renamed to chdirZ"); @@ -2325,13 +2325,13 @@ pub const ReadLinkError = error{ pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .wasi) { @compileError("readlink is not supported in WASI; use readlinkat instead"); - } - if (builtin.os.tag == .windows) { + } else if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return readlinkW(file_path_w.span().ptr, out_buffer); + } else { + const file_path_c = try toPosixPath(file_path); + return readlinkZ(&file_path_c, out_buffer); } - const file_path_c = try toPosixPath(file_path); - return readlinkZ(&file_path_c, out_buffer); } pub const readlinkC = @compileError("deprecated: renamed to readlinkZ"); @@ -3089,6 +3089,9 @@ pub fn fstat(fd: fd_t) FStatError!Stat { else => |err| return unexpectedErrno(err), } } + if (builtin.os.tag == .windows) { + @compileError("fstat is not yet implemented on Windows"); + } var stat: Stat = undefined; switch (errno(system.fstat(fd, &stat))) { @@ -3109,9 +3112,12 @@ pub const FStatAtError = FStatError || error{ NameTooLong, FileNotFound }; pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat { if (builtin.os.tag == .wasi) { return fstatatWasi(dirfd, pathname, flags); + } else if (builtin.os.tag == .windows) { + @compileError("fstatat is not yet implemented on Windows"); + } else { + const pathname_c = try toPosixPath(pathname); + return fstatatZ(dirfd, &pathname_c, flags); } - const pathname_c = try toPosixPath(pathname); - return fstatatZ(dirfd, &pathname_c, flags); } pub const fstatatC = @compileError("deprecated: renamed to fstatatZ"); -- 2.54.0 From 129a4fb251f8eab22eacf219fbf81006baec3251 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 24 Jun 2020 20:00:11 +0300 Subject: [PATCH 109/295] Copy union const values correctly --- src/analyze.cpp | 6 ++++++ test/stage1/behavior/type_info.zig | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/analyze.cpp b/src/analyze.cpp index 542fbb56ce184b8fd52cb9ed168ad5165b18d51e..699b1212767d3bb1294f4b2d6490d21b441f4a22 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -9597,6 +9597,12 @@ void copy_const_val(CodeGen *g, ZigValue *dest, ZigValue *src) { break; } } + } else if (dest->type->id == ZigTypeIdUnion) { + bigint_init_bigint(&dest->data.x_union.tag, &src->data.x_union.tag); + dest->data.x_union.payload = g->pass1_arena->create(); + copy_const_val(g, dest->data.x_union.payload, src->data.x_union.payload); + dest->data.x_union.payload->parent.id = ConstParentIdUnion; + dest->data.x_union.payload->parent.data.p_union.union_val = dest; } else if (type_has_optional_repr(dest->type) && dest->data.x_optional != nullptr) { dest->data.x_optional = g->pass1_arena->create(); copy_const_val(g, dest->data.x_optional, src->data.x_optional); diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index 41301f290d9e5bccdff9813a480ccba47b4ca498..68ff3aa31030f361962aa13ceb3345367f81f6a6 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -402,3 +402,11 @@ test "type info for async frames" { else => unreachable, } } + +test "type info: value is correctly copied" { + comptime { + var ptrInfo = @typeInfo([]u32); + ptrInfo.Pointer.size = .One; + expect(@typeInfo([]u32).Pointer.size == .Slice); + } +} -- 2.54.0 From fd7a97b3b2607c6de49e96ed32a7be0a037c67a8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Jun 2020 16:20:02 -0400 Subject: [PATCH 110/295] fix memory leak of anonymous decl name --- src-self-hosted/Module.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 9b3d1d23b57b9202bb72d3ad3a12b18a889c817b..dfefd3e6ba3648682067be1160a78e13fda3111e 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2378,6 +2378,7 @@ fn createAnonymousDecl( const name_index = self.getNextAnonNameIndex(); const scope_decl = scope.decl().?; const name = try std.fmt.allocPrint(self.allocator, "{}${}", .{ scope_decl.name, name_index }); + defer self.allocator.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); const src_hash: std.zig.SrcHash = undefined; const new_decl = try self.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash); -- 2.54.0 From 5aa3f56773f4b06629184a1e3753c3132b18e0bd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Jun 2020 20:28:52 -0400 Subject: [PATCH 111/295] self-hosted: fix test regressions I'm allowing incremental compilation of ZIR modules to be broken. This is not a real use case of ZIR, and the feature requires a lot of code duplication with incremental compilation of Zig AST (which works great). --- src-self-hosted/Module.zig | 51 ++++--- src-self-hosted/link.zig | 23 ++- src-self-hosted/main.zig | 25 ++++ src-self-hosted/test.zig | 35 +++-- src-self-hosted/tracy.zig | 2 +- src-self-hosted/type.zig | 6 + src-self-hosted/zir.zig | 83 ++++++++--- test/stage2/compile_errors.zig | 8 +- test/stage2/zir.zig | 264 ++++++++------------------------- 9 files changed, 227 insertions(+), 270 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index dfefd3e6ba3648682067be1160a78e13fda3111e..89dcac3f4186559319e5aa8547def3a4bde0cf72 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -69,6 +69,8 @@ next_anon_name_index: usize = 0, /// contains Decls that need to be deleted if they end up having no references to them. deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, +keep_source_files_loaded: bool, + const DeclTable = std.HashMap(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql); const WorkItem = union(enum) { @@ -580,11 +582,13 @@ pub const Scope = struct { .loaded_success => { self.contents.module.deinit(allocator); allocator.destroy(self.contents.module); + self.contents = .{ .not_available = {} }; self.status = .unloaded_success; }, .loaded_sema_failure => { self.contents.module.deinit(allocator); allocator.destroy(self.contents.module); + self.contents = .{ .not_available = {} }; self.status = .unloaded_sema_failure; }, } @@ -719,6 +723,7 @@ pub const InitOptions = struct { link_mode: ?std.builtin.LinkMode = null, object_format: ?std.builtin.ObjectFormat = null, optimize_mode: std.builtin.Mode = .Debug, + keep_source_files_loaded: bool = false, }; pub fn init(gpa: *Allocator, options: InitOptions) !Module { @@ -772,6 +777,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .failed_files = std.AutoHashMap(*Scope, *ErrorMsg).init(gpa), .failed_exports = std.AutoHashMap(*Export, *ErrorMsg).init(gpa), .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), + .keep_source_files_loaded = options.keep_source_files_loaded, }; } @@ -869,21 +875,22 @@ pub fn update(self: *Module) !void { try self.performAllTheWork(); // Process the deletion set. - for (self.deletion_set.items) |decl| { + while (self.deletion_set.popOrNull()) |decl| { if (decl.dependants.items.len != 0) { decl.deletion_flag = false; continue; } try self.deleteDecl(decl); } - self.deletion_set.shrink(self.allocator, 0); self.link_error_flags = self.bin_file.error_flags; // If there are any errors, we anticipate the source files being loaded // to report error messages. Otherwise we unload all source files to save memory. if (self.totalErrorCount() == 0) { - self.root_scope.unload(self.allocator); + if (!self.keep_source_files_loaded) { + self.root_scope.unload(self.allocator); + } try self.bin_file.flush(); } } @@ -1025,7 +1032,6 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { defer tracy.end(); const subsequent_analysis = switch (decl.analysis) { - .complete => return, .in_progress => unreachable, .sema_failure, @@ -1035,7 +1041,11 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { .codegen_failure_retryable, => return error.AnalysisFail, - .outdated => blk: { + .complete, .outdated => blk: { + if (decl.generation == self.generation) { + assert(decl.analysis == .complete); + return; + } //std.debug.warn("re-analyzing {}\n", .{decl.name}); // The exports this Decl performs will be re-discovered, so we remove them here @@ -1044,10 +1054,9 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { // Dependencies will be re-discovered, so we remove them here prior to re-analysis. for (decl.dependencies.items) |dep| { dep.removeDependant(decl); - if (dep.dependants.items.len == 0) { + if (dep.dependants.items.len == 0 and !dep.deletion_flag) { // We don't perform a deletion here, because this Decl or another one // may end up referencing it before the update is complete. - assert(!dep.deletion_flag); dep.deletion_flag = true; try self.deletion_set.append(self.allocator, dep); } @@ -1773,18 +1782,18 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { } } } - { - // Handle explicitly deleted decls from the source code. Not to be confused - // with when we delete decls because they are no longer referenced. - var it = deleted_decls.iterator(); - while (it.next()) |kv| { - //std.debug.warn("noticed '{}' deleted from source\n", .{kv.key.name}); - try self.deleteDecl(kv.key); - } - } for (exports_to_resolve.items) |export_decl| { _ = try self.resolveZirDecl(&root_scope.base, export_decl); } + { + // Handle explicitly deleted decls from the source code. Not to be confused + // with when we delete decls because they are no longer referenced. + var it = deleted_decls.iterator(); + while (it.next()) |kv| { + //std.debug.warn("noticed '{}' deleted from source\n", .{kv.key.name}); + try self.deleteDecl(kv.key); + } + } } fn deleteDecl(self: *Module, decl: *Decl) !void { @@ -1800,10 +1809,9 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { // Remove itself from its dependencies, because we are about to destroy the decl pointer. for (decl.dependencies.items) |dep| { dep.removeDependant(decl); - if (dep.dependants.items.len == 0) { + if (dep.dependants.items.len == 0 and !dep.deletion_flag) { // We don't recursively perform a deletion here, because during the update, // another reference to it may turn up. - assert(!dep.deletion_flag); dep.deletion_flag = true; self.deletion_set.appendAssumeCapacity(dep); } @@ -2026,9 +2034,10 @@ fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In }; const decl = try self.resolveCompleteZirDecl(scope, entry.decl); const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl); - const result = try self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); - old_inst.analyzed_inst = result; - return result; + // Note: it would be tempting here to store the result into old_inst.analyzed_inst field, + // but this would prevent the analyzeDeclRef from happening, which is needed to properly + // detect Decl dependencies and dependency failures on updates. + return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); } fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 012f543a7e073c6f9e74fe451f2d53eef0d57ab4..c6acf21b84727166d16e47abc457fbcb14875a01 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -369,7 +369,7 @@ pub const ElfFile = struct { const file_size = self.options.program_code_size_hint; const p_align = 0x1000; const off = self.findFreeSpace(file_size, p_align); - //std.debug.warn("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); try self.program_headers.append(self.allocator, .{ .p_type = elf.PT_LOAD, .p_offset = off, @@ -390,7 +390,7 @@ pub const ElfFile = struct { // page align. const p_align = 0x1000; const off = self.findFreeSpace(file_size, p_align); - //std.debug.warn("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. // we'll need to re-use that function anyway, in case the GOT grows and overlaps something // else in virtual memory. @@ -412,7 +412,7 @@ pub const ElfFile = struct { assert(self.shstrtab.items.len == 0); try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0 const off = self.findFreeSpace(self.shstrtab.items.len, 1); - //std.debug.warn("found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); + //std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); try self.sections.append(self.allocator, .{ .sh_name = try self.makeString(".shstrtab"), .sh_type = elf.SHT_STRTAB, @@ -470,7 +470,7 @@ pub const ElfFile = struct { const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); const file_size = self.options.symbol_count_hint * each_size; const off = self.findFreeSpace(file_size, min_align); - //std.debug.warn("found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + //std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); try self.sections.append(self.allocator, .{ .sh_name = try self.makeString(".symtab"), @@ -586,7 +586,7 @@ pub const ElfFile = struct { shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); } shstrtab_sect.sh_size = needed_size; - //std.debug.warn("shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); + //std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); if (!self.shdr_table_dirty) { @@ -632,7 +632,7 @@ pub const ElfFile = struct { for (buf) |*shdr, i| { shdr.* = self.sections.items[i]; - //std.debug.warn("writing section {}\n", .{shdr.*}); + //std.log.debug(.link, "writing section {}\n", .{shdr.*}); if (foreign_endian) { bswapAllFields(elf.Elf64_Shdr, shdr); } @@ -956,10 +956,10 @@ pub const ElfFile = struct { try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); if (self.local_symbol_free_list.popOrNull()) |i| { - //std.debug.warn("reusing symbol index {} for {}\n", .{i, decl.name}); + //std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name}); decl.link.local_sym_index = i; } else { - //std.debug.warn("allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); + //std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -1027,11 +1027,11 @@ pub const ElfFile = struct { !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); if (need_realloc) { const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); - //std.debug.warn("growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); + //std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); if (vaddr != local_sym.st_value) { local_sym.st_value = vaddr; - //std.debug.warn(" (writing new offset table entry)\n", .{}); + //std.log.debug(.link, " (writing new offset table entry)\n", .{}); self.offset_table.items[decl.link.offset_table_index] = vaddr; try self.writeOffsetTableEntry(decl.link.offset_table_index); } @@ -1049,7 +1049,7 @@ pub const ElfFile = struct { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); - //std.debug.warn("allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); + //std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); errdefer self.freeTextBlock(&decl.link); local_sym.* = .{ @@ -1307,7 +1307,6 @@ pub const ElfFile = struct { .p32 => @sizeOf(elf.Elf32_Sym), .p64 => @sizeOf(elf.Elf64_Sym), }; - //std.debug.warn("symtab start=0x{x} end=0x{x}\n", .{ syms_sect.sh_offset, syms_sect.sh_offset + needed_size }); const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size; switch (self.ptr_width) { diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index c12f3a86dc535834467419b4f171be9708090116..aa5a56010cdf4e45a2be9bdc9bd86575732920fa 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -38,6 +38,30 @@ const usage = \\ ; +pub fn log( + comptime level: std.log.Level, + comptime scope: @TypeOf(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + if (@enumToInt(level) > @enumToInt(std.log.level)) + return; + + const scope_prefix = "(" ++ switch (scope) { + // Uncomment to hide logs + //.compiler, + .link, + => return, + + else => @tagName(scope), + } ++ "): "; + + const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix; + + // Print the message to stderr, silently ignoring any errors + std.debug.print(prefix ++ format, args); +} + pub fn main() !void { // TODO general purpose allocator in the zig std lib const gpa = if (std.builtin.link_libc) std.heap.c_allocator else std.heap.page_allocator; @@ -450,6 +474,7 @@ fn buildOutputType( .link_mode = link_mode, .object_format = object_format, .optimize_mode = build_mode, + .keep_source_files_loaded = zir_out_path != null, }); defer module.deinit(); diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 4cf72ce481bb59e24a36ea83a531f649a171f11c..a7942575d0f4e6bdea0b05ba40f959e5241ab718 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -226,20 +226,36 @@ pub const TestContext = struct { for (self.zir_cases.items) |case| { std.testing.base_allocator_instance.reset(); + + var prg_node = root_node.start(case.name, case.updates.items.len); + prg_node.activate(); + defer prg_node.end(); + + // So that we can see which test case failed when the leak checker goes off. + progress.refresh(); + const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target); - try self.runOneZIRCase(std.testing.allocator, root_node, case, info.target); + try self.runOneZIRCase(std.testing.allocator, &prg_node, case, info.target); try std.testing.allocator_instance.validate(); } // TODO: wipe the rest of this function for (self.zir_cmp_output_cases.items) |case| { std.testing.base_allocator_instance.reset(); - try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target); + + var prg_node = root_node.start(case.name, case.src_list.len); + prg_node.activate(); + defer prg_node.end(); + + // So that we can see which test case failed when the leak checker goes off. + progress.refresh(); + + try self.runOneZIRCmpOutputCase(std.testing.allocator, &prg_node, case, native_info.target); try std.testing.allocator_instance.validate(); } } - fn runOneZIRCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: ZIRCase, target: std.Target) !void { + fn runOneZIRCase(self: *TestContext, allocator: *Allocator, prg_node: *std.Progress.Node, case: ZIRCase, target: std.Target) !void { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); @@ -247,10 +263,6 @@ pub const TestContext = struct { const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); - var prg_node = root_node.start(case.name, case.updates.items.len); - prg_node.activate(); - defer prg_node.end(); - var module = try Module.init(allocator, .{ .target = target, // This is an Executable, as opposed to e.g. a *library*. This does @@ -265,6 +277,7 @@ pub const TestContext = struct { .bin_file_dir = tmp.dir, .bin_file_path = "test_case.o", .root_pkg = root_pkg, + .keep_source_files_loaded = true, }); defer module.deinit(); @@ -329,7 +342,7 @@ pub const TestContext = struct { } }, - else => return error.unimplemented, + else => return error.Unimplemented, } } } @@ -337,7 +350,7 @@ pub const TestContext = struct { fn runOneZIRCmpOutputCase( self: *TestContext, allocator: *Allocator, - root_node: *std.Progress.Node, + prg_node: *std.Progress.Node, case: ZIRCompareOutputCase, target: std.Target, ) !void { @@ -348,10 +361,6 @@ pub const TestContext = struct { const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); - var prg_node = root_node.start(case.name, case.src_list.len); - prg_node.activate(); - defer prg_node.end(); - var module = try Module.init(allocator, .{ .target = target, .output_mode = .Exe, diff --git a/src-self-hosted/tracy.zig b/src-self-hosted/tracy.zig index 1e480d75b0a37d52b01f2aa3e5c622541b32c8f8..6f56a87ce6fad8484cfc8f37ff5e4e97ca0570f1 100644 --- a/src-self-hosted/tracy.zig +++ b/src-self-hosted/tracy.zig @@ -1,6 +1,6 @@ pub const std = @import("std"); -pub const enable = @import("build_options").enable_tracy; +pub const enable = if (std.builtin.is_test) false else @import("build_options").enable_tracy; extern fn ___tracy_emit_zone_begin_callstack( srcloc: *const ___tracy_source_location_data, diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index d8bc40a4f87ffb9ded29840c9d07f012ac0065c7..fb9718664863e1a6054205f916c9a445cc73344d 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -113,6 +113,12 @@ pub const Type = extern union { .Undefined => return true, .Null => return true, .Pointer => { + // Hot path for common case: + if (a.cast(Payload.SingleConstPointer)) |a_payload| { + if (b.cast(Payload.SingleConstPointer)) |b_payload| { + return eql(a_payload.pointee_type, b_payload.pointee_type); + } + } const is_slice_a = isSlice(a); const is_slice_b = isSlice(b); if (is_slice_a != is_slice_b) diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index a471d35c14562b71a74d78f482bbfefb6b0a6adb..43c0eac197c324e879fd767cf3a9ff3cd3d04f14 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -710,8 +710,9 @@ pub const Module = struct { } else if (inst.cast(Inst.DeclValInModule)) |decl_val| { try stream.print("@{}", .{decl_val.positionals.decl.name}); } else { - //try stream.print("?", .{}); - unreachable; + // This should be unreachable in theory, but since ZIR is used for debugging the compiler + // we output some debug text instead. + try stream.print("?{}?", .{@tagName(inst.tag)}); } } }; @@ -1175,6 +1176,39 @@ const EmitZIR = struct { // Emit all the decls. for (src_decls.items) |ir_decl| { + switch (ir_decl.analysis) { + .unreferenced => continue, + .complete => {}, + .in_progress => unreachable, + .outdated => unreachable, + + .sema_failure, + .sema_failure_retryable, + .codegen_failure, + .dependency_failure, + .codegen_failure_retryable, + => if (self.old_module.failed_decls.getValue(ir_decl)) |err_msg| { + const fail_inst = try self.arena.allocator.create(Inst.CompileError); + fail_inst.* = .{ + .base = .{ + .src = ir_decl.src(), + .tag = Inst.CompileError.base_tag, + }, + .positionals = .{ + .msg = try self.arena.allocator.dupe(u8, err_msg.msg), + }, + .kw_args = .{}, + }; + const decl = try self.arena.allocator.create(Decl); + decl.* = .{ + .name = mem.spanZ(ir_decl.name), + .contents_hash = undefined, + .inst = &fail_inst.base, + }; + try self.decls.append(self.allocator, decl); + continue; + }, + } if (self.old_module.export_owners.getValue(ir_decl)) |exports| { for (exports) |module_export| { const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name); @@ -1199,20 +1233,27 @@ const EmitZIR = struct { } } - fn resolveInst(self: *EmitZIR, inst_table: *std.AutoHashMap(*ir.Inst, *Inst), inst: *ir.Inst) !*Inst { + const ZirBody = struct { + inst_table: *std.AutoHashMap(*ir.Inst, *Inst), + instructions: *std.ArrayList(*Inst), + }; + + fn resolveInst(self: *EmitZIR, new_body: ZirBody, inst: *ir.Inst) !*Inst { if (inst.cast(ir.Inst.Constant)) |const_inst| { - const new_decl = if (const_inst.val.cast(Value.Payload.Function)) |func_pl| blk: { + const new_inst = if (const_inst.val.cast(Value.Payload.Function)) |func_pl| blk: { const owner_decl = func_pl.func.owner_decl; break :blk try self.emitDeclVal(inst.src, mem.spanZ(owner_decl.name)); } else if (const_inst.val.cast(Value.Payload.DeclRef)) |declref| blk: { - break :blk try self.emitDeclRef(inst.src, declref.decl); + const decl_ref = try self.emitDeclRef(inst.src, declref.decl); + try new_body.instructions.append(decl_ref); + break :blk decl_ref; } else blk: { break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst; }; - try inst_table.putNoClobber(inst, new_decl); - return new_decl; + try new_body.inst_table.putNoClobber(inst, new_inst); + return new_inst; } else { - return inst_table.getValue(inst).?; + return new_body.inst_table.getValue(inst).?; } } @@ -1419,6 +1460,10 @@ const EmitZIR = struct { inst_table: *std.AutoHashMap(*ir.Inst, *Inst), instructions: *std.ArrayList(*Inst), ) Allocator.Error!void { + const new_body = ZirBody{ + .inst_table = inst_table, + .instructions = instructions, + }; for (body.instructions) |inst| { const new_inst = switch (inst.tag) { .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), @@ -1428,7 +1473,7 @@ const EmitZIR = struct { const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len); for (args) |*elem, i| { - elem.* = try self.resolveInst(inst_table, old_inst.args.args[i]); + elem.* = try self.resolveInst(new_body, old_inst.args.args[i]); } new_inst.* = .{ .base = .{ @@ -1436,7 +1481,7 @@ const EmitZIR = struct { .tag = Inst.Call.base_tag, }, .positionals = .{ - .func = try self.resolveInst(inst_table, old_inst.args.func), + .func = try self.resolveInst(new_body, old_inst.args.func), .args = args, }, .kw_args = .{}, @@ -1453,7 +1498,7 @@ const EmitZIR = struct { .tag = Inst.Return.base_tag, }, .positionals = .{ - .operand = try self.resolveInst(inst_table, old_inst.args.operand), + .operand = try self.resolveInst(new_body, old_inst.args.operand), }, .kw_args = .{}, }; @@ -1477,7 +1522,7 @@ const EmitZIR = struct { const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len); for (args) |*elem, i| { - elem.* = try self.resolveInst(inst_table, old_inst.args.args[i]); + elem.* = try self.resolveInst(new_body, old_inst.args.args[i]); } new_inst.* = .{ @@ -1511,7 +1556,7 @@ const EmitZIR = struct { .tag = Inst.PtrToInt.base_tag, }, .positionals = .{ - .ptr = try self.resolveInst(inst_table, old_inst.args.ptr), + .ptr = try self.resolveInst(new_body, old_inst.args.ptr), }, .kw_args = .{}, }; @@ -1527,7 +1572,7 @@ const EmitZIR = struct { }, .positionals = .{ .dest_type = (try self.emitType(inst.src, inst.ty)).inst, - .operand = try self.resolveInst(inst_table, old_inst.args.operand), + .operand = try self.resolveInst(new_body, old_inst.args.operand), }, .kw_args = .{}, }; @@ -1542,8 +1587,8 @@ const EmitZIR = struct { .tag = Inst.Cmp.base_tag, }, .positionals = .{ - .lhs = try self.resolveInst(inst_table, old_inst.args.lhs), - .rhs = try self.resolveInst(inst_table, old_inst.args.rhs), + .lhs = try self.resolveInst(new_body, old_inst.args.lhs), + .rhs = try self.resolveInst(new_body, old_inst.args.rhs), .op = old_inst.args.op, }, .kw_args = .{}, @@ -1569,7 +1614,7 @@ const EmitZIR = struct { .tag = Inst.CondBr.base_tag, }, .positionals = .{ - .condition = try self.resolveInst(inst_table, old_inst.args.condition), + .condition = try self.resolveInst(new_body, old_inst.args.condition), .true_body = .{ .instructions = true_body.toOwnedSlice() }, .false_body = .{ .instructions = false_body.toOwnedSlice() }, }, @@ -1586,7 +1631,7 @@ const EmitZIR = struct { .tag = Inst.IsNull.base_tag, }, .positionals = .{ - .operand = try self.resolveInst(inst_table, old_inst.args.operand), + .operand = try self.resolveInst(new_body, old_inst.args.operand), }, .kw_args = .{}, }; @@ -1601,7 +1646,7 @@ const EmitZIR = struct { .tag = Inst.IsNonNull.base_tag, }, .positionals = .{ - .operand = try self.resolveInst(inst_table, old_inst.args.operand), + .operand = try self.resolveInst(new_body, old_inst.args.operand), }, .kw_args = .{}, }; diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 43c41aa3647535f761bb5d412bfac865591ff603..7596894dca5b3ad25bef7effc86c8a0e22b4fbbd 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -27,9 +27,8 @@ pub fn addCases(ctx: *TestContext) !void { \\ %0 = call(@notafunc, []) \\}) \\@0 = str("_start") - \\@1 = ref(@0) - \\@2 = export(@1, @start) - , &[_][]const u8{":5:13: error: use of undeclared identifier 'notafunc'"}); + \\@1 = export(@0, "start") + , &[_][]const u8{":5:13: error: decl 'notafunc' not found"}); // TODO: this error should occur at the call site, not the fntype decl ctx.addZIRError("call naked function", linux_x64, @@ -41,8 +40,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ %0 = call(@s, []) \\}) \\@0 = str("_start") - \\@1 = ref(@0) - \\@2 = export(@1, @start) + \\@1 = export(@0, "start") , &[_][]const u8{":4:9: error: unable to call function with naked calling convention"}); // TODO: re-enable these tests. diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index a22c770a9a6676f34ae5083390ff316fc08407a3..832629c6e7b00a029adc2ceedbd4e23670163b10 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -14,23 +14,21 @@ pub fn addCases(ctx: *TestContext) void { \\@fnty = fntype([], @void, cc=C) \\ \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) + \\@11 = export(@9, "entry") \\ \\@entry = fn(@fnty, { - \\ %11 = return() + \\ %11 = returnvoid() \\}) , \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) - \\@9 = str("entry") - \\@10 = ref(@9) - \\@unnamed$6 = str("entry") - \\@unnamed$7 = ref(@unnamed$6) - \\@unnamed$8 = export(@unnamed$7, @entry) - \\@unnamed$10 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$10, { - \\ %0 = return() + \\@9 = declref("9$0") + \\@9$0 = str("entry") + \\@unnamed$4 = str("entry") + \\@unnamed$5 = export(@unnamed$4, "entry") + \\@unnamed$6 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$6, { + \\ %0 = returnvoid() \\}) \\ ); @@ -45,11 +43,10 @@ pub fn addCases(ctx: *TestContext) void { \\ \\@entry = fn(@fnty, { \\ %a = str("\x32\x08\x01\x0a") - \\ %aref = ref(%a) - \\ %eptr0 = elemptr(%aref, @0) - \\ %eptr1 = elemptr(%aref, @1) - \\ %eptr2 = elemptr(%aref, @2) - \\ %eptr3 = elemptr(%aref, @3) + \\ %eptr0 = elemptr(%a, @0) + \\ %eptr1 = elemptr(%a, @1) + \\ %eptr2 = elemptr(%a, @2) + \\ %eptr3 = elemptr(%a, @3) \\ %v0 = deref(%eptr0) \\ %v1 = deref(%eptr1) \\ %v2 = deref(%eptr2) @@ -61,15 +58,14 @@ pub fn addCases(ctx: *TestContext) void { \\ %expected = int(69) \\ %ok = cmp(%result, eq, %expected) \\ %10 = condbr(%ok, { - \\ %11 = return() + \\ %11 = returnvoid() \\ }, { \\ %12 = breakpoint() \\ }) \\}) \\ \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) + \\@11 = export(@9, "entry") , \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) @@ -77,16 +73,15 @@ pub fn addCases(ctx: *TestContext) void { \\@1 = int(1) \\@2 = int(2) \\@3 = int(3) - \\@unnamed$7 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$7, { - \\ %0 = return() + \\@unnamed$6 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$6, { + \\ %0 = returnvoid() \\}) - \\@a = str("2\x08\x01\n") - \\@9 = str("entry") - \\@10 = ref(@9) - \\@unnamed$14 = str("entry") - \\@unnamed$15 = ref(@unnamed$14) - \\@unnamed$16 = export(@unnamed$15, @entry) + \\@entry$1 = str("2\x08\x01\n") + \\@9 = declref("9$0") + \\@9$0 = str("entry") + \\@unnamed$11 = str("entry") + \\@unnamed$12 = export(@unnamed$11, "entry") \\ ); @@ -97,45 +92,43 @@ pub fn addCases(ctx: *TestContext) void { \\@fnty = fntype([], @void, cc=C) \\ \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) + \\@11 = export(@9, "entry") \\ \\@entry = fn(@fnty, { \\ %0 = call(@a, []) - \\ %1 = return() + \\ %1 = returnvoid() \\}) \\ \\@a = fn(@fnty, { \\ %0 = call(@b, []) - \\ %1 = return() + \\ %1 = returnvoid() \\}) \\ \\@b = fn(@fnty, { \\ %0 = call(@a, []) - \\ %1 = return() + \\ %1 = returnvoid() \\}) , \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) - \\@9 = str("entry") - \\@10 = ref(@9) - \\@unnamed$6 = str("entry") - \\@unnamed$7 = ref(@unnamed$6) - \\@unnamed$8 = export(@unnamed$7, @entry) - \\@unnamed$12 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$12, { + \\@9 = declref("9$0") + \\@9$0 = str("entry") + \\@unnamed$4 = str("entry") + \\@unnamed$5 = export(@unnamed$4, "entry") + \\@unnamed$6 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$6, { \\ %0 = call(@a, [], modifier=auto) - \\ %1 = return() + \\ %1 = returnvoid() \\}) - \\@unnamed$17 = fntype([], @void, cc=C) - \\@a = fn(@unnamed$17, { + \\@unnamed$8 = fntype([], @void, cc=C) + \\@a = fn(@unnamed$8, { \\ %0 = call(@b, [], modifier=auto) - \\ %1 = return() + \\ %1 = returnvoid() \\}) - \\@unnamed$22 = fntype([], @void, cc=C) - \\@b = fn(@unnamed$22, { + \\@unnamed$10 = fntype([], @void, cc=C) + \\@b = fn(@unnamed$10, { \\ %0 = call(@a, [], modifier=auto) - \\ %1 = return() + \\ %1 = returnvoid() \\}) \\ ); @@ -145,27 +138,26 @@ pub fn addCases(ctx: *TestContext) void { \\@fnty = fntype([], @void, cc=C) \\ \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) + \\@11 = export(@9, "entry") \\ \\@entry = fn(@fnty, { \\ %0 = call(@a, []) - \\ %1 = return() + \\ %1 = returnvoid() \\}) \\ \\@a = fn(@fnty, { \\ %0 = call(@b, []) - \\ %1 = return() + \\ %1 = returnvoid() \\}) \\ \\@b = fn(@fnty, { \\ %9 = compileerror("message") \\ %0 = call(@a, []) - \\ %1 = return() + \\ %1 = returnvoid() \\}) , &[_][]const u8{ - ":19:21: error: message", + ":18:21: error: message", }, ); // Now we remove the call to `a`. `a` and `b` form a cycle, but no entry points are @@ -176,34 +168,32 @@ pub fn addCases(ctx: *TestContext) void { \\@fnty = fntype([], @void, cc=C) \\ \\@9 = str("entry") - \\@10 = ref(@9) - \\@11 = export(@10, @entry) + \\@11 = export(@9, "entry") \\ \\@entry = fn(@fnty, { - \\ %1 = return() + \\ %0 = returnvoid() \\}) \\ \\@a = fn(@fnty, { \\ %0 = call(@b, []) - \\ %1 = return() + \\ %1 = returnvoid() \\}) \\ \\@b = fn(@fnty, { \\ %9 = compileerror("message") \\ %0 = call(@a, []) - \\ %1 = return() + \\ %1 = returnvoid() \\}) , \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) - \\@9 = str("entry") - \\@10 = ref(@9) - \\@unnamed$6 = str("entry") - \\@unnamed$7 = ref(@unnamed$6) - \\@unnamed$8 = export(@unnamed$7, @entry) - \\@unnamed$10 = fntype([], @void, cc=C) - \\@entry = fn(@unnamed$10, { - \\ %0 = return() + \\@9 = declref("9$2") + \\@9$2 = str("entry") + \\@unnamed$4 = str("entry") + \\@unnamed$5 = export(@unnamed$4, "entry") + \\@unnamed$6 = fntype([], @void, cc=C) + \\@entry = fn(@unnamed$6, { + \\ %0 = returnvoid() \\}) \\ ); @@ -218,7 +208,7 @@ pub fn addCases(ctx: *TestContext) void { } ctx.addZIRCompareOutput( - "hello world ZIR, update msg", + "hello world ZIR", &[_][]const u8{ \\@noreturn = primitive(noreturn) \\@void = primitive(void) @@ -272,125 +262,10 @@ pub fn addCases(ctx: *TestContext) void { \\ \\@9 = str("_start") \\@11 = export(@9, "start") - , - \\@noreturn = primitive(noreturn) - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@msg = str("Hello, world!\n") - \\@msg2 = str("HELL WORLD\n") - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %SYS_exit_group = int(231) - \\ %exit_code = as(@usize, @0) - \\ - \\ %syscall = str("syscall") - \\ %sysoutreg = str("={rax}") - \\ %rax = str("{rax}") - \\ %rdi = str("{rdi}") - \\ %rcx = str("rcx") - \\ %rdx = str("{rdx}") - \\ %rsi = str("{rsi}") - \\ %r11 = str("r11") - \\ %memory = str("memory") - \\ - \\ %SYS_write = as(@usize, @1) - \\ %STDOUT_FILENO = as(@usize, @1) - \\ - \\ %msg_addr = ptrtoint(@msg2) - \\ - \\ %len_name = str("len") - \\ %msg_len_ptr = fieldptr(@msg2, %len_name) - \\ %msg_len = deref(%msg_len_ptr) - \\ %rc_write = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi, %rsi, %rdx], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) - \\ - \\ %rc_exit = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_exit_group, %exit_code]) - \\ - \\ %99 = unreachable() - \\}); - \\ - \\@9 = str("_start") - \\@11 = export(@9, "start") - , - \\@noreturn = primitive(noreturn) - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@msg = str("Hello, world!\n") - \\@msg2 = str("Editing the same msg2 decl but this time with a much longer message which will\ncause the data to need to be relocated in virtual address space.\n") - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %SYS_exit_group = int(231) - \\ %exit_code = as(@usize, @0) - \\ - \\ %syscall = str("syscall") - \\ %sysoutreg = str("={rax}") - \\ %rax = str("{rax}") - \\ %rdi = str("{rdi}") - \\ %rcx = str("rcx") - \\ %rdx = str("{rdx}") - \\ %rsi = str("{rsi}") - \\ %r11 = str("r11") - \\ %memory = str("memory") - \\ - \\ %SYS_write = as(@usize, @1) - \\ %STDOUT_FILENO = as(@usize, @1) - \\ - \\ %msg_addr = ptrtoint(@msg2) - \\ - \\ %len_name = str("len") - \\ %msg_len_ptr = fieldptr(@msg2, %len_name) - \\ %msg_len = deref(%msg_len_ptr) - \\ %rc_write = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi, %rsi, %rdx], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) - \\ - \\ %rc_exit = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_exit_group, %exit_code]) - \\ - \\ %99 = unreachable() - \\}); - \\ - \\@9 = str("_start") - \\@11 = export(@9, "start") }, &[_][]const u8{ \\Hello, world! \\ - , - \\HELL WORLD - \\ - , - \\Editing the same msg2 decl but this time with a much longer message which will - \\cause the data to need to be relocated in virtual address space. - \\ }, ); @@ -405,26 +280,18 @@ pub fn addCases(ctx: *TestContext) void { \\@2 = int(2) \\@3 = int(3) \\ - \\@syscall_array = str("syscall") - \\@sysoutreg_array = str("={rax}") - \\@rax_array = str("{rax}") - \\@rdi_array = str("{rdi}") - \\@rcx_array = str("rcx") - \\@r11_array = str("r11") - \\@memory_array = str("memory") - \\ \\@exit0_fnty = fntype([], @noreturn) \\@exit0 = fn(@exit0_fnty, { \\ %SYS_exit_group = int(231) \\ %exit_code = as(@usize, @0) \\ - \\ %syscall = ref(@syscall_array) - \\ %sysoutreg = ref(@sysoutreg_array) - \\ %rax = ref(@rax_array) - \\ %rdi = ref(@rdi_array) - \\ %rcx = ref(@rcx_array) - \\ %r11 = ref(@r11_array) - \\ %memory = ref(@memory_array) + \\ %syscall = str("syscall") + \\ %sysoutreg = str("={rax}") + \\ %rax = str("{rax}") + \\ %rdi = str("{rdi}") + \\ %rcx = str("rcx") + \\ %r11 = str("r11") + \\ %memory = str("memory") \\ \\ %rc = asm(%syscall, @usize, \\ volatile=1, @@ -441,8 +308,7 @@ pub fn addCases(ctx: *TestContext) void { \\ %0 = call(@exit0, []) \\}) \\@9 = str("_start") - \\@10 = ref(@9) - \\@11 = export(@10, @start) + \\@11 = export(@9, "start") }, &[_][]const u8{""}, ); -- 2.54.0 From 20b4a2cf2cded8904a57714ed2b90c857f12c6b1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Jun 2020 21:28:42 -0400 Subject: [PATCH 112/295] self-hosted: add compare output test for new AST->ZIR code --- lib/std/zig.zig | 21 +++ src-self-hosted/main.zig | 19 +-- src-self-hosted/test.zig | 295 +++++++++++++++------------------ test/stage2/compare_output.zig | 137 ++++++++++++--- test/stage2/zir.zig | 200 +++++++++++----------- 5 files changed, 372 insertions(+), 300 deletions(-) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 87960d432100bb0985fab1152436213e5119e350..841827cc194c1e9ce609bb51669ecbfbc89cbaf1 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -43,6 +43,27 @@ pub fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usi return .{ .line = line, .column = column }; } +/// Returns the standard file system basename of a binary generated by the Zig compiler. +pub fn binNameAlloc( + allocator: *std.mem.Allocator, + root_name: []const u8, + target: std.Target, + output_mode: std.builtin.OutputMode, + link_mode: ?std.builtin.LinkMode, +) error{OutOfMemory}![]u8 { + switch (output_mode) { + .Exe => return std.fmt.allocPrint(allocator, "{}{}", .{ root_name, target.exeFileExt() }), + .Lib => { + const suffix = switch (link_mode orelse .Static) { + .Static => target.staticLibSuffix(), + .Dynamic => target.dynamicLibSuffix(), + }; + return std.fmt.allocPrint(allocator, "{}{}{}", .{ target.libPrefix(), root_name, suffix }); + }, + .Obj => return std.fmt.allocPrint(allocator, "{}{}", .{ root_name, target.oFileExt() }), + } +} + test "" { @import("std").meta.refAllDecls(@This()); } diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index aa5a56010cdf4e45a2be9bdc9bd86575732920fa..3743b4f3342bad0111060cd58ef073847960e8a2 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -50,8 +50,7 @@ pub fn log( const scope_prefix = "(" ++ switch (scope) { // Uncomment to hide logs //.compiler, - .link, - => return, + .link => return, else => @tagName(scope), } ++ "): "; @@ -431,21 +430,7 @@ fn buildOutputType( std.debug.warn("-fno-emit-bin not supported yet", .{}); process.exit(1); }, - .yes_default_path => switch (output_mode) { - .Exe => try std.fmt.allocPrint(arena, "{}{}", .{ root_name, target_info.target.exeFileExt() }), - .Lib => blk: { - const suffix = switch (link_mode orelse .Static) { - .Static => target_info.target.staticLibSuffix(), - .Dynamic => target_info.target.dynamicLibSuffix(), - }; - break :blk try std.fmt.allocPrint(arena, "{}{}{}", .{ - target_info.target.libPrefix(), - root_name, - suffix, - }); - }, - .Obj => try std.fmt.allocPrint(arena, "{}{}", .{ root_name, target_info.target.oFileExt() }), - }, + .yes_default_path => try std.zig.binNameAlloc(arena, root_name, target_info.target, output_mode, link_mode), .yes => |p| p, }; diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index a7942575d0f4e6bdea0b05ba40f959e5241ab718..14804cab68e41128507d16a4f8ae42828f81ea9f 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -21,32 +21,7 @@ const ErrorMsg = struct { }; pub const TestContext = struct { - // TODO: remove these. They are deprecated. - zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase), - - /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases) - zir_cases: std.ArrayList(ZIRCase), - - // TODO: remove - pub const ZIRCompareOutputCase = struct { - name: []const u8, - src_list: []const []const u8, - expected_stdout_list: []const []const u8, - }; - - pub const ZIRUpdateType = enum { - /// A transformation update transforms the input ZIR and tests against - /// the expected output - Transformation, - /// An error update attempts to compile bad code, and ensures that it - /// fails to compile, and for the expected reasons - Error, - /// An execution update compiles and runs the input ZIR, feeding in - /// provided input and ensuring that the outputs match what is expected - Execution, - /// A compilation update checks that the ZIR compiles without any issues - Compiles, - }; + zir_cases: std.ArrayList(Case), pub const ZIRUpdate = struct { /// The input to the current update. We simulate an incremental update @@ -57,58 +32,55 @@ pub const TestContext = struct { /// you can keep it mostly consistent, with small changes, testing the /// effects of the incremental compilation. src: [:0]const u8, - case: union(ZIRUpdateType) { - /// The expected output ZIR + case: union(enum) { + /// A transformation update transforms the input ZIR and tests against + /// the expected output ZIR. Transformation: [:0]const u8, + /// An error update attempts to compile bad code, and ensures that it + /// fails to compile, and for the expected reasons. /// A slice containing the expected errors *in sequential order*. Error: []const ErrorMsg, - - /// Input to feed to the program, and expected outputs. - /// - /// If stdout, stderr, and exit_code are all null, addZIRCase will - /// discard the test. To test for successful compilation, use a - /// dedicated Compile update instead. - Execution: struct { - stdin: ?[]const u8, - stdout: ?[]const u8, - stderr: ?[]const u8, - exit_code: ?u8, - }, - /// A Compiles test checks only that compilation of the given ZIR - /// succeeds. To test outputs, use an Execution test. It is good to - /// use a Compiles test before an Execution, as the overhead should - /// be low (due to incremental compilation) and TODO: provide a way - /// to check changed / new / etc decls in testing mode - /// (usingnamespace a debug info struct with a comptime flag?) - Compiles: void, + /// An execution update compiles and runs the input ZIR, feeding in + /// provided input and ensuring that the stdout match what is expected. + Execution: []const u8, }, }; - /// A ZIRCase consists of a set of *updates*. A update can transform ZIR, + /// A Case consists of a set of *updates*. A update can transform ZIR, /// compile it, ensure that compilation fails, and more. The same Module is /// used for each update, so each update's source is treated as a single file /// being updated by the test harness and incrementally compiled. - pub const ZIRCase = struct { + pub const Case = struct { name: []const u8, /// The platform the ZIR targets. For non-native platforms, an emulator /// such as QEMU is required for tests to complete. target: std.zig.CrossTarget, updates: std.ArrayList(ZIRUpdate), + output_mode: std.builtin.OutputMode, + /// Either ".zir" or ".zig" + extension: [4]u8, /// Adds a subcase in which the module is updated with new ZIR, and the /// resulting ZIR is validated. - pub fn addTransform(self: *ZIRCase, src: [:0]const u8, result: [:0]const u8) void { + pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void { self.updates.append(.{ .src = src, .case = .{ .Transformation = result }, }) catch unreachable; } + pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { + self.updates.append(.{ + .src = src, + .case = .{ .Execution = result }, + }) catch unreachable; + } + /// Adds a subcase in which the module is updated with invalid ZIR, and /// ensures that compilation fails for the expected reasons. /// /// Errors must be specified in sequential order. - pub fn addError(self: *ZIRCase, src: [:0]const u8, errors: []const []const u8) void { + pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { if (e[0] != ':') { @@ -146,15 +118,65 @@ pub const TestContext = struct { } }; - pub fn addZIRMulti( + pub fn addExeZIR( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - ) *ZIRCase { - const case = ZIRCase{ + ) *Case { + const case = Case{ .name = name, .target = target, .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), + .output_mode = .Exe, + .extension = ".zir".*, + }; + ctx.zir_cases.append(case) catch unreachable; + return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; + } + + pub fn addObjZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + ) *Case { + const case = Case{ + .name = name, + .target = target, + .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), + .output_mode = .Obj, + .extension = ".zir".*, + }; + ctx.zir_cases.append(case) catch unreachable; + return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; + } + + pub fn addExe( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + ) *Case { + const case = Case{ + .name = name, + .target = target, + .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), + .output_mode = .Exe, + .extension = ".zig".*, + }; + ctx.zir_cases.append(case) catch unreachable; + return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; + } + + pub fn addObj( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + ) *Case { + const case = Case{ + .name = name, + .target = target, + .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), + .output_mode = .Obj, + .extension = ".zig".*, }; ctx.zir_cases.append(case) catch unreachable; return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; @@ -163,14 +185,21 @@ pub const TestContext = struct { pub fn addZIRCompareOutput( ctx: *TestContext, name: []const u8, - src_list: []const []const u8, - expected_stdout_list: []const []const u8, + src: [:0]const u8, + expected_stdout: []const u8, ) void { - ctx.zir_cmp_output_cases.append(.{ - .name = name, - .src_list = src_list, - .expected_stdout_list = expected_stdout_list, - }) catch unreachable; + var c = ctx.addExeZIR(name, .{}); + c.addCompareOutput(src, expected_stdout); + } + + pub fn addCompareOutput( + ctx: *TestContext, + name: []const u8, + src: [:0]const u8, + expected_stdout: []const u8, + ) void { + var c = ctx.addExe(name, .{}); + c.addCompareOutput(src, expected_stdout); } pub fn addZIRTransform( @@ -180,7 +209,7 @@ pub const TestContext = struct { src: [:0]const u8, result: [:0]const u8, ) void { - var c = ctx.addZIRMulti(name, target); + var c = ctx.addObjZIR(name, target); c.addTransform(src, result); } @@ -191,20 +220,18 @@ pub const TestContext = struct { src: [:0]const u8, expected_errors: []const []const u8, ) void { - var c = ctx.addZIRMulti(name, target); + var c = ctx.addObjZIR(name, target); c.addError(src, expected_errors); } fn init() TestContext { const allocator = std.heap.page_allocator; return .{ - .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(allocator), - .zir_cases = std.ArrayList(ZIRCase).init(allocator), + .zir_cases = std.ArrayList(Case).init(allocator), }; } fn deinit(self: *TestContext) void { - self.zir_cmp_output_cases.deinit(); for (self.zir_cases.items) |c| { for (c.updates.items) |u| { if (u.case == .Error) { @@ -235,34 +262,24 @@ pub const TestContext = struct { progress.refresh(); const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target); - try self.runOneZIRCase(std.testing.allocator, &prg_node, case, info.target); - try std.testing.allocator_instance.validate(); - } - - // TODO: wipe the rest of this function - for (self.zir_cmp_output_cases.items) |case| { - std.testing.base_allocator_instance.reset(); - - var prg_node = root_node.start(case.name, case.src_list.len); - prg_node.activate(); - defer prg_node.end(); - - // So that we can see which test case failed when the leak checker goes off. - progress.refresh(); - - try self.runOneZIRCmpOutputCase(std.testing.allocator, &prg_node, case, native_info.target); + try self.runOneCase(std.testing.allocator, &prg_node, case, info.target); try std.testing.allocator_instance.validate(); } } - fn runOneZIRCase(self: *TestContext, allocator: *Allocator, prg_node: *std.Progress.Node, case: ZIRCase, target: std.Target) !void { + fn runOneCase(self: *TestContext, allocator: *Allocator, prg_node: *std.Progress.Node, case: Case, target: std.Target) !void { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const tmp_src_path = "test_case.zir"; + const root_name = "test_case"; + const tmp_src_path = try std.fmt.allocPrint(allocator, "{}{}", .{ root_name, case.extension }); + defer allocator.free(tmp_src_path); const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); + const bin_name = try std.zig.binNameAlloc(allocator, root_name, target, case.output_mode, null); + defer allocator.free(bin_name); + var module = try Module.init(allocator, .{ .target = target, // This is an Executable, as opposed to e.g. a *library*. This does @@ -271,17 +288,17 @@ pub const TestContext = struct { // TODO: support tests for object file building, and library builds // and linking. This will require a rework to support multi-file // tests. - .output_mode = .Obj, + .output_mode = case.output_mode, // TODO: support testing optimizations .optimize_mode = .Debug, .bin_file_dir = tmp.dir, - .bin_file_path = "test_case.o", + .bin_file_path = bin_name, .root_pkg = root_pkg, .keep_source_files_loaded = true, }); defer module.deinit(); - for (case.updates.items) |update| { + for (case.updates.items) |update, update_index| { var update_node = prg_node.start("update", 4); update_node.activate(); defer update_node.end(); @@ -293,6 +310,7 @@ pub const TestContext = struct { var module_node = update_node.start("parse/analysis/codegen", null); module_node.activate(); + try module.makeBinFileWritable(); try module.update(); module_node.end(); @@ -341,78 +359,41 @@ pub const TestContext = struct { } } }, - - else => return error.Unimplemented, - } - } - } - - fn runOneZIRCmpOutputCase( - self: *TestContext, - allocator: *Allocator, - prg_node: *std.Progress.Node, - case: ZIRCompareOutputCase, - target: std.Target, - ) !void { - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const tmp_src_path = "test-case.zir"; - const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); - defer root_pkg.destroy(); - - var module = try Module.init(allocator, .{ - .target = target, - .output_mode = .Exe, - .optimize_mode = .Debug, - .bin_file_dir = tmp.dir, - .bin_file_path = "a.out", - .root_pkg = root_pkg, - }); - defer module.deinit(); - - for (case.src_list) |source, i| { - var src_node = prg_node.start("update", 2); - src_node.activate(); - defer src_node.end(); - - try tmp.dir.writeFile(tmp_src_path, source); - - var update_node = src_node.start("parse,analysis,codegen", null); - update_node.activate(); - try module.makeBinFileWritable(); - try module.update(); - update_node.end(); - - var exec_result = x: { - var exec_node = src_node.start("execute", null); - exec_node.activate(); - defer exec_node.end(); - - try module.makeBinFileExecutable(); - break :x try std.ChildProcess.exec(.{ - .allocator = allocator, - .argv = &[_][]const u8{"./a.out"}, - .cwd_dir = tmp.dir, - }); - }; - defer allocator.free(exec_result.stdout); - defer allocator.free(exec_result.stderr); - switch (exec_result.term) { - .Exited => |code| { - if (code != 0) { - std.debug.warn("elf file exited with code {}\n", .{code}); - return error.BinaryBadExitCode; + .Execution => |expected_stdout| { + var exec_result = x: { + var exec_node = update_node.start("execute", null); + exec_node.activate(); + defer exec_node.end(); + + try module.makeBinFileExecutable(); + + const exe_path = try std.fmt.allocPrint(allocator, "." ++ std.fs.path.sep_str ++ "{}", .{bin_name}); + defer allocator.free(exe_path); + + break :x try std.ChildProcess.exec(.{ + .allocator = allocator, + .argv = &[_][]const u8{exe_path}, + .cwd_dir = tmp.dir, + }); + }; + defer allocator.free(exec_result.stdout); + defer allocator.free(exec_result.stderr); + switch (exec_result.term) { + .Exited => |code| { + if (code != 0) { + std.debug.warn("elf file exited with code {}\n", .{code}); + return error.BinaryBadExitCode; + } + }, + else => return error.BinaryCrashed, + } + if (!std.mem.eql(u8, expected_stdout, exec_result.stdout)) { + std.debug.panic( + "update index {}, mismatched stdout\n====Expected (len={}):====\n{}\n====Actual (len={}):====\n{}\n========\n", + .{ update_index, expected_stdout.len, expected_stdout, exec_result.stdout.len, exec_result.stdout }, + ); } }, - else => return error.BinaryCrashed, - } - const expected_stdout = case.expected_stdout_list[i]; - if (!std.mem.eql(u8, expected_stdout, exec_result.stdout)) { - std.debug.panic( - "update index {}, mismatched stdout\n====Expected (len={}):====\n{}\n====Actual (len={}):====\n{}\n========\n", - .{ i, expected_stdout.len, expected_stdout, exec_result.stdout.len, exec_result.stdout }, - ); } } } diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 1f289c2762ae8bd98e6933c9ee5e145ae91c372e..182ff8131a17698669757dbe0fd047ec78a3c813 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -1,28 +1,121 @@ const std = @import("std"); const TestContext = @import("../../src-self-hosted/test.zig").TestContext; +// self-hosted does not yet support PE executable files / COFF object files +// or mach-o files. So we do these test cases cross compiling for x86_64-linux. +const linux_x64 = std.zig.CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .linux, +}; pub fn addCases(ctx: *TestContext) !void { - // TODO: re-enable these tests. - // https://github.com/ziglang/zig/issues/1364 + if (std.Target.current.os.tag != .linux or + std.Target.current.cpu.arch != .x86_64) + { + // TODO implement self-hosted PE (.exe file) linking + // TODO implement more ZIR so we don't depend on x86_64-linux + return; + } - //// hello world - //try ctx.testCompareOutputLibC( - // \\extern fn puts([*]const u8) void; - // \\pub export fn main() c_int { - // \\ puts("Hello, world!"); - // \\ return 0; - // \\} - //, "Hello, world!" ++ std.cstr.line_sep); - - //// function calling another function - //try ctx.testCompareOutputLibC( - // \\extern fn puts(s: [*]const u8) void; - // \\pub export fn main() c_int { - // \\ return foo("OK"); - // \\} - // \\fn foo(s: [*]const u8) c_int { - // \\ puts(s); - // \\ return 0; - // \\} - //, "OK" ++ std.cstr.line_sep); + { + var case = ctx.addExe("hello world with updates", linux_x64); + // Regular old hello world + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (1), + \\ [arg1] "{rdi}" (1), + \\ [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{rdx}" (14) + \\ : "rcx", "r11", "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + // Now change the message only + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (1), + \\ [arg1] "{rdi}" (1), + \\ [arg2] "{rsi}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")), + \\ [arg3] "{rdx}" (104) + \\ : "rcx", "r11", "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n", + ); + // Now we print it twice. + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ print(); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (1), + \\ [arg1] "{rdi}" (1), + \\ [arg2] "{rsi}" (@ptrToInt("What is up? This is a longer message that will force the data to be relocated in virtual address space.\n")), + \\ [arg3] "{rdx}" (104) + \\ : "rcx", "r11", "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + \\What is up? This is a longer message that will force the data to be relocated in virtual address space. + \\What is up? This is a longer message that will force the data to be relocated in virtual address space. + \\ + ); + } } diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index 832629c6e7b00a029adc2ceedbd4e23670163b10..673be6d99f96bfefb6696cbe5ab9315d80f46e20 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -86,7 +86,7 @@ pub fn addCases(ctx: *TestContext) void { ); { - var case = ctx.addZIRMulti("reference cycle with compile error in the cycle", linux_x64); + var case = ctx.addObjZIR("reference cycle with compile error in the cycle", linux_x64); case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) @@ -207,109 +207,101 @@ pub fn addCases(ctx: *TestContext) void { return; } - ctx.addZIRCompareOutput( - "hello world ZIR", - &[_][]const u8{ - \\@noreturn = primitive(noreturn) - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@msg = str("Hello, world!\n") - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %SYS_exit_group = int(231) - \\ %exit_code = as(@usize, @0) - \\ - \\ %syscall = str("syscall") - \\ %sysoutreg = str("={rax}") - \\ %rax = str("{rax}") - \\ %rdi = str("{rdi}") - \\ %rcx = str("rcx") - \\ %rdx = str("{rdx}") - \\ %rsi = str("{rsi}") - \\ %r11 = str("r11") - \\ %memory = str("memory") - \\ - \\ %SYS_write = as(@usize, @1) - \\ %STDOUT_FILENO = as(@usize, @1) - \\ - \\ %msg_addr = ptrtoint(@msg) - \\ - \\ %len_name = str("len") - \\ %msg_len_ptr = fieldptr(@msg, %len_name) - \\ %msg_len = deref(%msg_len_ptr) - \\ %rc_write = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi, %rsi, %rdx], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) - \\ - \\ %rc_exit = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_exit_group, %exit_code]) - \\ - \\ %99 = unreachable() - \\}); - \\ - \\@9 = str("_start") - \\@11 = export(@9, "start") - }, - &[_][]const u8{ - \\Hello, world! - \\ - }, + ctx.addZIRCompareOutput("hello world ZIR", + \\@noreturn = primitive(noreturn) + \\@void = primitive(void) + \\@usize = primitive(usize) + \\@0 = int(0) + \\@1 = int(1) + \\@2 = int(2) + \\@3 = int(3) + \\ + \\@msg = str("Hello, world!\n") + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %SYS_exit_group = int(231) + \\ %exit_code = as(@usize, @0) + \\ + \\ %syscall = str("syscall") + \\ %sysoutreg = str("={rax}") + \\ %rax = str("{rax}") + \\ %rdi = str("{rdi}") + \\ %rcx = str("rcx") + \\ %rdx = str("{rdx}") + \\ %rsi = str("{rsi}") + \\ %r11 = str("r11") + \\ %memory = str("memory") + \\ + \\ %SYS_write = as(@usize, @1) + \\ %STDOUT_FILENO = as(@usize, @1) + \\ + \\ %msg_addr = ptrtoint(@msg) + \\ + \\ %len_name = str("len") + \\ %msg_len_ptr = fieldptr(@msg, %len_name) + \\ %msg_len = deref(%msg_len_ptr) + \\ %rc_write = asm(%syscall, @usize, + \\ volatile=1, + \\ output=%sysoutreg, + \\ inputs=[%rax, %rdi, %rsi, %rdx], + \\ clobbers=[%rcx, %r11, %memory], + \\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len]) + \\ + \\ %rc_exit = asm(%syscall, @usize, + \\ volatile=1, + \\ output=%sysoutreg, + \\ inputs=[%rax, %rdi], + \\ clobbers=[%rcx, %r11, %memory], + \\ args=[%SYS_exit_group, %exit_code]) + \\ + \\ %99 = unreachable() + \\}); + \\ + \\@9 = str("_start") + \\@11 = export(@9, "start") + , + \\Hello, world! + \\ ); - ctx.addZIRCompareOutput( - "function call with no args no return value", - &[_][]const u8{ - \\@noreturn = primitive(noreturn) - \\@void = primitive(void) - \\@usize = primitive(usize) - \\@0 = int(0) - \\@1 = int(1) - \\@2 = int(2) - \\@3 = int(3) - \\ - \\@exit0_fnty = fntype([], @noreturn) - \\@exit0 = fn(@exit0_fnty, { - \\ %SYS_exit_group = int(231) - \\ %exit_code = as(@usize, @0) - \\ - \\ %syscall = str("syscall") - \\ %sysoutreg = str("={rax}") - \\ %rax = str("{rax}") - \\ %rdi = str("{rdi}") - \\ %rcx = str("rcx") - \\ %r11 = str("r11") - \\ %memory = str("memory") - \\ - \\ %rc = asm(%syscall, @usize, - \\ volatile=1, - \\ output=%sysoutreg, - \\ inputs=[%rax, %rdi], - \\ clobbers=[%rcx, %r11, %memory], - \\ args=[%SYS_exit_group, %exit_code]) - \\ - \\ %99 = unreachable() - \\}); - \\ - \\@start_fnty = fntype([], @noreturn, cc=Naked) - \\@start = fn(@start_fnty, { - \\ %0 = call(@exit0, []) - \\}) - \\@9 = str("_start") - \\@11 = export(@9, "start") - }, - &[_][]const u8{""}, - ); + ctx.addZIRCompareOutput("function call with no args no return value", + \\@noreturn = primitive(noreturn) + \\@void = primitive(void) + \\@usize = primitive(usize) + \\@0 = int(0) + \\@1 = int(1) + \\@2 = int(2) + \\@3 = int(3) + \\ + \\@exit0_fnty = fntype([], @noreturn) + \\@exit0 = fn(@exit0_fnty, { + \\ %SYS_exit_group = int(231) + \\ %exit_code = as(@usize, @0) + \\ + \\ %syscall = str("syscall") + \\ %sysoutreg = str("={rax}") + \\ %rax = str("{rax}") + \\ %rdi = str("{rdi}") + \\ %rcx = str("rcx") + \\ %r11 = str("r11") + \\ %memory = str("memory") + \\ + \\ %rc = asm(%syscall, @usize, + \\ volatile=1, + \\ output=%sysoutreg, + \\ inputs=[%rax, %rdi], + \\ clobbers=[%rcx, %r11, %memory], + \\ args=[%SYS_exit_group, %exit_code]) + \\ + \\ %99 = unreachable() + \\}); + \\ + \\@start_fnty = fntype([], @noreturn, cc=Naked) + \\@start = fn(@start_fnty, { + \\ %0 = call(@exit0, []) + \\}) + \\@9 = str("_start") + \\@11 = export(@9, "start") + , ""); } -- 2.54.0 From 7875649c2481f90b918581670c9268d6033f873f Mon Sep 17 00:00:00 2001 From: Code Hz Date: Thu, 25 Jun 2020 10:03:49 +0800 Subject: [PATCH 113/295] Pdb.openFile use `[]const u8` instead of `[]u8` --- lib/std/pdb.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index 62681968a6da717f580494df3e2adf2b86f3ab7d..a702a8aed6e7821898a64cf1e8ea1cf2e713a975 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -469,7 +469,7 @@ pub const Pdb = struct { msf: Msf, - pub fn openFile(self: *Pdb, coff_ptr: *coff.Coff, file_name: []u8) !void { + pub fn openFile(self: *Pdb, coff_ptr: *coff.Coff, file_name: []const u8) !void { self.in_file = try fs.cwd().openFile(file_name, .{ .intended_io_mode = .blocking }); self.allocator = coff_ptr.allocator; self.coff = coff_ptr; -- 2.54.0 From 5d7e981f95423b3b009e0d7eebccae6c856f68ca Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 24 Jun 2020 22:31:54 -0400 Subject: [PATCH 114/295] Clean up test harness --- src-self-hosted/test.zig | 168 +++++++++++++-------------------- test/stage2/compare_output.zig | 8 +- test/stage2/compile_errors.zig | 22 ++--- test/stage2/test.zig | 2 +- test/stage2/zir.zig | 18 ++-- 5 files changed, 92 insertions(+), 126 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 14804cab68e41128507d16a4f8ae42828f81ea9f..cd53eaeb6806e719b41434b26959aa349d6098f2 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -21,9 +21,10 @@ const ErrorMsg = struct { }; pub const TestContext = struct { - zir_cases: std.ArrayList(Case), + /// TODO: find a way to treat cases as individual tests (shouldn't show "1 test passed" if there are 200 cases) + cases: std.ArrayList(Case), - pub const ZIRUpdate = struct { + pub const Update = struct { /// The input to the current update. We simulate an incremental update /// with the file's contents changed to this value each update. /// @@ -40,67 +41,70 @@ pub const TestContext = struct { /// fails to compile, and for the expected reasons. /// A slice containing the expected errors *in sequential order*. Error: []const ErrorMsg, - /// An execution update compiles and runs the input ZIR, feeding in - /// provided input and ensuring that the stdout match what is expected. + /// An execution update compiles and runs the input, testing the + /// stdout against the expected results Execution: []const u8, }, }; - /// A Case consists of a set of *updates*. A update can transform ZIR, - /// compile it, ensure that compilation fails, and more. The same Module is - /// used for each update, so each update's source is treated as a single file - /// being updated by the test harness and incrementally compiled. + pub const TestType = enum { + Zig, + ZIR, + }; + + /// A Case consists of a set of *updates*. The same Module is used for each + /// update, so each update's source is treated as a single file being + /// updated by the test harness and incrementally compiled. pub const Case = struct { name: []const u8, - /// The platform the ZIR targets. For non-native platforms, an emulator + /// The platform the test targets. For non-native platforms, an emulator /// such as QEMU is required for tests to complete. target: std.zig.CrossTarget, - updates: std.ArrayList(ZIRUpdate), output_mode: std.builtin.OutputMode, - /// Either ".zir" or ".zig" - extension: [4]u8, + updates: std.ArrayList(Update), + @"type": TestType, /// Adds a subcase in which the module is updated with new ZIR, and the /// resulting ZIR is validated. - pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void { - self.updates.append(.{ + pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) !void { + try self.updates.append(.{ .src = src, .case = .{ .Transformation = result }, - }) catch unreachable; + }); } - pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { - self.updates.append(.{ + pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) !void { + try self.updates.append(.{ .src = src, .case = .{ .Execution = result }, - }) catch unreachable; + }); } /// Adds a subcase in which the module is updated with invalid ZIR, and /// ensures that compilation fails for the expected reasons. /// /// Errors must be specified in sequential order. - pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { - var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; + pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) !void { + var array = try self.updates.allocator.alloc(ErrorMsg, errors.len); for (errors) |e, i| { if (e[0] != ':') { - std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); } var cur = e[1..]; var line_index = std.mem.indexOf(u8, cur, ":"); if (line_index == null) { - std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); } const line = std.fmt.parseInt(u32, cur[0..line_index.?], 10) catch @panic("Unable to parse line number"); cur = cur[line_index.? + 1 ..]; const column_index = std.mem.indexOf(u8, cur, ":"); if (column_index == null) { - std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); } const column = std.fmt.parseInt(u32, cur[0..column_index.?], 10) catch @panic("Unable to parse column number"); cur = cur[column_index.? + 2 ..]; if (!std.mem.eql(u8, cur[0..7], "error: ")) { - std.debug.panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n", .{}); + @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); } const msg = cur[7..]; @@ -114,125 +118,87 @@ pub const TestContext = struct { .column = column - 1, }; } - self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable; + try self.updates.append(.{ .src = src, .case = .{ .Error = array } }); } }; - pub fn addExeZIR( - ctx: *TestContext, - name: []const u8, - target: std.zig.CrossTarget, - ) *Case { - const case = Case{ - .name = name, - .target = target, - .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), - .output_mode = .Exe, - .extension = ".zir".*, - }; - ctx.zir_cases.append(case) catch unreachable; - return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; - } - - pub fn addObjZIR( - ctx: *TestContext, - name: []const u8, - target: std.zig.CrossTarget, - ) *Case { - const case = Case{ - .name = name, - .target = target, - .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), - .output_mode = .Obj, - .extension = ".zir".*, - }; - ctx.zir_cases.append(case) catch unreachable; - return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; - } - pub fn addExe( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - ) *Case { + T: TestType, + ) !*Case { const case = Case{ .name = name, .target = target, - .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), + .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Exe, - .extension = ".zig".*, + .@"type" = T, }; - ctx.zir_cases.append(case) catch unreachable; - return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; + try ctx.cases.append(case); + return &ctx.cases.items[ctx.cases.items.len - 1]; } pub fn addObj( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, - ) *Case { - const case = Case{ + T: TestType, + ) !*Case { + try ctx.cases.append(Case{ .name = name, .target = target, - .updates = std.ArrayList(ZIRUpdate).init(ctx.zir_cases.allocator), + .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, - .extension = ".zig".*, - }; - ctx.zir_cases.append(case) catch unreachable; - return &ctx.zir_cases.items[ctx.zir_cases.items.len - 1]; - } - - pub fn addZIRCompareOutput( - ctx: *TestContext, - name: []const u8, - src: [:0]const u8, - expected_stdout: []const u8, - ) void { - var c = ctx.addExeZIR(name, .{}); - c.addCompareOutput(src, expected_stdout); + .@"type" = T, + }); + return &ctx.cases.items[ctx.cases.items.len - 1]; } pub fn addCompareOutput( ctx: *TestContext, name: []const u8, + T: TestType, src: [:0]const u8, expected_stdout: []const u8, - ) void { - var c = ctx.addExe(name, .{}); - c.addCompareOutput(src, expected_stdout); + ) !void { + var c = try ctx.addExe(name, .{}, T); + try c.addCompareOutput(src, expected_stdout); } - pub fn addZIRTransform( + pub fn addTransform( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, + T: TestType, src: [:0]const u8, result: [:0]const u8, - ) void { - var c = ctx.addObjZIR(name, target); - c.addTransform(src, result); + ) !void { + var c = try ctx.addObj(name, target, T); + try c.addTransform(src, result); } - pub fn addZIRError( + pub fn addError( ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, + T: TestType, src: [:0]const u8, expected_errors: []const []const u8, - ) void { - var c = ctx.addObjZIR(name, target); - c.addError(src, expected_errors); + ) !void { + var c = try ctx.addObj(name, target, T); + try c.addError(src, expected_errors); } fn init() TestContext { const allocator = std.heap.page_allocator; return .{ - .zir_cases = std.ArrayList(Case).init(allocator), + .cases = std.ArrayList(Case).init(allocator), }; } fn deinit(self: *TestContext) void { - for (self.zir_cases.items) |c| { + for (self.cases.items) |c| { for (c.updates.items) |u| { if (u.case == .Error) { c.updates.allocator.free(u.case.Error); @@ -240,18 +206,18 @@ pub const TestContext = struct { } c.updates.deinit(); } - self.zir_cases.deinit(); + self.cases.deinit(); self.* = undefined; } fn run(self: *TestContext) !void { var progress = std.Progress{}; - const root_node = try progress.start("zir", self.zir_cases.items.len); + const root_node = try progress.start("tests", self.cases.items.len); defer root_node.end(); const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{}); - for (self.zir_cases.items) |case| { + for (self.cases.items) |case| { std.testing.base_allocator_instance.reset(); var prg_node = root_node.start(case.name, case.updates.items.len); @@ -267,17 +233,19 @@ pub const TestContext = struct { } } - fn runOneCase(self: *TestContext, allocator: *Allocator, prg_node: *std.Progress.Node, case: Case, target: std.Target) !void { + fn runOneCase(self: *TestContext, allocator: *Allocator, root_node: *std.Progress.Node, case: Case, target: std.Target) !void { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const root_name = "test_case"; - const tmp_src_path = try std.fmt.allocPrint(allocator, "{}{}", .{ root_name, case.extension }); - defer allocator.free(tmp_src_path); + const tmp_src_path = if (case.type == .Zig) "test_case.zig" else if (case.type == .ZIR) "test_case.zir" else unreachable; const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); - const bin_name = try std.zig.binNameAlloc(allocator, root_name, target, case.output_mode, null); + var prg_node = root_node.start(case.name, case.updates.items.len); + prg_node.activate(); + defer prg_node.end(); + + const bin_name = try std.zig.binNameAlloc(allocator, "test_case", target, case.output_mode, null); defer allocator.free(bin_name); var module = try Module.init(allocator, .{ diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 182ff8131a17698669757dbe0fd047ec78a3c813..4332ed120cf84ccb46a740e619f9c5ca7d382ab6 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -17,9 +17,9 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.addExe("hello world with updates", linux_x64); + var case = try ctx.addExe("hello world with updates", linux_x64, .Zig); // Regular old hello world - case.addCompareOutput( + try case.addCompareOutput( \\export fn _start() noreturn { \\ print(); \\ @@ -51,7 +51,7 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); // Now change the message only - case.addCompareOutput( + try case.addCompareOutput( \\export fn _start() noreturn { \\ print(); \\ @@ -83,7 +83,7 @@ pub fn addCases(ctx: *TestContext) !void { "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n", ); // Now we print it twice. - case.addCompareOutput( + try case.addCompareOutput( \\export fn _start() noreturn { \\ print(); \\ print(); diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 7596894dca5b3ad25bef7effc86c8a0e22b4fbbd..4f4ec611dbc6fc2fba5f6515918b4466d82d4eb1 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -9,7 +9,7 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - ctx.addZIRError("call undefined local", linux_x64, + try ctx.addError("call undefined local", linux_x64, .ZIR, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -19,7 +19,7 @@ pub fn addCases(ctx: *TestContext) !void { // TODO: address inconsistency in this message and the one in the next test , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); - ctx.addZIRError("call with non-existent target", linux_x64, + try ctx.addError("call with non-existent target", linux_x64, .ZIR, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -31,7 +31,7 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{":5:13: error: decl 'notafunc' not found"}); // TODO: this error should occur at the call site, not the fntype decl - ctx.addZIRError("call naked function", linux_x64, + try ctx.addError("call naked function", linux_x64, .ZIR, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -45,17 +45,15 @@ pub fn addCases(ctx: *TestContext) !void { // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 - // TODO: add Zig AST -> ZIR testing pipeline - //try ctx.testCompileError( - // \\export fn entry() void {} - // \\export fn entry() void {} - //, "1.zig", 2, 8, "exported symbol collision: 'entry'"); - - //try ctx.testCompileError( - // \\fn() void {} - //, "1.zig", 1, 1, "missing function name"); + // try ctx.addError("Export same symbol twice", linux_x64, .Zig, + // \\export fn entry() void {} + // \\export fn entry() void {} + // , &[_][]const u8{":2:1: error: exported symbol collision"}); + // try ctx.addError("Missing function name", linux_x64, .Zig, + // \\fn() void {} + // , &[_][]const u8{":1:3: error: missing function name"}); //try ctx.testCompileError( // \\comptime { // \\ return; diff --git a/test/stage2/test.zig b/test/stage2/test.zig index dc92f99506ea8fd5e4b7ca34236f033e368a3ebb..e0ef2915885bb7f8078c19a1d3fe660b84ff4827 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -3,5 +3,5 @@ const TestContext = @import("../../src-self-hosted/test.zig").TestContext; pub fn addCases(ctx: *TestContext) !void { try @import("compile_errors.zig").addCases(ctx); try @import("compare_output.zig").addCases(ctx); - @import("zir.zig").addCases(ctx); + try @import("zir.zig").addCases(ctx); } diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index 673be6d99f96bfefb6696cbe5ab9315d80f46e20..a3dec10e7389a1a34d94889d3ff912bcd0eb4c3e 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -8,8 +8,8 @@ const linux_x64 = std.zig.CrossTarget{ .os_tag = .linux, }; -pub fn addCases(ctx: *TestContext) void { - ctx.addZIRTransform("referencing decls which appear later in the file", linux_x64, +pub fn addCases(ctx: *TestContext) !void { + try ctx.addTransform("referencing decls which appear later in the file", linux_x64, .ZIR, \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -32,7 +32,7 @@ pub fn addCases(ctx: *TestContext) void { \\}) \\ ); - ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, + try ctx.addTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, .ZIR, \\@void = primitive(void) \\@usize = primitive(usize) \\@fnty = fntype([], @void, cc=C) @@ -86,8 +86,8 @@ pub fn addCases(ctx: *TestContext) void { ); { - var case = ctx.addObjZIR("reference cycle with compile error in the cycle", linux_x64); - case.addTransform( + var case = try ctx.addObj("reference cycle with compile error in the cycle", linux_x64, .ZIR); + try case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -133,7 +133,7 @@ pub fn addCases(ctx: *TestContext) void { \\ ); // Now we introduce a compile error - case.addError( + try case.addError( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -163,7 +163,7 @@ pub fn addCases(ctx: *TestContext) void { // Now we remove the call to `a`. `a` and `b` form a cycle, but no entry points are // referencing either of them. This tests that the cycle is detected, and the error // goes away. - case.addTransform( + try case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -207,7 +207,7 @@ pub fn addCases(ctx: *TestContext) void { return; } - ctx.addZIRCompareOutput("hello world ZIR", + try ctx.addCompareOutput("hello world ZIR", .ZIR, \\@noreturn = primitive(noreturn) \\@void = primitive(void) \\@usize = primitive(usize) @@ -265,7 +265,7 @@ pub fn addCases(ctx: *TestContext) void { \\ ); - ctx.addZIRCompareOutput("function call with no args no return value", + try ctx.addCompareOutput("function call with no args no return value", .ZIR, \\@noreturn = primitive(noreturn) \\@void = primitive(void) \\@usize = primitive(usize) -- 2.54.0 From c88edbc46fbbdc5a97c9703d09097af5f8d2a653 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 24 Jun 2020 23:34:58 -0400 Subject: [PATCH 115/295] OOM -> catch unreachable --- src-self-hosted/test.zig | 50 +++++++++++++++------------------- test/stage2/compare_output.zig | 8 +++--- test/stage2/compile_errors.zig | 24 ++++++++-------- test/stage2/zir.zig | 16 +++++------ 4 files changed, 46 insertions(+), 52 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index cd53eaeb6806e719b41434b26959aa349d6098f2..3f7ff3303a00a52c243dd9db5bc97ea4588c7b54 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -66,26 +66,26 @@ pub const TestContext = struct { /// Adds a subcase in which the module is updated with new ZIR, and the /// resulting ZIR is validated. - pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) !void { - try self.updates.append(.{ + pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void { + self.updates.append(.{ .src = src, .case = .{ .Transformation = result }, - }); + }) catch unreachable; } - pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) !void { - try self.updates.append(.{ + pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { + self.updates.append(.{ .src = src, .case = .{ .Execution = result }, - }); + }) catch unreachable; } /// Adds a subcase in which the module is updated with invalid ZIR, and /// ensures that compilation fails for the expected reasons. /// /// Errors must be specified in sequential order. - pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) !void { - var array = try self.updates.allocator.alloc(ErrorMsg, errors.len); + pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { + var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { if (e[0] != ':') { @panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n"); @@ -118,7 +118,7 @@ pub const TestContext = struct { .column = column - 1, }; } - try self.updates.append(.{ .src = src, .case = .{ .Error = array } }); + self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable; } }; @@ -127,15 +127,14 @@ pub const TestContext = struct { name: []const u8, target: std.zig.CrossTarget, T: TestType, - ) !*Case { - const case = Case{ + ) *Case { + ctx.cases.append(Case{ .name = name, .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Exe, .@"type" = T, - }; - try ctx.cases.append(case); + }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } @@ -144,14 +143,14 @@ pub const TestContext = struct { name: []const u8, target: std.zig.CrossTarget, T: TestType, - ) !*Case { - try ctx.cases.append(Case{ + ) *Case { + ctx.cases.append(Case{ .name = name, .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, .@"type" = T, - }); + }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } @@ -161,9 +160,8 @@ pub const TestContext = struct { T: TestType, src: [:0]const u8, expected_stdout: []const u8, - ) !void { - var c = try ctx.addExe(name, .{}, T); - try c.addCompareOutput(src, expected_stdout); + ) void { + ctx.addExe(name, .{}, T).addCompareOutput(src, expected_stdout); } pub fn addTransform( @@ -173,9 +171,8 @@ pub const TestContext = struct { T: TestType, src: [:0]const u8, result: [:0]const u8, - ) !void { - var c = try ctx.addObj(name, target, T); - try c.addTransform(src, result); + ) void { + ctx.addObj(name, target, T).addTransform(src, result); } pub fn addError( @@ -185,16 +182,13 @@ pub const TestContext = struct { T: TestType, src: [:0]const u8, expected_errors: []const []const u8, - ) !void { - var c = try ctx.addObj(name, target, T); - try c.addError(src, expected_errors); + ) void { + ctx.addObj(name, target, T).addError(src, expected_errors); } fn init() TestContext { const allocator = std.heap.page_allocator; - return .{ - .cases = std.ArrayList(Case).init(allocator), - }; + return .{ .cases = std.ArrayList(Case).init(allocator) }; } fn deinit(self: *TestContext) void { diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 4332ed120cf84ccb46a740e619f9c5ca7d382ab6..902c1e493f98980106af05b511192474dc523b5c 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -17,9 +17,9 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = try ctx.addExe("hello world with updates", linux_x64, .Zig); + var case = ctx.addExe("hello world with updates", linux_x64, .Zig); // Regular old hello world - try case.addCompareOutput( + case.addCompareOutput( \\export fn _start() noreturn { \\ print(); \\ @@ -51,7 +51,7 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); // Now change the message only - try case.addCompareOutput( + case.addCompareOutput( \\export fn _start() noreturn { \\ print(); \\ @@ -83,7 +83,7 @@ pub fn addCases(ctx: *TestContext) !void { "What is up? This is a longer message that will force the data to be relocated in virtual address space.\n", ); // Now we print it twice. - try case.addCompareOutput( + case.addCompareOutput( \\export fn _start() noreturn { \\ print(); \\ print(); diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 4f4ec611dbc6fc2fba5f6515918b4466d82d4eb1..1ee8e9184b5863884cf86f5e414c6edd955811c9 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -9,7 +9,7 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - try ctx.addError("call undefined local", linux_x64, .ZIR, + ctx.addError("call undefined local", linux_x64, .ZIR, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -19,7 +19,7 @@ pub fn addCases(ctx: *TestContext) !void { // TODO: address inconsistency in this message and the one in the next test , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); - try ctx.addError("call with non-existent target", linux_x64, .ZIR, + ctx.addError("call with non-existent target", linux_x64, .ZIR, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -31,7 +31,7 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{":5:13: error: decl 'notafunc' not found"}); // TODO: this error should occur at the call site, not the fntype decl - try ctx.addError("call naked function", linux_x64, .ZIR, + ctx.addError("call naked function", linux_x64, .ZIR, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -46,51 +46,51 @@ pub fn addCases(ctx: *TestContext) !void { // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 - // try ctx.addError("Export same symbol twice", linux_x64, .Zig, + // ctx.addError("Export same symbol twice", linux_x64, .Zig, // \\export fn entry() void {} // \\export fn entry() void {} // , &[_][]const u8{":2:1: error: exported symbol collision"}); - // try ctx.addError("Missing function name", linux_x64, .Zig, + // ctx.addError("Missing function name", linux_x64, .Zig, // \\fn() void {} // , &[_][]const u8{":1:3: error: missing function name"}); - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ return; // \\} //, "1.zig", 2, 5, "return expression outside function definition"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\export fn entry() void { // \\ defer return; // \\} //, "1.zig", 2, 11, "cannot return from defer expression"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\export fn entry() c_int { // \\ return 36893488147419103232; // \\} //, "1.zig", 2, 12, "integer value '36893488147419103232' cannot be stored in type 'c_int'"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ var a: *align(4) align(4) i32 = 0; // \\} //, "1.zig", 2, 22, "Extra align qualifier"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ var b: *const const i32 = 0; // \\} //, "1.zig", 2, 19, "Extra align qualifier"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ var c: *volatile volatile i32 = 0; // \\} //, "1.zig", 2, 22, "Extra align qualifier"); - //try ctx.testCompileError( + //ctx.testCompileError( // \\comptime { // \\ var d: *allowzero allowzero i32 = 0; // \\} diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index a3dec10e7389a1a34d94889d3ff912bcd0eb4c3e..17d5ce9b5b19b1aba4f893a7715a8608bbce8c8a 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -9,7 +9,7 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - try ctx.addTransform("referencing decls which appear later in the file", linux_x64, .ZIR, + ctx.addTransform("referencing decls which appear later in the file", linux_x64, .ZIR, \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -32,7 +32,7 @@ pub fn addCases(ctx: *TestContext) !void { \\}) \\ ); - try ctx.addTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, .ZIR, + ctx.addTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, .ZIR, \\@void = primitive(void) \\@usize = primitive(usize) \\@fnty = fntype([], @void, cc=C) @@ -86,8 +86,8 @@ pub fn addCases(ctx: *TestContext) !void { ); { - var case = try ctx.addObj("reference cycle with compile error in the cycle", linux_x64, .ZIR); - try case.addTransform( + var case = ctx.addObj("reference cycle with compile error in the cycle", linux_x64, .ZIR); + case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -133,7 +133,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); // Now we introduce a compile error - try case.addError( + case.addError( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -163,7 +163,7 @@ pub fn addCases(ctx: *TestContext) !void { // Now we remove the call to `a`. `a` and `b` form a cycle, but no entry points are // referencing either of them. This tests that the cycle is detected, and the error // goes away. - try case.addTransform( + case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -207,7 +207,7 @@ pub fn addCases(ctx: *TestContext) !void { return; } - try ctx.addCompareOutput("hello world ZIR", .ZIR, + ctx.addCompareOutput("hello world ZIR", .ZIR, \\@noreturn = primitive(noreturn) \\@void = primitive(void) \\@usize = primitive(usize) @@ -265,7 +265,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); - try ctx.addCompareOutput("function call with no args no return value", .ZIR, + ctx.addCompareOutput("function call with no args no return value", .ZIR, \\@noreturn = primitive(noreturn) \\@void = primitive(void) \\@usize = primitive(usize) -- 2.54.0 From 2fde8249b72f22ddfa16793c245315f30c7f3ff8 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 24 Jun 2020 21:04:31 +0300 Subject: [PATCH 116/295] Fixed crash when resolving peer types of *[N:s]const T and [*:s]const T --- src/ir.cpp | 1 + test/stage1/behavior.zig | 1 + test/stage1/behavior/bugs/5413.zig | 6 ++++++ 3 files changed, 8 insertions(+) create mode 100644 test/stage1/behavior/bugs/5413.zig diff --git a/src/ir.cpp b/src/ir.cpp index 01c7936f75277b739db930798d6648166b1a50c1..3e2b75822e6cd553439194547747a940f4cf9c97 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -12603,6 +12603,7 @@ static ZigType *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, ZigT prev_type->data.pointer.child_type->id == ZigTypeIdArray && ((cur_type->id == ZigTypeIdPointer && cur_type->data.pointer.ptr_len == PtrLenUnknown))) { + convert_to_const_slice = false; prev_inst = cur_inst; if (prev_type->data.pointer.is_const && !cur_type->data.pointer.is_const) { diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index 8c567fa99f57e0aa9fd437c09ca0997ad82fc00f..a15f1f26b9127e2d30c29f38cb2139e3e7690e0f 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -50,6 +50,7 @@ comptime { _ = @import("behavior/bugs/4769_b.zig"); _ = @import("behavior/bugs/4769_c.zig"); _ = @import("behavior/bugs/4954.zig"); + _ = @import("behavior/bugs/5413.zig"); _ = @import("behavior/bugs/5474.zig"); _ = @import("behavior/bugs/5487.zig"); _ = @import("behavior/bugs/394.zig"); diff --git a/test/stage1/behavior/bugs/5413.zig b/test/stage1/behavior/bugs/5413.zig new file mode 100644 index 0000000000000000000000000000000000000000..5ef533a761b03f5de84a3cb4b5026363f83c34d9 --- /dev/null +++ b/test/stage1/behavior/bugs/5413.zig @@ -0,0 +1,6 @@ +const expect = @import("std").testing.expect; + +test "Peer type resolution with string literals and unknown length u8 pointers" { + expect(@TypeOf("", "a", @as([*:0]const u8, "")) == [*:0]const u8); + expect(@TypeOf(@as([*:0]const u8, "baz"), "foo", "bar") == [*:0]const u8); +} -- 2.54.0 From 78d8931647f207965ef98354db67fb880987fafe Mon Sep 17 00:00:00 2001 From: arbrk1 Date: Thu, 25 Jun 2020 06:58:50 +0300 Subject: [PATCH 117/295] Fix issue #5618 (#5685) * fix issue #5618 * A test for the issue #5618 added. Also inserted a comma in the neighboring test to make it more zigfmt-friendly. --- src/ir.cpp | 1 + test/compile_errors.zig | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index 3e2b75822e6cd553439194547747a940f4cf9c97..20067b74d14fd2377a19d12e3e024144349aa379 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -15452,6 +15452,7 @@ static IrInstGen *ir_analyze_cast(IrAnalyze *ira, IrInst *source_instr, if (is_pointery_and_elem_is_not_pointery(actual_type)) { ZigType *dest_ptr_type = nullptr; if (wanted_type->id == ZigTypeIdPointer && + actual_type->id != ZigTypeIdOptional && wanted_type->data.pointer.child_type == ira->codegen->builtin_types.entry_c_void) { dest_ptr_type = wanted_type; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index e8b7e610ee70e6ce82201eb3369606d6fbcb9acb..79ecaee7c152c119f74f8d3508f796f6fd1e0101 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -7571,6 +7571,16 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ return -y; \\} , &[_][]const u8{ - "tmp.zig:3:12: error: negation of type 'u32'" + "tmp.zig:3:12: error: negation of type 'u32'", + }); + + cases.add("Issue #5618: coercion of ?*c_void to *c_void must fail.", + \\export fn foo() void { + \\ var u: ?*c_void = null; + \\ var v: *c_void = undefined; + \\ v = u; + \\} + , &[_][]const u8{ + "tmp.zig:4:9: error: expected type '*c_void', found '?*c_void'", }); } -- 2.54.0 From f50ed941746c7c5ca4643080fbd2228491df6f19 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Tue, 23 Jun 2020 17:36:28 -0700 Subject: [PATCH 118/295] Windows: Fix fs.Dir.openDir not handling STATUS_NOT_A_DIRECTORY Now correctly returns error.NotDir --- lib/std/fs.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 10422b9d546cd5a23a40c12603c62dc93e5ffddc..6cb7d478b21cb57732325475b9ff3dc4a0843d7e 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1099,6 +1099,7 @@ pub const Dir = struct { .OBJECT_NAME_INVALID => unreachable, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, + .NOT_A_DIRECTORY => return error.NotDir, .INVALID_PARAMETER => unreachable, else => return w.unexpectedStatus(rc), } -- 2.54.0 From dcdbb7006ca63a20c003a9928d1cb5e3d0d1cbdb Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Tue, 23 Jun 2020 17:41:41 -0700 Subject: [PATCH 119/295] Add tests for using directory operations on files --- lib/std/fs/test.zig | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 23a9c96ff5eeb72518696ba7467cc783259fcfef..b024f0f4f666feb9e834455b943bc21968698719 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -40,6 +40,39 @@ test "readAllAlloc" { testing.expectError(error.FileTooBig, file.readAllAlloc(testing.allocator, file_size, write_buf.len - 1)); } +test "directory operations on files" { + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + const test_file_name = "test_file"; + + var file = try tmp_dir.dir.createFile(test_file_name, .{ .read = true }); + file.close(); + + testing.expectError(error.PathAlreadyExists, tmp_dir.dir.makeDir(test_file_name)); + testing.expectError(error.NotDir, tmp_dir.dir.openDir(test_file_name, .{})); + testing.expectError(error.NotDir, tmp_dir.dir.deleteDir(test_file_name)); + + if (builtin.os.tag != .wasi) { + // TODO: use Dir's realpath function once that exists + const absolute_path = blk: { + const relative_path = try fs.path.join(testing.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..], test_file_name }); + defer testing.allocator.free(relative_path); + break :blk try fs.realpathAlloc(testing.allocator, relative_path); + }; + defer testing.allocator.free(absolute_path); + + testing.expectError(error.PathAlreadyExists, fs.makeDirAbsolute(absolute_path)); + testing.expectError(error.NotDir, fs.deleteDirAbsolute(absolute_path)); + } + + // ensure the file still exists and is a file as a sanity check + file = try tmp_dir.dir.openFile(test_file_name, .{}); + const stat = try file.stat(); + testing.expect(stat.kind == .File); + file.close(); +} + test "openSelfExe" { if (builtin.os.tag == .wasi) return error.SkipZigTest; -- 2.54.0 From 77bb2dc094bfe9fff9208a8af94d0da617bdae13 Mon Sep 17 00:00:00 2001 From: data-man Date: Thu, 25 Jun 2020 21:05:52 +0500 Subject: [PATCH 120/295] Use writer in benchmarks --- lib/std/crypto/benchmark.zig | 18 +++--------------- lib/std/hash/benchmark.zig | 6 +++--- test/standalone/guess_number/main.zig | 2 +- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 8f961f80f27b8eed0daccd59c561d37b2ee06723..8c5d75f80a426b7d9d697dc2cfa802ba596b8b24 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -123,15 +123,6 @@ fn mode(comptime x: comptime_int) comptime_int { return if (builtin.mode == .Debug) x / 64 else x; } -// TODO(#1358): Replace with builtin formatted padding when available. -fn printPad(stdout: var, s: []const u8) !void { - var i: usize = 0; - while (i < 12 - s.len) : (i += 1) { - try stdout.print(" ", .{}); - } - try stdout.print("{}", .{s}); -} - pub fn main() !void { const stdout = std.io.getStdOut().outStream(); @@ -175,24 +166,21 @@ pub fn main() !void { inline for (hashes) |H| { if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) { const throughput = try benchmarkHash(H.ty, mode(32 * MiB)); - try printPad(stdout, H.name); - try stdout.print(": {} MiB/s\n", .{throughput / (1 * MiB)}); + try stdout.print("{:>11}: {:5} MiB/s\n", .{H.name, throughput / (1 * MiB)}); } } inline for (macs) |M| { if (filter == null or std.mem.indexOf(u8, M.name, filter.?) != null) { const throughput = try benchmarkMac(M.ty, mode(128 * MiB)); - try printPad(stdout, M.name); - try stdout.print(": {} MiB/s\n", .{throughput / (1 * MiB)}); + try stdout.print("{:>11}: {:5} MiB/s\n", .{M.name, throughput / (1 * MiB)}); } } inline for (exchanges) |E| { if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) { const throughput = try benchmarkKeyExchange(E.ty, mode(1000)); - try printPad(stdout, E.name); - try stdout.print(": {} exchanges/s\n", .{throughput}); + try stdout.print("{:>11}: {:5} exchanges/s\n", .{E.name, throughput}); } } } diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index 255c98e409014a1de192f122f3e6ce5a70639c86..0eb3a25fe1bc8c9fcd1265c4a01496da2e1e9f13 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -172,7 +172,7 @@ fn mode(comptime x: comptime_int) comptime_int { } pub fn main() !void { - const stdout = std.io.getStdOut().outStream(); + const stdout = std.io.getStdOut().writer(); var buffer: [1024]u8 = undefined; var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); @@ -248,13 +248,13 @@ pub fn main() !void { if (H.has_iterative_api) { prng.seed(seed); const result = try benchmarkHash(H, count); - try stdout.print(" iterative: {:4} MiB/s [{x:0<16}]\n", .{ result.throughput / (1 * MiB), result.hash }); + try stdout.print(" iterative: {:5} MiB/s [{x:0<16}]\n", .{ result.throughput / (1 * MiB), result.hash }); } if (!test_iterative_only) { prng.seed(seed); const result_small = try benchmarkHashSmallKeys(H, key_size, count); - try stdout.print(" small keys: {:4} MiB/s [{x:0<16}]\n", .{ result_small.throughput / (1 * MiB), result_small.hash }); + try stdout.print(" small keys: {:5} MiB/s [{x:0<16}]\n", .{ result_small.throughput / (1 * MiB), result_small.hash }); } } } diff --git a/test/standalone/guess_number/main.zig b/test/standalone/guess_number/main.zig index 5a82ec81ee200719a503534ddea62c5be91d3f65..87f9f9a1e7245988b39bb9e9937d6aa00cdf0f99 100644 --- a/test/standalone/guess_number/main.zig +++ b/test/standalone/guess_number/main.zig @@ -4,7 +4,7 @@ const io = std.io; const fmt = std.fmt; pub fn main() !void { - const stdout = io.getStdOut().outStream(); + const stdout = io.getStdOut().writer(); const stdin = io.getStdIn(); try stdout.print("Welcome to the Guess Number Game in Zig.\n", .{}); -- 2.54.0 From 130c7fd23b9f23aa5afad2eb1744ffab9e193715 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Jun 2020 02:25:09 -0400 Subject: [PATCH 121/295] self-hosted: working towards conditional branching test case New features: * Functions can have parameters in semantic analysis. Codegen is not implemented yet. * Support for i8, i16, i32, i64, u8, u16, u32, u64 primitive identifiers. * New ZIR instructions: arg, block, and breakvoid Implementation details: * Move Module.Body to ir.Body * Scope.Block gains a parent field and an optional Label field * Fix bug in integer type equality comparison. Here's the test case I'm working towards: ``` @void = primitive(void) @i32 = primitive(i32) @fnty = fntype([@i32, @i32], @void) @0 = str("entry") @1 = export(@0, "entry") @entry = fn(@fnty, { %0 = arg(0) %1 = arg(1) %2 = add(%0, %1) %3 = int(7) %4 = block("if", { %neq = cmp(%2, neq, %3) %5 = condbr(%neq, { %6 = unreachable() }, { %7 = breakvoid("if") }) }) %11 = returnvoid() }) ``` $ ./zig-cache/bin/zig build-obj test.zir test.zir:9:12: error: TODO implement function parameters for Arch.x86_64 That's where I left off. --- src-self-hosted/Module.zig | 252 +++++++++++++++++++++----- src-self-hosted/codegen.zig | 22 +++ src-self-hosted/ir.zig | 65 ++++++- src-self-hosted/type.zig | 340 ++++++++++++++++++++++++++++++++++-- src-self-hosted/value.zig | 102 +++++++++-- src-self-hosted/zir.zig | 158 +++++++++++++++-- 6 files changed, 842 insertions(+), 97 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 89dcac3f4186559319e5aa8547def3a4bde0cf72..c0c52d2ce6dba17652623d5068c0204a77864bd6 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -15,6 +15,7 @@ const ir = @import("ir.zig"); const zir = @import("zir.zig"); const Module = @This(); const Inst = ir.Inst; +const Body = ir.Body; const ast = std.zig.ast; const trace = @import("tracy.zig").trace; @@ -649,11 +650,19 @@ pub const Scope = struct { pub const Block = struct { pub const base_tag: Tag = .block; base: Scope = Scope{ .tag = base_tag }, + parent: ?*Block, func: ?*Fn, decl: *Decl, instructions: ArrayListUnmanaged(*Inst), /// Points to the arena allocator of DeclAnalysis arena: *Allocator, + label: ?Label = null, + + pub const Label = struct { + name: []const u8, + results: ArrayListUnmanaged(*Inst), + block_inst: *Inst.Block, + }; }; /// This is a temporary structure, references to it are valid only @@ -676,10 +685,6 @@ pub const Scope = struct { }; }; -pub const Body = struct { - instructions: []*Inst, -}; - pub const AllErrors = struct { arena: std.heap.ArenaAllocator.State, list: []const Message, @@ -1139,13 +1144,16 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const body_node = fn_proto.body_node orelse return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); - if (fn_proto.params_len != 0) { - return self.failTok( - &fn_type_scope.base, - fn_proto.params()[0].name_token.?, - "TODO implement function parameters", - .{}, - ); + + const param_decls = fn_proto.params(); + const param_types = try fn_type_scope.arena.allocator.alloc(*zir.Inst, param_decls.len); + for (param_decls) |param_decl, i| { + const param_type_node = switch (param_decl.param_type) { + .var_type => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement anytype parameter", .{}), + .var_args => |tok| return self.failTok(&fn_type_scope.base, tok, "TODO implement var args", .{}), + .type_expr => |node| node, + }; + param_types[i] = try self.astGenExpr(&fn_type_scope.base, param_type_node); } if (fn_proto.lib_name) |lib_name| { return self.failNode(&fn_type_scope.base, lib_name, "TODO implement function library name", .{}); @@ -1174,7 +1182,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const fn_src = tree.token_locs[fn_proto.fn_token].start; const fn_type_inst = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.FnType, .{ .return_type = return_type_inst, - .param_types = &[0]*zir.Inst{}, + .param_types = param_types, }, .{}); _ = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.Return, .{ .operand = fn_type_inst }, .{}); @@ -1184,6 +1192,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); var block_scope: Scope.Block = .{ + .parent = null, .func = null, .decl = decl, .instructions = .{}, @@ -1302,10 +1311,25 @@ fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir .Call => return self.astGenCall(scope, @fieldParentPtr(ast.Node.Call, "base", ast_node)), .Unreachable => return self.astGenUnreachable(scope, @fieldParentPtr(ast.Node.Unreachable, "base", ast_node)), .ControlFlowExpression => return self.astGenControlFlowExpression(scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)), + .If => return self.astGenIf(scope, @fieldParentPtr(ast.Node.If, "base", ast_node)), else => return self.failNode(scope, ast_node, "TODO implement astGenExpr for {}", .{@tagName(ast_node.id)}), } } +fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.Inst { + if (if_node.payload) |payload| { + return self.failNode(scope, payload, "TODO implement astGenIf for optionals", .{}); + } + if (if_node.@"else") |else_node| { + if (else_node.payload) |payload| { + return self.failNode(scope, payload, "TODO implement astGenIf for error unions", .{}); + } + } + const cond = try self.astGenExpr(scope, if_node.condition); + const body = try self.astGenExpr(scope, if_node.condition); + return self.failNode(scope, if_node.condition, "TODO implement astGenIf", .{}); +} + fn astGenControlFlowExpression( self: *Module, scope: *Scope, @@ -1351,7 +1375,18 @@ fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerE ), error.InvalidCharacter => break :integer, }; - return self.failNode(scope, &ident.base, "TODO implement arbitrary integer bitwidth types", .{}); + const val = switch (bit_count) { + 8 => if (is_signed) Value.initTag(.i8_type) else Value.initTag(.u8_type), + 16 => if (is_signed) Value.initTag(.i16_type) else Value.initTag(.u16_type), + 32 => if (is_signed) Value.initTag(.i32_type) else Value.initTag(.u32_type), + 64 => if (is_signed) Value.initTag(.i64_type) else Value.initTag(.u64_type), + else => return self.failNode(scope, &ident.base, "TODO implement arbitrary integer bitwidth types", .{}), + }; + const src = tree.token_locs[ident.token].start; + return self.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.type), + .val = val, + }); } } @@ -1494,16 +1529,18 @@ fn astGenBuiltinCall(self: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) fn astGenCall(self: *Module, scope: *Scope, call: *ast.Node.Call) InnerError!*zir.Inst { const tree = scope.tree(); - - if (call.params_len != 0) { - return self.failNode(scope, &call.base, "TODO implement fn calls with parameters", .{}); - } const lhs = try self.astGenExpr(scope, call.lhs); + const param_nodes = call.params(); + const args = try scope.cast(Scope.GenZIR).?.arena.allocator.alloc(*zir.Inst, param_nodes.len); + for (param_nodes) |param_node, i| { + args[i] = try self.astGenExpr(scope, param_node); + } + const src = tree.token_locs[call.lhs.firstToken()].start; return self.addZIRInst(scope, src, zir.Inst.Call, .{ .func = lhs, - .args = &[0]*zir.Inst{}, + .args = args, }, .{}); } @@ -1871,6 +1908,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); defer decl.typed_value.most_recent.arena.?.* = arena.state; var inner_block: Scope.Block = .{ + .parent = null, .func = func, .decl = decl, .instructions = .{}, @@ -2323,7 +2361,10 @@ fn analyzeInstConst(self: *Module, scope: *Scope, const_inst: *zir.Inst.Const) I fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { switch (old_inst.tag) { + .arg => return self.analyzeInstArg(scope, old_inst.cast(zir.Inst.Arg).?), + .block => return self.analyzeInstBlock(scope, old_inst.cast(zir.Inst.Block).?), .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.cast(zir.Inst.Breakpoint).?), + .breakvoid => return self.analyzeInstBreakVoid(scope, old_inst.cast(zir.Inst.BreakVoid).?), .call => return self.analyzeInstCall(scope, old_inst.cast(zir.Inst.Call).?), .compileerror => return self.analyzeInstCompileError(scope, old_inst.cast(zir.Inst.CompileError).?), .@"const" => return self.analyzeInstConst(scope, old_inst.cast(zir.Inst.Const).?), @@ -2436,11 +2477,105 @@ fn analyzeInstCompileError(self: *Module, scope: *Scope, inst: *zir.Inst.Compile return self.fail(scope, inst.base.src, "{}", .{inst.positionals.msg}); } +fn analyzeInstArg(self: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst { + const b = try self.requireRuntimeBlock(scope, inst.base.src); + const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; + const param_count = fn_ty.fnParamLen(); + if (inst.positionals.index >= param_count) { + return self.fail(scope, inst.base.src, "parameter index {} outside list of length {}", .{ + inst.positionals.index, + param_count, + }); + } + const param_type = fn_ty.fnParamType(inst.positionals.index); + return self.addNewInstArgs(b, inst.base.src, param_type, Inst.Arg, .{ + .index = inst.positionals.index, + }); +} + +fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst { + const parent_block = scope.cast(Scope.Block).?; + + // Reserve space for a Block instruction so that generated Break instructions can + // point to it, even if it doesn't end up getting used because the code ends up being + // comptime evaluated. + const block_inst = try parent_block.arena.create(Inst.Block); + block_inst.* = .{ + .base = .{ + .tag = Inst.Block.base_tag, + .ty = undefined, // Set after analysis. + .src = inst.base.src, + }, + .args = undefined, + }; + + var child_block: Scope.Block = .{ + .parent = parent_block, + .func = parent_block.func, + .decl = parent_block.decl, + .instructions = .{}, + .arena = parent_block.arena, + // TODO @as here is working around a miscompilation compiler bug :( + .label = @as(?Scope.Block.Label, Scope.Block.Label{ + .name = inst.positionals.label, + .results = .{}, + .block_inst = block_inst, + }), + }; + const label = &child_block.label.?; + + defer child_block.instructions.deinit(self.allocator); + defer label.results.deinit(self.allocator); + + try self.analyzeBody(&child_block.base, inst.positionals.body); + + // Blocks must terminate with noreturn instruction. + assert(child_block.instructions.items.len != 0); + assert(child_block.instructions.items[child_block.instructions.items.len - 1].tag.isNoReturn()); + + if (label.results.items.len <= 1) { + // No need to add the Block instruction; we can add the instructions to the parent block directly. + // Blocks are terminated with a noreturn instruction which we do not want to include. + const instrs = child_block.instructions.items; + try parent_block.instructions.appendSlice(self.allocator, instrs[0 .. instrs.len - 1]); + if (label.results.items.len == 1) { + return label.results.items[0]; + } else { + return self.constNoReturn(scope, inst.base.src); + } + } + + // Need to set the type and emit the Block instruction. This allows machine code generation + // to emit a jump instruction to after the block when it encounters the break. + try parent_block.instructions.append(self.allocator, &block_inst.base); + block_inst.base.ty = try self.resolvePeerTypes(scope, label.results.items); + block_inst.args.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; + return &block_inst.base; +} + fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.Breakpoint) InnerError!*Inst { const b = try self.requireRuntimeBlock(scope, inst.base.src); return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, {}); } +fn analyzeInstBreakVoid(self: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst { + const label_name = inst.positionals.label; + const void_inst = try self.constVoid(scope, inst.base.src); + + var opt_block = scope.cast(Scope.Block); + while (opt_block) |block| { + if (block.label) |*label| { + if (mem.eql(u8, label.name, label_name)) { + try label.results.append(self.allocator, void_inst); + return self.constNoReturn(scope, inst.base.src); + } + } + opt_block = block.parent; + } else { + return self.fail(scope, inst.base.src, "use of undeclared label '{}'", .{label_name}); + } +} + fn analyzeInstDeclRefStr(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { const decl_name = try self.resolveConstString(scope, inst.positionals.name); return self.analyzeDeclRefByName(scope, inst.base.src, decl_name); @@ -2602,35 +2737,38 @@ fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { const return_type = try self.resolveType(scope, fntype.positionals.return_type); - if (return_type.zigTypeTag() == .NoReturn and - fntype.positionals.param_types.len == 0 and - fntype.kw_args.cc == .Unspecified) - { - return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args)); - } + // Hot path for some common function types. + if (fntype.positionals.param_types.len == 0) { + if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Unspecified) { + return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args)); + } - if (return_type.zigTypeTag() == .Void and - fntype.positionals.param_types.len == 0 and - fntype.kw_args.cc == .Unspecified) - { - return self.constType(scope, fntype.base.src, Type.initTag(.fn_void_no_args)); - } + if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .Unspecified) { + return self.constType(scope, fntype.base.src, Type.initTag(.fn_void_no_args)); + } + + if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Naked) { + return self.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args)); + } - if (return_type.zigTypeTag() == .NoReturn and - fntype.positionals.param_types.len == 0 and - fntype.kw_args.cc == .Naked) - { - return self.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args)); + if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .C) { + return self.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args)); + } } - if (return_type.zigTypeTag() == .Void and - fntype.positionals.param_types.len == 0 and - fntype.kw_args.cc == .C) - { - return self.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args)); + const arena = scope.arena(); + const param_types = try arena.alloc(Type, fntype.positionals.param_types.len); + for (fntype.positionals.param_types) |param_type, i| { + param_types[i] = try self.resolveType(scope, param_type); } - return self.fail(scope, fntype.base.src, "TODO implement fntype instruction more", .{}); + const payload = try arena.create(Type.Payload.Function); + payload.* = .{ + .cc = fntype.kw_args.cc, + .return_type = return_type, + .param_types = param_types, + }; + return self.constType(scope, fntype.base.src, Type.initPayload(&payload.base)); } fn analyzeInstPrimitive(self: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst { @@ -2757,10 +2895,17 @@ fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inn } fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + const lhs = try self.resolveInst(scope, inst.positionals.lhs); const rhs = try self.resolveInst(scope, inst.positionals.rhs); if (lhs.ty.zigTypeTag() == .Int and rhs.ty.zigTypeTag() == .Int) { + if (!lhs.ty.eql(rhs.ty)) { + return self.fail(scope, inst.base.src, "TODO implement peer type resolution", .{}); + } + if (lhs.value()) |lhs_val| { if (rhs.value()) |rhs_val| { // TODO is this a performance issue? maybe we should try the operation without @@ -2777,10 +2922,6 @@ fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError! result_bigint.add(lhs_bigint, rhs_bigint); const result_limbs = result_bigint.limbs[0..result_bigint.len]; - if (!lhs.ty.eql(rhs.ty)) { - return self.fail(scope, inst.base.src, "TODO implement peer type resolution", .{}); - } - const val_payload = if (result_bigint.positive) blk: { const val_payload = try scope.arena().create(Value.Payload.IntBigPositive); val_payload.* = .{ .limbs = result_limbs }; @@ -2797,6 +2938,12 @@ fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError! }); } } + + const b = try self.requireRuntimeBlock(scope, inst.base.src); + return self.addNewInstArgs(b, inst.base.src, lhs.ty, Inst.Add, .{ + .lhs = lhs, + .rhs = rhs, + }); } return self.fail(scope, inst.base.src, "TODO implement more analyze add", .{}); @@ -2936,6 +3083,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner const parent_block = try self.requireRuntimeBlock(scope, inst.base.src); var true_block: Scope.Block = .{ + .parent = parent_block, .func = parent_block.func, .decl = parent_block.decl, .instructions = .{}, @@ -2945,6 +3093,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner try self.analyzeBody(&true_block.base, inst.positionals.true_body); var false_block: Scope.Block = .{ + .parent = parent_block, .func = parent_block.func, .decl = parent_block.decl, .instructions = .{}, @@ -3178,7 +3327,7 @@ fn cmpNumeric( const casted_lhs = try self.coerce(scope, dest_type, lhs); const casted_rhs = try self.coerce(scope, dest_type, lhs); - return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, .{ + return self.addNewInstArgs(b, src, Type.initTag(.bool), Inst.Cmp, .{ .lhs = casted_lhs, .rhs = casted_rhs, .op = op, @@ -3197,6 +3346,12 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { } } +fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { + if (instructions.len == 0) + return Type.initTag(.noreturn); + return self.fail(scope, instructions[0].src, "TODO peer type resolution", .{}); +} + fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { // If the types are the same, we can return the operand. if (dest_type.eql(inst.ty)) @@ -3238,7 +3393,10 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { if (inst.value()) |val| { return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); } else { - return self.fail(scope, inst.src, "TODO implement runtime integer widening", .{}); + return self.fail(scope, inst.src, "TODO implement runtime integer widening ({} to {})", .{ + inst.ty, + dest_type, + }); } } else { return self.fail(scope, inst.src, "TODO implement more int widening {} to {}", .{ inst.ty, dest_type }); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 7473ccc43156a333646392cf03319c65054f016a..686edda373e6ea310ef72fb1763842453f168e46 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -174,6 +174,9 @@ const Function = struct { fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { switch (inst.tag) { + .add => return self.genAdd(inst.cast(ir.Inst.Add).?), + .arg => return self.genArg(inst.src), + .block => return self.genBlock(inst.cast(ir.Inst.Block).?), .breakpoint => return self.genBreakpoint(inst.src), .call => return self.genCall(inst.cast(ir.Inst.Call).?), .unreach => return MCValue{ .unreach = {} }, @@ -190,6 +193,19 @@ const Function = struct { } } + fn genAdd(self: *Function, inst: *ir.Inst.Add) !MCValue { + switch (self.target.cpu.arch) { + else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), + } + } + + fn genArg(self: *Function, src: usize) !MCValue { + switch (self.target.cpu.arch) { + else => return self.fail(src, "TODO implement function parameters for {}", .{self.target.cpu.arch}), + } + return .none; + } + fn genBreakpoint(self: *Function, src: usize) !MCValue { switch (self.target.cpu.arch) { .i386, .x86_64 => { @@ -302,6 +318,12 @@ const Function = struct { } } + fn genBlock(self: *Function, inst: *ir.Inst.Block) !MCValue { + switch (self.target.cpu.arch) { + else => return self.fail(inst.base.src, "TODO implement codegen Block for {}", .{self.target.cpu.arch}), + } + } + fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue { // TODO convert to inline function switch (self.target.cpu.arch) { diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 387c88df3b5300814e5588a145873b03737d035b..cb9c7b5e11c2d9772ddba0242cfa638cfbdd0f5a 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -15,8 +15,11 @@ pub const Inst = struct { src: usize, pub const Tag = enum { + add, + arg, assembly, bitcast, + block, breakpoint, call, cmp, @@ -28,6 +31,33 @@ pub const Inst = struct { ret, retvoid, unreach, + + /// Returns whether the instruction is one of the control flow "noreturn" types. + /// Function calls do not count. When ZIR is generated, the compiler automatically + /// emits an `Unreach` after a function call with the `noreturn` return type. + pub fn isNoReturn(tag: Tag) bool { + return switch (tag) { + .add, + .arg, + .assembly, + .bitcast, + .block, + .breakpoint, + .cmp, + .constant, + .isnonnull, + .isnull, + .ptrtoint, + .call, + => false, + + .condbr, + .ret, + .retvoid, + .unreach, + => true, + }; + } }; pub fn cast(base: *Inst, comptime T: type) ?*T { @@ -50,6 +80,25 @@ pub const Inst = struct { return inst.val; } + pub const Add = struct { + pub const base_tag = Tag.add; + base: Inst, + + args: struct { + lhs: *Inst, + rhs: *Inst, + }, + }; + + pub const Arg = struct { + pub const base_tag = Tag.arg; + base: Inst, + + args: struct { + index: usize, + }, + }; + pub const Assembly = struct { pub const base_tag = Tag.assembly; base: Inst, @@ -73,6 +122,14 @@ pub const Inst = struct { }, }; + pub const Block = struct { + pub const base_tag = Tag.block; + base: Inst, + args: struct { + body: Body, + }, + }; + pub const Breakpoint = struct { pub const base_tag = Tag.breakpoint; base: Inst, @@ -105,8 +162,8 @@ pub const Inst = struct { base: Inst, args: struct { condition: *Inst, - true_body: Module.Body, - false_body: Module.Body, + true_body: Body, + false_body: Body, }, }; @@ -164,3 +221,7 @@ pub const Inst = struct { args: void, }; }; + +pub const Body = struct { + instructions: []*Inst, +}; diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index fb9718664863e1a6054205f916c9a445cc73344d..2a02d0d89bf1f975f8b7dfddf838887a965d6159 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -21,8 +21,14 @@ pub const Type = extern union { switch (self.tag()) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -57,6 +63,7 @@ pub const Type = extern union { .fn_void_no_args => return .Fn, .fn_naked_noreturn_no_args => return .Fn, .fn_ccc_void_no_args => return .Fn, + .function => return .Fn, .array, .array_u8_sentinel_0 => return .Array, .single_const_pointer => return .Pointer, @@ -126,10 +133,14 @@ pub const Type = extern union { @panic("TODO implement more pointer Type equality comparison"); }, .Int => { - if (a.tag() != b.tag()) { - // Detect that e.g. u64 != usize, even if the bits match on a particular target. + // Detect that e.g. u64 != usize, even if the bits match on a particular target. + const a_is_named_int = a.isNamedInt(); + const b_is_named_int = b.isNamedInt(); + if (a_is_named_int != b_is_named_int) return false; - } + if (a_is_named_int) + return a.tag() == b.tag(); + // Remaining cases are arbitrary sized integers. // The target will not be branched upon, because we handled target-dependent cases above. const info_a = a.intInfo(@as(Target, undefined)); const info_b = b.intInfo(@as(Target, undefined)); @@ -176,8 +187,14 @@ pub const Type = extern union { } else switch (self.ptr_otherwise.tag) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -231,6 +248,21 @@ pub const Type = extern union { }, .int_signed => return self.copyPayloadShallow(allocator, Payload.IntSigned), .int_unsigned => return self.copyPayloadShallow(allocator, Payload.IntUnsigned), + .function => { + const payload = @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise); + const new_payload = try allocator.create(Payload.Function); + const param_types = try allocator.alloc(Type, payload.param_types.len); + for (payload.param_types) |param_type, i| { + param_types[i] = try param_type.copy(allocator); + } + new_payload.* = .{ + .base = payload.base, + .return_type = try payload.return_type.copy(allocator), + .param_types = param_types, + .cc = payload.cc, + }; + return Type{ .ptr_otherwise = &new_payload.base }; + }, } } @@ -246,7 +278,7 @@ pub const Type = extern union { comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: var, - ) !void { + ) @TypeOf(out_stream).Error!void { comptime assert(fmt.len == 0); var ty = self; while (true) { @@ -254,8 +286,14 @@ pub const Type = extern union { switch (t) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -288,6 +326,16 @@ pub const Type = extern union { .fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"), .fn_ccc_void_no_args => return out_stream.writeAll("fn() callconv(.C) void"), .single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"), + .function => { + const payload = @fieldParentPtr(Payload.Function, "base", ty.ptr_otherwise); + try out_stream.writeAll("fn("); + for (payload.param_types) |param_type, i| { + if (i != 0) try out_stream.writeAll(", "); + try param_type.format("", .{}, out_stream); + } + try out_stream.writeAll(") "); + try payload.return_type.format("", .{}, out_stream); + }, .array_u8_sentinel_0 => { const payload = @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", ty.ptr_otherwise); @@ -322,8 +370,14 @@ pub const Type = extern union { switch (self.tag()) { .u8 => return Value.initTag(.u8_type), .i8 => return Value.initTag(.i8_type), - .isize => return Value.initTag(.isize_type), + .u16 => return Value.initTag(.u16_type), + .i16 => return Value.initTag(.i16_type), + .u32 => return Value.initTag(.u32_type), + .i32 => return Value.initTag(.i32_type), + .u64 => return Value.initTag(.u64_type), + .i64 => return Value.initTag(.i64_type), .usize => return Value.initTag(.usize_type), + .isize => return Value.initTag(.isize_type), .c_short => return Value.initTag(.c_short_type), .c_ushort => return Value.initTag(.c_ushort_type), .c_int => return Value.initTag(.c_int_type), @@ -365,8 +419,14 @@ pub const Type = extern union { return switch (self.tag()) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -386,6 +446,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .single_const_pointer_to_comptime_int, .const_slice_u8, .array_u8_sentinel_0, @@ -417,9 +478,14 @@ pub const Type = extern union { .fn_void_no_args, // represents machine code; not a pointer .fn_naked_noreturn_no_args, // represents machine code; not a pointer .fn_ccc_void_no_args, // represents machine code; not a pointer + .function, // represents machine code; not a pointer .array_u8_sentinel_0, => return 1, + .i16, .u16 => return 2, + .i32, .u32 => return 4, + .i64, .u64 => return 8, + .isize, .usize, .single_const_pointer_to_comptime_int, @@ -473,8 +539,14 @@ pub const Type = extern union { return switch (self.tag()) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -505,6 +577,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .int_unsigned, .int_signed, => false, @@ -519,8 +592,14 @@ pub const Type = extern union { return switch (self.tag()) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -552,6 +631,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .int_unsigned, .int_signed, => false, @@ -565,8 +645,14 @@ pub const Type = extern union { return switch (self.tag()) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -596,6 +682,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .int_unsigned, .int_signed, => unreachable, @@ -612,8 +699,14 @@ pub const Type = extern union { return switch (self.tag()) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -641,6 +734,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .int_unsigned, .int_signed, => unreachable, @@ -657,8 +751,14 @@ pub const Type = extern union { return switch (self.tag()) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -686,6 +786,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .single_const_pointer, .single_const_pointer_to_comptime_int, .const_slice_u8, @@ -703,8 +804,14 @@ pub const Type = extern union { return switch (self.tag()) { .u8, .i8, - .isize, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, + .isize, .c_short, .c_ushort, .c_int, @@ -732,6 +839,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .single_const_pointer, .single_const_pointer_to_comptime_int, .const_slice_u8, @@ -766,6 +874,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .array, .single_const_pointer, .single_const_pointer_to_comptime_int, @@ -778,6 +887,9 @@ pub const Type = extern union { .c_uint, .c_ulong, .c_ulonglong, + .u16, + .u32, + .u64, => false, .int_signed, @@ -787,11 +899,14 @@ pub const Type = extern union { .c_int, .c_long, .c_longlong, + .i16, + .i32, + .i64, => true, }; } - /// Asserts the type is a fixed-width integer. + /// Asserts the type is an integer. pub fn intInfo(self: Type, target: Target) struct { signed: bool, bits: u16 } { return switch (self.tag()) { .f16, @@ -813,6 +928,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .array, .single_const_pointer, .single_const_pointer_to_comptime_int, @@ -824,6 +940,12 @@ pub const Type = extern union { .int_signed => .{ .signed = true, .bits = self.cast(Payload.IntSigned).?.bits }, .u8 => .{ .signed = false, .bits = 8 }, .i8 => .{ .signed = true, .bits = 8 }, + .u16 => .{ .signed = false, .bits = 16 }, + .i16 => .{ .signed = true, .bits = 16 }, + .u32 => .{ .signed = false, .bits = 32 }, + .i32 => .{ .signed = true, .bits = 32 }, + .u64 => .{ .signed = false, .bits = 64 }, + .i64 => .{ .signed = true, .bits = 64 }, .usize => .{ .signed = false, .bits = target.cpu.arch.ptrBitWidth() }, .isize => .{ .signed = true, .bits = target.cpu.arch.ptrBitWidth() }, .c_short => .{ .signed = true, .bits = CType.short.sizeInBits(target) }, @@ -837,6 +959,59 @@ pub const Type = extern union { }; } + pub fn isNamedInt(self: Type) bool { + return switch (self.tag()) { + .f16, + .f32, + .f64, + .f128, + .c_longdouble, + .c_void, + .bool, + .void, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .noreturn, + .@"null", + .@"undefined", + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + .function, + .array, + .single_const_pointer, + .single_const_pointer_to_comptime_int, + .array_u8_sentinel_0, + .const_slice_u8, + .int_unsigned, + .int_signed, + .u8, + .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, + => false, + + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + => true, + }; + } + pub fn isFloat(self: Type) bool { return switch (self.tag()) { .f16, @@ -870,6 +1045,7 @@ pub const Type = extern union { .fn_void_no_args => 0, .fn_naked_noreturn_no_args => 0, .fn_ccc_void_no_args => 0, + .function => @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise).param_types.len, .f16, .f32, @@ -893,6 +1069,12 @@ pub const Type = extern union { .const_slice_u8, .u8, .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, .isize, .c_short, @@ -917,6 +1099,10 @@ pub const Type = extern union { .fn_void_no_args => return, .fn_naked_noreturn_no_args => return, .fn_ccc_void_no_args => return, + .function => { + const payload = @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise); + std.mem.copy(Type, types, payload.param_types); + }, .f16, .f32, @@ -940,6 +1126,68 @@ pub const Type = extern union { .const_slice_u8, .u8, .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, + .usize, + .isize, + .c_short, + .c_ushort, + .c_int, + .c_uint, + .c_long, + .c_ulong, + .c_longlong, + .c_ulonglong, + .int_unsigned, + .int_signed, + => unreachable, + } + } + + /// Asserts the type is a function. + pub fn fnParamType(self: Type, index: usize) Type { + switch (self.tag()) { + .function => { + const payload = @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise); + return payload.param_types[index]; + }, + + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + .f16, + .f32, + .f64, + .f128, + .c_longdouble, + .c_void, + .bool, + .void, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .noreturn, + .@"null", + .@"undefined", + .array, + .single_const_pointer, + .single_const_pointer_to_comptime_int, + .array_u8_sentinel_0, + .const_slice_u8, + .u8, + .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, .isize, .c_short, @@ -966,6 +1214,8 @@ pub const Type = extern union { .fn_ccc_void_no_args, => Type.initTag(.void), + .function => @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise).return_type, + .f16, .f32, .f64, @@ -988,6 +1238,12 @@ pub const Type = extern union { .const_slice_u8, .u8, .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, .isize, .c_short, @@ -1011,6 +1267,7 @@ pub const Type = extern union { .fn_void_no_args => .Unspecified, .fn_naked_noreturn_no_args => .Naked, .fn_ccc_void_no_args => .C, + .function => @fieldParentPtr(Payload.Function, "base", self.ptr_otherwise).cc, .f16, .f32, @@ -1034,6 +1291,12 @@ pub const Type = extern union { .const_slice_u8, .u8, .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, .isize, .c_short, @@ -1057,6 +1320,7 @@ pub const Type = extern union { .fn_void_no_args => false, .fn_naked_noreturn_no_args => false, .fn_ccc_void_no_args => false, + .function => false, .f16, .f32, @@ -1080,6 +1344,12 @@ pub const Type = extern union { .const_slice_u8, .u8, .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, .isize, .c_short, @@ -1107,6 +1377,12 @@ pub const Type = extern union { .comptime_float, .u8, .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, .isize, .c_short, @@ -1133,6 +1409,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .array, .single_const_pointer, .single_const_pointer_to_comptime_int, @@ -1154,6 +1431,12 @@ pub const Type = extern union { .comptime_float, .u8, .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, .isize, .c_short, @@ -1171,6 +1454,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .single_const_pointer_to_comptime_int, .array_u8_sentinel_0, .const_slice_u8, @@ -1211,6 +1495,12 @@ pub const Type = extern union { .comptime_float, .u8, .i8, + .u16, + .i16, + .u32, + .i32, + .u64, + .i64, .usize, .isize, .c_short, @@ -1228,6 +1518,7 @@ pub const Type = extern union { .fn_void_no_args, .fn_naked_noreturn_no_args, .fn_ccc_void_no_args, + .function, .single_const_pointer_to_comptime_int, .array_u8_sentinel_0, .const_slice_u8, @@ -1254,8 +1545,14 @@ pub const Type = extern union { // The first section of this enum are tags that require no payload. u8, i8, - isize, + u16, + i16, + u32, + i32, + u64, + i64, usize, + isize, c_short, c_ushort, c_int, @@ -1292,6 +1589,7 @@ pub const Type = extern union { single_const_pointer, int_signed, int_unsigned, + function, pub const last_no_payload_tag = Tag.const_slice_u8; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -1330,6 +1628,14 @@ pub const Type = extern union { bits: u16, }; + + pub const Function = struct { + base: Payload = Payload{ .tag = .function }, + + param_types: []Type, + return_type: Type, + cc: std.builtin.CallingConvention, + }; }; }; diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index fc5854c40fb80060915c65acc60ac0bf787c68b7..6509ee52f6938b9225408fbf65d0c7dab790b0b6 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -23,8 +23,14 @@ pub const Value = extern union { // The first section of this enum are tags that require no payload. u8_type, i8_type, - isize_type, + u16_type, + i16_type, + u32_type, + i32_type, + u64_type, + i64_type, usize_type, + isize_type, c_short_type, c_ushort_type, c_int_type, @@ -114,8 +120,14 @@ pub const Value = extern union { } else switch (self.ptr_otherwise.tag) { .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, @@ -222,6 +234,12 @@ pub const Value = extern union { while (true) switch (val.tag()) { .u8_type => return out_stream.writeAll("u8"), .i8_type => return out_stream.writeAll("i8"), + .u16_type => return out_stream.writeAll("u16"), + .i16_type => return out_stream.writeAll("i16"), + .u32_type => return out_stream.writeAll("u32"), + .i32_type => return out_stream.writeAll("i32"), + .u64_type => return out_stream.writeAll("u64"), + .i64_type => return out_stream.writeAll("i64"), .isize_type => return out_stream.writeAll("isize"), .usize_type => return out_stream.writeAll("usize"), .c_short_type => return out_stream.writeAll("c_short"), @@ -308,8 +326,14 @@ pub const Value = extern union { .u8_type => Type.initTag(.u8), .i8_type => Type.initTag(.i8), - .isize_type => Type.initTag(.isize), + .u16_type => Type.initTag(.u16), + .i16_type => Type.initTag(.i16), + .u32_type => Type.initTag(.u32), + .i32_type => Type.initTag(.i32), + .u64_type => Type.initTag(.u64), + .i64_type => Type.initTag(.i64), .usize_type => Type.initTag(.usize), + .isize_type => Type.initTag(.isize), .c_short_type => Type.initTag(.c_short), .c_ushort_type => Type.initTag(.c_ushort), .c_int_type => Type.initTag(.c_int), @@ -366,8 +390,14 @@ pub const Value = extern union { .ty, .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, @@ -426,8 +456,14 @@ pub const Value = extern union { .ty, .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, @@ -487,8 +523,14 @@ pub const Value = extern union { .ty, .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, @@ -553,8 +595,14 @@ pub const Value = extern union { .ty, .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, @@ -648,8 +696,14 @@ pub const Value = extern union { .ty, .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, @@ -705,8 +759,14 @@ pub const Value = extern union { .ty, .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, @@ -807,8 +867,14 @@ pub const Value = extern union { .ty, .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, @@ -870,8 +936,14 @@ pub const Value = extern union { .ty, .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, @@ -950,8 +1022,14 @@ pub const Value = extern union { .ty, .u8_type, .i8_type, - .isize_type, + .u16_type, + .i16_type, + .u32_type, + .i32_type, + .u64_type, + .i64_type, .usize_type, + .isize_type, .c_short_type, .c_ushort_type, .c_int_type, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 43c0eac197c324e879fd767cf3a9ff3cd3d04f14..92dbc66e2b9f928f9dca1dcfee5e3c0a93645ff1 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -34,7 +34,13 @@ pub const Inst = struct { /// These names are used directly as the instruction names in the text format. pub const Tag = enum { + /// Function parameter value. + arg, + /// A labeled block of code, which can return a value. + block, breakpoint, + /// Same as `break` but without an operand; the operand is assumed to be the void value. + breakvoid, call, compileerror, /// Special case, has no textual representation. @@ -75,7 +81,10 @@ pub const Inst = struct { pub fn TagToType(tag: Tag) type { return switch (tag) { + .arg => Arg, + .block => Block, .breakpoint => Breakpoint, + .breakvoid => BreakVoid, .call => Call, .declref => DeclRef, .declref_str => DeclRefStr, @@ -115,6 +124,27 @@ pub const Inst = struct { return @fieldParentPtr(T, "base", base); } + pub const Arg = struct { + pub const base_tag = Tag.arg; + base: Inst, + + positionals: struct { + index: usize, + }, + kw_args: struct {}, + }; + + pub const Block = struct { + pub const base_tag = Tag.block; + base: Inst, + + positionals: struct { + label: []const u8, + body: Module.Body, + }, + kw_args: struct {}, + }; + pub const Breakpoint = struct { pub const base_tag = Tag.breakpoint; base: Inst, @@ -123,6 +153,16 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const BreakVoid = struct { + pub const base_tag = Tag.breakvoid; + base: Inst, + + positionals: struct { + label: []const u8, + }, + kw_args: struct {}, + }; + pub const Call = struct { pub const base_tag = Tag.call; base: Inst, @@ -347,6 +387,14 @@ pub const Inst = struct { kw_args: struct {}, pub const Builtin = enum { + i8, + u8, + i16, + u16, + i32, + u32, + i64, + u64, isize, usize, c_short, @@ -378,6 +426,14 @@ pub const Inst = struct { pub fn toTypedValue(self: Builtin) TypedValue { return switch (self) { + .i8 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i8_type) }, + .u8 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u8_type) }, + .i16 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i16_type) }, + .u16 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u16_type) }, + .i32 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i32_type) }, + .u32 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u32_type) }, + .i64 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.i64_type) }, + .u64 => .{ .ty = Type.initTag(.type), .val = Value.initTag(.u64_type) }, .isize => .{ .ty = Type.initTag(.type), .val = Value.initTag(.isize_type) }, .usize => .{ .ty = Type.initTag(.type), .val = Value.initTag(.usize_type) }, .c_short => .{ .ty = Type.initTag(.type), .val = Value.initTag(.c_short_type) }, @@ -591,7 +647,10 @@ pub const Module = struct { ) @TypeOf(stream).Error!void { // TODO I tried implementing this with an inline for loop and hit a compiler bug switch (inst.tag) { + .arg => return self.writeInstToStreamGeneric(stream, .arg, inst, inst_table), + .block => return self.writeInstToStreamGeneric(stream, .block, inst, inst_table), .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst, inst_table), + .breakvoid => return self.writeInstToStreamGeneric(stream, .breakvoid, inst, inst_table), .call => return self.writeInstToStreamGeneric(stream, .call, inst, inst_table), .declref => return self.writeInstToStreamGeneric(stream, .declref, inst, inst_table), .declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst, inst_table), @@ -691,7 +750,7 @@ pub const Module = struct { }, bool => return stream.writeByte("01"[@boolToInt(param)]), []u8, []const u8 => return std.zig.renderStringLiteral(param, stream), - BigIntConst => return stream.print("{}", .{param}), + BigIntConst, usize => return stream.print("{}", .{param}), TypedValue => unreachable, // this is a special case *IrModule.Decl => unreachable, // this is a special case else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), @@ -718,7 +777,7 @@ pub const Module = struct { }; pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module { - var global_name_map = std.StringHashMap(usize).init(allocator); + var global_name_map = std.StringHashMap(*Inst).init(allocator); defer global_name_map.deinit(); var parser: Parser = .{ @@ -752,22 +811,24 @@ const Parser = struct { i: usize, source: [:0]const u8, decls: std.ArrayListUnmanaged(*Decl), - global_name_map: *std.StringHashMap(usize), + global_name_map: *std.StringHashMap(*Inst), error_msg: ?ErrorMsg = null, unnamed_index: usize, const Body = struct { instructions: std.ArrayList(*Inst), - name_map: std.StringHashMap(usize), + name_map: *std.StringHashMap(*Inst), }; - fn parseBody(self: *Parser) !Module.Body { + fn parseBody(self: *Parser, body_ctx: ?*Body) !Module.Body { + var name_map = std.StringHashMap(*Inst).init(self.allocator); + defer name_map.deinit(); + var body_context = Body{ .instructions = std.ArrayList(*Inst).init(self.allocator), - .name_map = std.StringHashMap(usize).init(self.allocator), + .name_map = if (body_ctx) |bctx| bctx.name_map else &name_map, }; defer body_context.instructions.deinit(); - defer body_context.name_map.deinit(); try requireEatBytes(self, "{"); skipSpace(self); @@ -782,7 +843,7 @@ const Parser = struct { skipSpace(self); const decl = try parseInstruction(self, &body_context, ident); const ident_index = body_context.instructions.items.len; - if (try body_context.name_map.put(ident, ident_index)) |_| { + if (try body_context.name_map.put(ident, decl.inst)) |_| { return self.fail("redefinition of identifier '{}'", .{ident}); } try body_context.instructions.append(decl.inst); @@ -866,12 +927,12 @@ const Parser = struct { skipSpace(self); try requireEatBytes(self, "="); skipSpace(self); - const inst = try parseInstruction(self, null, ident); + const decl = try parseInstruction(self, null, ident); const ident_index = self.decls.items.len; - if (try self.global_name_map.put(ident, ident_index)) |_| { + if (try self.global_name_map.put(ident, decl.inst)) |_| { return self.fail("redefinition of identifier '{}'", .{ident}); } - try self.decls.append(self.allocator, inst); + try self.decls.append(self.allocator, decl); }, ' ', '\n' => self.i += 1, 0 => break, @@ -1032,7 +1093,7 @@ const Parser = struct { }; } switch (T) { - Module.Body => return parseBody(self), + Module.Body => return parseBody(self, body_ctx), bool => { const bool_value = switch (self.source[self.i]) { '0' => false, @@ -1060,6 +1121,10 @@ const Parser = struct { *Inst => return parseParameterInst(self, body_ctx), []u8, []const u8 => return self.parseStringLiteral(), BigIntConst => return self.parseIntegerLiteral(), + usize => { + const big_int = try self.parseIntegerLiteral(); + return big_int.to(usize) catch |err| return self.fail("integer literal: {}", .{@errorName(err)}); + }, TypedValue => return self.fail("'const' is a special instruction; not legal in ZIR text", .{}), *IrModule.Decl => return self.fail("'declval_in_module' is a special instruction; not legal in ZIR text", .{}), else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), @@ -1075,7 +1140,7 @@ const Parser = struct { }; const map = if (local_ref) if (body_ctx) |bc| - &bc.name_map + bc.name_map else return self.fail("referencing a % instruction in global scope", .{}) else @@ -1107,11 +1172,7 @@ const Parser = struct { return &declval.base; } }; - if (local_ref) { - return body_ctx.?.instructions.items[kv.value]; - } else { - return self.decls.items[kv.value].inst; - } + return kv.value; } fn generateName(self: *Parser) ![]u8 { @@ -1456,7 +1517,7 @@ const EmitZIR = struct { fn emitBody( self: *EmitZIR, - body: IrModule.Body, + body: ir.Body, inst_table: *std.AutoHashMap(*ir.Inst, *Inst), instructions: *std.ArrayList(*Inst), ) Allocator.Error!void { @@ -1466,6 +1527,57 @@ const EmitZIR = struct { }; for (body.instructions) |inst| { const new_inst = switch (inst.tag) { + .add => blk: { + const old_inst = inst.cast(ir.Inst.Add).?; + const new_inst = try self.arena.allocator.create(Inst.Add); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.Add.base_tag, + }, + .positionals = .{ + .lhs = try self.resolveInst(new_body, old_inst.args.lhs), + .rhs = try self.resolveInst(new_body, old_inst.args.rhs), + }, + .kw_args = .{}, + }; + break :blk &new_inst.base; + }, + .arg => blk: { + const old_inst = inst.cast(ir.Inst.Arg).?; + const new_inst = try self.arena.allocator.create(Inst.Arg); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.Arg.base_tag, + }, + .positionals = .{ .index = old_inst.args.index }, + .kw_args = .{}, + }; + break :blk &new_inst.base; + }, + .block => blk: { + const old_inst = inst.cast(ir.Inst.Block).?; + const new_inst = try self.arena.allocator.create(Inst.Block); + + var block_body = std.ArrayList(*Inst).init(self.allocator); + defer block_body.deinit(); + + try self.emitBody(old_inst.args.body, inst_table, &block_body); + + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.Block.base_tag, + }, + .positionals = .{ + .label = try self.autoName(), + .body = .{ .instructions = block_body.toOwnedSlice() }, + }, + .kw_args = .{}, + }; + break :blk &new_inst.base; + }, .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), .call => blk: { const old_inst = inst.cast(ir.Inst.Call).?; @@ -1660,6 +1772,14 @@ const EmitZIR = struct { fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Decl { switch (ty.tag()) { + .i8 => return self.emitPrimitive(src, .i8), + .u8 => return self.emitPrimitive(src, .u8), + .i16 => return self.emitPrimitive(src, .i16), + .u16 => return self.emitPrimitive(src, .u16), + .i32 => return self.emitPrimitive(src, .i32), + .u32 => return self.emitPrimitive(src, .u32), + .i64 => return self.emitPrimitive(src, .i64), + .u64 => return self.emitPrimitive(src, .u64), .isize => return self.emitPrimitive(src, .isize), .usize => return self.emitPrimitive(src, .usize), .c_short => return self.emitPrimitive(src, .c_short), -- 2.54.0 From 649da2df5234f504f2f22afe9e0b4cf7eeb9ef35 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 26 Jun 2020 02:42:02 -0400 Subject: [PATCH 122/295] Stage2/Testing: Add convenience wrappers --- src-self-hosted/test.zig | 74 ++++++++++++++++++++++++++++++++++ test/stage2/compare_output.zig | 2 +- test/stage2/compile_errors.zig | 6 +-- test/stage2/zir.zig | 10 ++--- 4 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 3f7ff3303a00a52c243dd9db5bc97ea4588c7b54..89532378a511f3262261f7a11a7803c2d198900a 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -138,6 +138,14 @@ pub const TestContext = struct { return &ctx.cases.items[ctx.cases.items.len - 1]; } + pub fn exe(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + return ctx.addExe(name, target, .Zig); + } + + pub fn exeZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + return ctx.addExe(name, target, .ZIR); + } + pub fn addObj( ctx: *TestContext, name: []const u8, @@ -154,6 +162,14 @@ pub const TestContext = struct { return &ctx.cases.items[ctx.cases.items.len - 1]; } + pub fn obj(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + return ctx.addObj(name, target, .Zig); + } + + pub fn objZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { + return ctx.addObj(name, target, .ZIR); + } + pub fn addCompareOutput( ctx: *TestContext, name: []const u8, @@ -164,6 +180,24 @@ pub const TestContext = struct { ctx.addExe(name, .{}, T).addCompareOutput(src, expected_stdout); } + pub fn compareOutput( + ctx: *TestContext, + name: []const u8, + src: [:0]const u8, + expected_stdout: []const u8, + ) void { + return ctx.addCompareOutput(name, .Zig, src, expected_stdout); + } + + pub fn compareOutputZIR( + ctx: *TestContext, + name: []const u8, + src: [:0]const u8, + expected_stdout: []const u8, + ) void { + ctx.addCompareOutput(name, .ZIR, src, expected_stdout); + } + pub fn addTransform( ctx: *TestContext, name: []const u8, @@ -175,6 +209,26 @@ pub const TestContext = struct { ctx.addObj(name, target, T).addTransform(src, result); } + pub fn transform( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + result: [:0]const u8, + ) void { + ctx.addTransform(name, target, .Zig, src, result); + } + + pub fn transformZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + result: [:0]const u8, + ) void { + ctx.addTransform(name, target, .ZIR, src, result); + } + pub fn addError( ctx: *TestContext, name: []const u8, @@ -186,6 +240,26 @@ pub const TestContext = struct { ctx.addObj(name, target, T).addError(src, expected_errors); } + pub fn compileError( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, + ) void { + ctx.addError(name, target, .Zig, src, expected_errors); + } + + pub fn compileErrorZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, + ) void { + ctx.addError(name, target, .ZIR, src, expected_errors); + } + fn init() TestContext { const allocator = std.heap.page_allocator; return .{ .cases = std.ArrayList(Case).init(allocator) }; diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 902c1e493f98980106af05b511192474dc523b5c..d49f16876ef0d1c34a9e44acb94d59bfb6cf2c6a 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -17,7 +17,7 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.addExe("hello world with updates", linux_x64, .Zig); + var case = ctx.exe("hello world with updates", linux_x64); // Regular old hello world case.addCompareOutput( \\export fn _start() noreturn { diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 1ee8e9184b5863884cf86f5e414c6edd955811c9..905f106f94fa1350d14bfb58934559b03a2e2fd3 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -9,7 +9,7 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - ctx.addError("call undefined local", linux_x64, .ZIR, + ctx.compileErrorZIR("call undefined local", linux_x64, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -19,7 +19,7 @@ pub fn addCases(ctx: *TestContext) !void { // TODO: address inconsistency in this message and the one in the next test , &[_][]const u8{":5:13: error: unrecognized identifier: %test"}); - ctx.addError("call with non-existent target", linux_x64, .ZIR, + ctx.compileErrorZIR("call with non-existent target", linux_x64, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) @@ -31,7 +31,7 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{":5:13: error: decl 'notafunc' not found"}); // TODO: this error should occur at the call site, not the fntype decl - ctx.addError("call naked function", linux_x64, .ZIR, + ctx.compileErrorZIR("call naked function", linux_x64, \\@noreturn = primitive(noreturn) \\ \\@start_fnty = fntype([], @noreturn, cc=Naked) diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index 17d5ce9b5b19b1aba4f893a7715a8608bbce8c8a..052ada667e7ca4906c2432f863de2bbcea806224 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -9,7 +9,7 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - ctx.addTransform("referencing decls which appear later in the file", linux_x64, .ZIR, + ctx.transformZIR("referencing decls which appear later in the file", linux_x64, \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) \\ @@ -32,7 +32,7 @@ pub fn addCases(ctx: *TestContext) !void { \\}) \\ ); - ctx.addTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, .ZIR, + ctx.transformZIR("elemptr, add, cmp, condbr, return, breakpoint", linux_x64, \\@void = primitive(void) \\@usize = primitive(usize) \\@fnty = fntype([], @void, cc=C) @@ -86,7 +86,7 @@ pub fn addCases(ctx: *TestContext) !void { ); { - var case = ctx.addObj("reference cycle with compile error in the cycle", linux_x64, .ZIR); + var case = ctx.objZIR("reference cycle with compile error in the cycle", linux_x64); case.addTransform( \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) @@ -207,7 +207,7 @@ pub fn addCases(ctx: *TestContext) !void { return; } - ctx.addCompareOutput("hello world ZIR", .ZIR, + ctx.compareOutputZIR("hello world ZIR", \\@noreturn = primitive(noreturn) \\@void = primitive(void) \\@usize = primitive(usize) @@ -265,7 +265,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); - ctx.addCompareOutput("function call with no args no return value", .ZIR, + ctx.compareOutputZIR("function call with no args no return value", \\@noreturn = primitive(noreturn) \\@void = primitive(void) \\@usize = primitive(usize) -- 2.54.0 From 0e3d74df8a8c06bcc0b1e4bff205f4ab8e4c6c13 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 26 Jun 2020 00:06:23 -0700 Subject: [PATCH 123/295] Add tests for using file operations on directories --- lib/std/fs/test.zig | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index b024f0f4f666feb9e834455b943bc21968698719..87477549d953159f399fcea170b9bd926f2e13f6 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -73,6 +73,38 @@ test "directory operations on files" { file.close(); } +test "file operations on directories" { + var tmp_dir = tmpDir(.{}); + defer tmp_dir.cleanup(); + + const test_dir_name = "test_dir"; + + try tmp_dir.dir.makeDir(test_dir_name); + + testing.expectError(error.IsDir, tmp_dir.dir.createFile(test_dir_name, .{})); + testing.expectError(error.IsDir, tmp_dir.dir.deleteFile(test_dir_name)); + testing.expectError(error.IsDir, tmp_dir.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize))); + // note: the `.write = true` is necessary to ensure the error occurs on all platforms + testing.expectError(error.IsDir, tmp_dir.dir.openFile(test_dir_name, .{ .write = true })); + + if (builtin.os.tag != .wasi) { + // TODO: use Dir's realpath function once that exists + const absolute_path = blk: { + const relative_path = try fs.path.join(testing.allocator, &[_][]const u8{ "zig-cache", "tmp", tmp_dir.sub_path[0..], test_dir_name }); + defer testing.allocator.free(relative_path); + break :blk try fs.realpathAlloc(testing.allocator, relative_path); + }; + defer testing.allocator.free(absolute_path); + + testing.expectError(error.IsDir, fs.createFileAbsolute(absolute_path, .{})); + testing.expectError(error.IsDir, fs.deleteFileAbsolute(absolute_path)); + } + + // ensure the directory still exists as a sanity check + var dir = try tmp_dir.dir.openDir(test_dir_name, .{}); + dir.close(); +} + test "openSelfExe" { if (builtin.os.tag == .wasi) return error.SkipZigTest; -- 2.54.0 From 53fead580eb2abc2813d70b794239c4629324159 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 26 Jun 2020 03:08:21 -0400 Subject: [PATCH 124/295] Add a `compiles` wrapper case --- src-self-hosted/test.zig | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 89532378a511f3262261f7a11a7803c2d198900a..13f08d86e262eedb963afa0cd2f901302a9f05ac 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -120,6 +120,10 @@ pub const TestContext = struct { } self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable; } + + pub fn compiles(self: *Case, src: [:0]const u8) void { + self.addError(src, &[_][]const u8{}); + } }; pub fn addExe( @@ -260,6 +264,34 @@ pub const TestContext = struct { ctx.addError(name, target, .ZIR, src, expected_errors); } + pub fn addCompiles( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + T: TestType, + src: [:0]const u8, + ) void { + ctx.addObj(name, target, T).compiles(src); + } + + pub fn compiles( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + ) void { + ctx.addCompiles(name, target, .Zig, src); + } + + pub fn compilesZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + ) void { + ctx.addCompiles(name, target, .ZIR, src); + } + fn init() TestContext { const allocator = std.heap.page_allocator; return .{ .cases = std.ArrayList(Case).init(allocator) }; -- 2.54.0 From e5a3cb8d7134d7c4e4db03a609c5b7b6de00edfc Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 26 Jun 2020 03:15:12 -0400 Subject: [PATCH 125/295] Stage2: fix incremental compilation after error --- src-self-hosted/Module.zig | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 89dcac3f4186559319e5aa8547def3a4bde0cf72..63cca38973b99ab647c81bb7e4368405c6731973 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -851,6 +851,8 @@ pub fn update(self: *Module) !void { self.generation += 1; + self.clearErrors(); + // TODO Use the cache hash file system to detect which source files changed. // Until then we simulate a full cache miss. Source files could have been loaded for any reason; // to force a refresh we unload now. @@ -914,6 +916,31 @@ pub fn totalErrorCount(self: *Module) usize { return if (total == 0) @boolToInt(self.link_error_flags.no_entry_point_found) else total; } +pub fn clearErrors(self: *Module) void { + const allocator = self.allocator; + { + var it = self.failed_decls.iterator(); + while (it.next()) |kv| { + kv.value.destroy(allocator); + } + self.failed_decls.clear(); + } + { + var it = self.failed_files.iterator(); + while (it.next()) |kv| { + kv.value.destroy(allocator); + } + self.failed_files.clear(); + } + { + var it = self.failed_exports.iterator(); + while (it.next()) |kv| { + kv.value.destroy(allocator); + } + self.failed_exports.clear(); + } +} + pub fn getAllErrorsAlloc(self: *Module) !AllErrors { var arena = std.heap.ArenaAllocator.init(self.allocator); errdefer arena.deinit(); @@ -2092,6 +2119,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const .Fn => {}, else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), } + try self.decl_exports.ensureCapacity(self.decl_exports.size + 1); try self.export_owners.ensureCapacity(self.export_owners.size + 1); -- 2.54.0 From 4a17e008daa90d5dbb0fc917d53e52c1ec990f2d Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 26 Jun 2020 03:17:13 -0400 Subject: [PATCH 126/295] Stage2: exported symbol collision detection --- src-self-hosted/Module.zig | 50 +++++++++++++++++++++++++--------- test/stage2/compile_errors.zig | 32 +++++++++++++++++++--- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 63cca38973b99ab647c81bb7e4368405c6731973..600da10d93743d4d813a29beb8099adafc0fb109 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2120,6 +2120,20 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), } + var already_exported = false; + { + var it = self.decl_exports.iterator(); + while (it.next()) |kv| { + const export_list = kv.value; + for (export_list) |e| { + if (std.mem.eql(u8, e.options.name, symbol_name)) { + already_exported = true; + break; + } + } + } + } + try self.decl_exports.ensureCapacity(self.decl_exports.size + 1); try self.export_owners.ensureCapacity(self.export_owners.size + 1); @@ -2155,19 +2169,29 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const de_gop.kv.value[de_gop.kv.value.len - 1] = new_export; errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1); - self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); - self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( - self.allocator, - src, - "unable to export: {}", - .{@errorName(err)}, - )); - new_export.status = .failed_retryable; - }, - }; + if (already_exported) { + try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); + self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( + self.allocator, + src, + "exported symbol collision: {}", + .{symbol_name}, + )); + } else { + self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); + self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( + self.allocator, + src, + "unable to export: {}", + .{@errorName(err)}, + )); + new_export.status = .failed_retryable; + }, + }; + } } fn addNewInstArgs( diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 905f106f94fa1350d14bfb58934559b03a2e2fd3..79b67da2c2a0ff17ad40140749646e328f4fb7ce 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -43,13 +43,37 @@ pub fn addCases(ctx: *TestContext) !void { \\@1 = export(@0, "start") , &[_][]const u8{":4:9: error: unable to call function with naked calling convention"}); + { + var case = ctx.objZIR("exported symbol collision", linux_x64); + // First, ensure we receive the error correctly + case.addError( + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn) + \\@start = fn(@start_fnty, {}) + \\ + \\@0 = str("_start") + \\@1 = export(@0, "start") + \\@2 = export(@0, "start") + , &[_][]const u8{":8:13: error: exported symbol collision: _start"}); + // Next, ensure everything works properly on the next compilation with the problem fixed + case.compiles( + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn) + \\@start = fn(@start_fnty, {}) + \\ + \\@0 = str("_start") + \\@1 = export(@0, "start") + ); + } // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 - // ctx.addError("Export same symbol twice", linux_x64, .Zig, - // \\export fn entry() void {} - // \\export fn entry() void {} - // , &[_][]const u8{":2:1: error: exported symbol collision"}); + // ctx.compileError("Export same symbol twice", linux_x64, + // \\export fn entry() void {} + // \\export fn entry() void {} + // , &[_][]const u8{":2:1: error: exported symbol collision"}); // ctx.addError("Missing function name", linux_x64, .Zig, // \\fn() void {} -- 2.54.0 From 6510888039baaa0058a7e2bade2750569af1abbd Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 26 Jun 2020 04:03:54 -0400 Subject: [PATCH 127/295] Stage2: function redefinition detection for Zig code --- src-self-hosted/Module.zig | 13 +++++++++---- test/stage2/compile_errors.zig | 24 +++++++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 600da10d93743d4d813a29beb8099adafc0fb109..134693b6fdfeadccc2871a562071d8dc132fe634 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1733,10 +1733,15 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { // Update the AST Node index of the decl, even if its contents are unchanged, it may // have been re-ordered. decl.src_index = decl_i; - deleted_decls.removeAssertDiscard(decl); - if (!srcHashEql(decl.contents_hash, contents_hash)) { - try self.markOutdatedDecl(decl); - decl.contents_hash = contents_hash; + if (deleted_decls.remove(decl) == null) { + const err_msg = try ErrorMsg.create(self.allocator, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); + errdefer err_msg.destroy(self.allocator); + try self.failed_decls.putNoClobber(decl, err_msg); + } else { + if (!srcHashEql(decl.contents_hash, contents_hash)) { + try self.markOutdatedDecl(decl); + decl.contents_hash = contents_hash; + } } } else { const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 79b67da2c2a0ff17ad40140749646e328f4fb7ce..f7766abcef5852ddebbcb8216905912f13de7433 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -67,14 +67,28 @@ pub fn addCases(ctx: *TestContext) !void { \\@1 = export(@0, "start") ); } + // TODO: need to make sure this works with other variants of export. + // As is, the same error occurs without export. + { + var case = ctx.obj("exported symbol collision", linux_x64); + case.addError( + \\export fn entry() void {} + \\export fn entry() void {} + , &[_][]const u8{":2:11: error: redefinition of 'entry'"}); + case.compiles( + \\export fn entry() void {} + ); + case.addError( + \\fn entry() void {} + \\fn entry() void {} + , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); + case.compiles( + \\export fn entry() void {} + ); + } // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 - // ctx.compileError("Export same symbol twice", linux_x64, - // \\export fn entry() void {} - // \\export fn entry() void {} - // , &[_][]const u8{":2:1: error: exported symbol collision"}); - // ctx.addError("Missing function name", linux_x64, .Zig, // \\fn() void {} // , &[_][]const u8{":1:3: error: missing function name"}); -- 2.54.0 From c8f60b2e2f15713754ac4b0911a7d13b6057264d Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 26 Jun 2020 04:36:17 -0400 Subject: [PATCH 128/295] Stage2: handle missing function names --- src-self-hosted/Module.zig | 13 +++++++++++-- test/stage2/compile_errors.zig | 12 +++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 134693b6fdfeadccc2871a562071d8dc132fe634..8ac4e898076167bd8866a559a713d7ecc45cb7e9 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1722,8 +1722,16 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { for (decls) |src_decl, decl_i| { if (src_decl.cast(ast.Node.FnProto)) |fn_proto| { // We will create a Decl for it regardless of analysis status. - const name_tok = fn_proto.name_token orelse - @panic("TODO handle missing function name in the parser"); + const name_tok = fn_proto.name_token orelse { + const err_msg = try ErrorMsg.create(self.allocator, tree.token_locs[fn_proto.firstToken()].end, "missing function name", .{}); + // TODO: cache a single invalid decl in the Module? + const new_decl = try self.createNewDecl(&root_scope.base, "", decl_i, [1]u8{0} ** 16, [1]u8{0} ** 16); + root_scope.decls.appendAssumeCapacity(new_decl); + errdefer err_msg.destroy(self.allocator); + try self.failed_decls.putNoClobber(new_decl, err_msg); + continue; + }; + const name_loc = tree.token_locs[name_tok]; const name = tree.tokenSliceLoc(name_loc); const name_hash = root_scope.fullyQualifiedNameHash(name); @@ -1734,6 +1742,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { // have been re-ordered. decl.src_index = decl_i; if (deleted_decls.remove(decl) == null) { + decl.analysis = .sema_failure; const err_msg = try ErrorMsg.create(self.allocator, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); errdefer err_msg.destroy(self.allocator); try self.failed_decls.putNoClobber(decl, err_msg); diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index f7766abcef5852ddebbcb8216905912f13de7433..1184048cbba40ac6a04241514df1b3fd963b35d3 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -86,12 +86,18 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn entry() void {} ); } + { + var case = ctx.obj("missing function name", linux_x64); + case.addError( + \\fn() void {} + , &[_][]const u8{":1:3: error: missing function name"}); + case.compiles( + \\fn a() void {} + ); + } // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 - // ctx.addError("Missing function name", linux_x64, .Zig, - // \\fn() void {} - // , &[_][]const u8{":1:3: error: missing function name"}); //ctx.testCompileError( // \\comptime { // \\ return; -- 2.54.0 From 0e952a9f3a1522a6fd39d67a495d3918b8d8240d Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 26 Jun 2020 05:00:53 -0400 Subject: [PATCH 129/295] Stage2/Testing: Simply incremental compilation tests --- src-self-hosted/test.zig | 26 ++++++++++ test/stage2/compile_errors.zig | 90 +++++++++++++++------------------- 2 files changed, 65 insertions(+), 51 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 13f08d86e262eedb963afa0cd2f901302a9f05ac..cb1a9f6981ef307410598a29eabfd443754f6c2a 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -274,6 +274,32 @@ pub const TestContext = struct { ctx.addObj(name, target, T).compiles(src); } + pub fn incrementalFailure( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, + fixed_src: [:0]const u8, + ) void { + var case = ctx.addObj(name, target, .Zig); + case.addError(src, expected_errors); + case.compiles(fixed_src); + } + + pub fn incrementalFailureZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, + fixed_src: [:0]const u8, + ) void { + var case = ctx.addObj(name, target, .ZIR); + case.addError(src, expected_errors); + case.compiles(fixed_src); + } + pub fn compiles( ctx: *TestContext, name: []const u8, diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index 1184048cbba40ac6a04241514df1b3fd963b35d3..bfe4bf7d4820cadb46f73ece4d2b199c19313df3 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -43,58 +43,46 @@ pub fn addCases(ctx: *TestContext) !void { \\@1 = export(@0, "start") , &[_][]const u8{":4:9: error: unable to call function with naked calling convention"}); - { - var case = ctx.objZIR("exported symbol collision", linux_x64); - // First, ensure we receive the error correctly - case.addError( - \\@noreturn = primitive(noreturn) - \\ - \\@start_fnty = fntype([], @noreturn) - \\@start = fn(@start_fnty, {}) - \\ - \\@0 = str("_start") - \\@1 = export(@0, "start") - \\@2 = export(@0, "start") - , &[_][]const u8{":8:13: error: exported symbol collision: _start"}); - // Next, ensure everything works properly on the next compilation with the problem fixed - case.compiles( - \\@noreturn = primitive(noreturn) - \\ - \\@start_fnty = fntype([], @noreturn) - \\@start = fn(@start_fnty, {}) - \\ - \\@0 = str("_start") - \\@1 = export(@0, "start") - ); - } + ctx.incrementalFailureZIR("exported symbol collision", linux_x64, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn) + \\@start = fn(@start_fnty, {}) + \\ + \\@0 = str("_start") + \\@1 = export(@0, "start") + \\@2 = export(@0, "start") + , &[_][]const u8{":8:13: error: exported symbol collision: _start"}, + \\@noreturn = primitive(noreturn) + \\ + \\@start_fnty = fntype([], @noreturn) + \\@start = fn(@start_fnty, {}) + \\ + \\@0 = str("_start") + \\@1 = export(@0, "start") + ); + + ctx.incrementalFailure("function redefinition", linux_x64, + \\fn entry() void {} + \\fn entry() void {} + , &[_][]const u8{":2:4: error: redefinition of 'entry'"}, + \\fn entry() void {} + ); + // TODO: need to make sure this works with other variants of export. - // As is, the same error occurs without export. - { - var case = ctx.obj("exported symbol collision", linux_x64); - case.addError( - \\export fn entry() void {} - \\export fn entry() void {} - , &[_][]const u8{":2:11: error: redefinition of 'entry'"}); - case.compiles( - \\export fn entry() void {} - ); - case.addError( - \\fn entry() void {} - \\fn entry() void {} - , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); - case.compiles( - \\export fn entry() void {} - ); - } - { - var case = ctx.obj("missing function name", linux_x64); - case.addError( - \\fn() void {} - , &[_][]const u8{":1:3: error: missing function name"}); - case.compiles( - \\fn a() void {} - ); - } + ctx.incrementalFailure("function redefinition", linux_x64, + \\export fn entry() void {} + \\export fn entry() void {} + , &[_][]const u8{":2:11: error: redefinition of 'entry'"}, + \\export fn entry() void {} + ); + + ctx.incrementalFailure("missing function name", linux_x64, + \\fn() void {} + , &[_][]const u8{":1:3: error: missing function name"}, + \\fn a() void {} + ); + // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 -- 2.54.0 From 6d536168b004a56e0cd0748dca8f0e7f0822fdce Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 26 Jun 2020 06:51:35 -0400 Subject: [PATCH 130/295] Stage2/Testing: Update documentation --- src-self-hosted/test.zig | 107 +++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 33 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index cb1a9f6981ef307410598a29eabfd443754f6c2a..fd3d772c98c200520e673a5431fb9a8fe5ad16ba 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -34,7 +34,7 @@ pub const TestContext = struct { /// effects of the incremental compilation. src: [:0]const u8, case: union(enum) { - /// A transformation update transforms the input ZIR and tests against + /// A transformation update transforms the input and tests against /// the expected output ZIR. Transformation: [:0]const u8, /// An error update attempts to compile bad code, and ensures that it @@ -43,6 +43,7 @@ pub const TestContext = struct { Error: []const ErrorMsg, /// An execution update compiles and runs the input, testing the /// stdout against the expected results + /// This is a slice containing the expected message. Execution: []const u8, }, }; @@ -56,16 +57,20 @@ pub const TestContext = struct { /// update, so each update's source is treated as a single file being /// updated by the test harness and incrementally compiled. pub const Case = struct { + /// The name of the test case. This is shown if a test fails, and + /// otherwise ignored. name: []const u8, /// The platform the test targets. For non-native platforms, an emulator /// such as QEMU is required for tests to complete. target: std.zig.CrossTarget, + /// In order to be able to run e.g. Execution updates, this must be set + /// to Executable. output_mode: std.builtin.OutputMode, updates: std.ArrayList(Update), @"type": TestType, - /// Adds a subcase in which the module is updated with new ZIR, and the - /// resulting ZIR is validated. + /// Adds a subcase in which the module is updated with `src`, and the + /// resulting ZIR is validated against `result`. pub fn addTransform(self: *Case, src: [:0]const u8, result: [:0]const u8) void { self.updates.append(.{ .src = src, @@ -73,6 +78,8 @@ pub const TestContext = struct { }) catch unreachable; } + /// Adds a subcase in which the module is updated with `src`, compiled, + /// run, and the output is tested against `result`. pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { self.updates.append(.{ .src = src, @@ -80,10 +87,10 @@ pub const TestContext = struct { }) catch unreachable; } - /// Adds a subcase in which the module is updated with invalid ZIR, and - /// ensures that compilation fails for the expected reasons. - /// - /// Errors must be specified in sequential order. + /// Adds a subcase in which the module is updated with `src`, which + /// should contain invalid input, and ensures that compilation fails + /// for the expected reasons, given in sequential order in `errors` in + /// the form `:error: line:column: message`. pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { @@ -121,6 +128,8 @@ pub const TestContext = struct { self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable; } + /// Adds a subcase in which the module is updated with `src`, and + /// asserts that it compiles without issue pub fn compiles(self: *Case, src: [:0]const u8) void { self.addError(src, &[_][]const u8{}); } @@ -142,10 +151,12 @@ pub const TestContext = struct { return &ctx.cases.items[ctx.cases.items.len - 1]; } + /// Adds a test case for Zig input, producing an executable pub fn exe(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { return ctx.addExe(name, target, .Zig); } + /// Adds a test case for ZIR input, producing an executable pub fn exeZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { return ctx.addExe(name, target, .ZIR); } @@ -166,10 +177,12 @@ pub const TestContext = struct { return &ctx.cases.items[ctx.cases.items.len - 1]; } + /// Adds a test case for Zig input, producing an object file pub fn obj(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { return ctx.addObj(name, target, .Zig); } + /// Adds a test case for ZIR input, producing an object file pub fn objZIR(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget) *Case { return ctx.addObj(name, target, .ZIR); } @@ -184,6 +197,8 @@ pub const TestContext = struct { ctx.addExe(name, .{}, T).addCompareOutput(src, expected_stdout); } + /// Adds a test case that compiles the Zig source given in `src`, executes + /// it, runs it, and tests the output against `expected_stdout` pub fn compareOutput( ctx: *TestContext, name: []const u8, @@ -193,6 +208,8 @@ pub const TestContext = struct { return ctx.addCompareOutput(name, .Zig, src, expected_stdout); } + /// Adds a test case that compiles the ZIR source given in `src`, executes + /// it, runs it, and tests the output against `expected_stdout` pub fn compareOutputZIR( ctx: *TestContext, name: []const u8, @@ -213,6 +230,8 @@ pub const TestContext = struct { ctx.addObj(name, target, T).addTransform(src, result); } + /// Adds a test case that compiles the Zig given in `src` to ZIR and tests + /// the ZIR against `result` pub fn transform( ctx: *TestContext, name: []const u8, @@ -223,6 +242,8 @@ pub const TestContext = struct { ctx.addTransform(name, target, .Zig, src, result); } + /// Adds a test case that cleans up the ZIR source given in `src`, and + /// tests the resulting ZIR against `result` pub fn transformZIR( ctx: *TestContext, name: []const u8, @@ -244,6 +265,9 @@ pub const TestContext = struct { ctx.addObj(name, target, T).addError(src, expected_errors); } + /// Adds a test case that ensures that the Zig given in `src` fails to + /// compile for the expected reasons, given in sequential order in + /// `expected_errors` in the form `:error: line:column: message`. pub fn compileError( ctx: *TestContext, name: []const u8, @@ -254,6 +278,9 @@ pub const TestContext = struct { ctx.addError(name, target, .Zig, src, expected_errors); } + /// Adds a test case that ensures that the ZIR given in `src` fails to + /// compile for the expected reasons, given in sequential order in + /// `expected_errors` in the form `:error: line:column: message`. pub fn compileErrorZIR( ctx: *TestContext, name: []const u8, @@ -274,32 +301,8 @@ pub const TestContext = struct { ctx.addObj(name, target, T).compiles(src); } - pub fn incrementalFailure( - ctx: *TestContext, - name: []const u8, - target: std.zig.CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - fixed_src: [:0]const u8, - ) void { - var case = ctx.addObj(name, target, .Zig); - case.addError(src, expected_errors); - case.compiles(fixed_src); - } - - pub fn incrementalFailureZIR( - ctx: *TestContext, - name: []const u8, - target: std.zig.CrossTarget, - src: [:0]const u8, - expected_errors: []const []const u8, - fixed_src: [:0]const u8, - ) void { - var case = ctx.addObj(name, target, .ZIR); - case.addError(src, expected_errors); - case.compiles(fixed_src); - } - + /// Adds a test case that asserts that the Zig given in `src` compiles + /// without any errors. pub fn compiles( ctx: *TestContext, name: []const u8, @@ -309,6 +312,8 @@ pub const TestContext = struct { ctx.addCompiles(name, target, .Zig, src); } + /// Adds a test case that asserts that the ZIR given in `src` compiles + /// without any errors. pub fn compilesZIR( ctx: *TestContext, name: []const u8, @@ -318,6 +323,42 @@ pub const TestContext = struct { ctx.addCompiles(name, target, .ZIR, src); } + /// Adds a test case that first ensures that the Zig given in `src` fails + /// to compile for the reasons given in sequential order in + /// `expected_errors` in the form `:error: line:column: message`, then + /// asserts that fixing the source (updating with `fixed_src`) isn't broken + /// by incremental compilation. + pub fn incrementalFailure( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, + fixed_src: [:0]const u8, + ) void { + var case = ctx.addObj(name, target, .Zig); + case.addError(src, expected_errors); + case.compiles(fixed_src); + } + + /// Adds a test case that first ensures that the ZIR given in `src` fails + /// to compile for the reasons given in sequential order in + /// `expected_errors` in the form `:error: line:column: message`, then + /// asserts that fixing the source (updating with `fixed_src`) isn't broken + /// by incremental compilation. + pub fn incrementalFailureZIR( + ctx: *TestContext, + name: []const u8, + target: std.zig.CrossTarget, + src: [:0]const u8, + expected_errors: []const []const u8, + fixed_src: [:0]const u8, + ) void { + var case = ctx.addObj(name, target, .ZIR); + case.addError(src, expected_errors); + case.compiles(fixed_src); + } + fn init() TestContext { const allocator = std.heap.page_allocator; return .{ .cases = std.ArrayList(Case).init(allocator) }; -- 2.54.0 From 52787f2c9b8157f348a34f19a22025b78f95b873 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Fri, 26 Jun 2020 06:52:29 -0400 Subject: [PATCH 131/295] Fix a dumb --- src-self-hosted/test.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index fd3d772c98c200520e673a5431fb9a8fe5ad16ba..af9ffef5096b69085fc052ffbf48f11789bcc843 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -90,7 +90,7 @@ pub const TestContext = struct { /// Adds a subcase in which the module is updated with `src`, which /// should contain invalid input, and ensures that compilation fails /// for the expected reasons, given in sequential order in `errors` in - /// the form `:error: line:column: message`. + /// the form `:line:column: error: message`. pub fn addError(self: *Case, src: [:0]const u8, errors: []const []const u8) void { var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable; for (errors) |e, i| { @@ -267,7 +267,7 @@ pub const TestContext = struct { /// Adds a test case that ensures that the Zig given in `src` fails to /// compile for the expected reasons, given in sequential order in - /// `expected_errors` in the form `:error: line:column: message`. + /// `expected_errors` in the form `:line:column: error: message`. pub fn compileError( ctx: *TestContext, name: []const u8, @@ -280,7 +280,7 @@ pub const TestContext = struct { /// Adds a test case that ensures that the ZIR given in `src` fails to /// compile for the expected reasons, given in sequential order in - /// `expected_errors` in the form `:error: line:column: message`. + /// `expected_errors` in the form `:line:column: error: message`. pub fn compileErrorZIR( ctx: *TestContext, name: []const u8, @@ -325,7 +325,7 @@ pub const TestContext = struct { /// Adds a test case that first ensures that the Zig given in `src` fails /// to compile for the reasons given in sequential order in - /// `expected_errors` in the form `:error: line:column: message`, then + /// `expected_errors` in the form `:line:column: error: message`, then /// asserts that fixing the source (updating with `fixed_src`) isn't broken /// by incremental compilation. pub fn incrementalFailure( @@ -343,7 +343,7 @@ pub const TestContext = struct { /// Adds a test case that first ensures that the ZIR given in `src` fails /// to compile for the reasons given in sequential order in - /// `expected_errors` in the form `:error: line:column: message`, then + /// `expected_errors` in the form `:line:column: error: message`, then /// asserts that fixing the source (updating with `fixed_src`) isn't broken /// by incremental compilation. pub fn incrementalFailureZIR( -- 2.54.0 From bb55889ce0989e120b85a38aec44865a20844ff0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Jun 2020 15:10:30 -0400 Subject: [PATCH 132/295] README: add link to troubleshooting build issues wiki page See #5673 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 916f2e16f32e30807e63b4cf737689f337e900b3..e7ed9b746df77953f1f902a2cda7a98daaa0e73a 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ cmake .. make install ``` +Need help? [Troubleshooting Build Issues](https://github.com/ziglang/zig/wiki/Troubleshooting-Build-Issues) + ##### MacOS ``` -- 2.54.0 From dc9648f868ed8ad08f040753767c03976bbcf3b7 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Fri, 17 Apr 2020 14:15:36 -0600 Subject: [PATCH 133/295] new allocator interface --- lib/std/array_list.zig | 6 +- lib/std/c.zig | 11 + lib/std/heap.zig | 592 +++++++++++------------ lib/std/heap/arena_allocator.zig | 29 +- lib/std/heap/logging_allocator.zig | 61 ++- lib/std/mem.zig | 319 +++++++++--- lib/std/os/windows/bits.zig | 1 + lib/std/testing.zig | 2 +- lib/std/testing/failing_allocator.zig | 41 +- lib/std/testing/leak_count_allocator.zig | 21 +- 10 files changed, 623 insertions(+), 460 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 452ebe2124da40d9ae2aece7a61bb956722d03b3..896e5bf9dcec7463d99e30e1437a36203f50e310 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -219,7 +219,8 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { if (better_capacity >= new_capacity) break; } - const new_memory = try self.allocator.realloc(self.allocatedSlice(), better_capacity); + const new_memory = try self.allocator.reallocAtLeast(self.allocatedSlice(), better_capacity); + assert(new_memory.len >= better_capacity); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } @@ -441,7 +442,8 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ if (better_capacity >= new_capacity) break; } - const new_memory = try allocator.realloc(self.allocatedSlice(), better_capacity); + const new_memory = try allocator.reallocAtLeast(self.allocatedSlice(), better_capacity); + assert(new_memory.len >= better_capacity); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } diff --git a/lib/std/c.zig b/lib/std/c.zig index 97d6bf521565413943bb13c75c31b2f94eed0a14..57e6de65a3492d033181e760dd4914be1597f592 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -232,6 +232,17 @@ pub extern "c" fn setuid(uid: c_uint) c_int; pub extern "c" fn aligned_alloc(alignment: usize, size: usize) ?*c_void; pub extern "c" fn malloc(usize) ?*c_void; + +pub usingnamespace switch (builtin.os.tag) { + .linux, .freebsd, .kfreebsd, .netbsd, .openbsd => struct { + pub extern "c" fn malloc_usable_size(?*const c_void) usize; + }, + .macosx, .ios, .watchos, .tvos => struct { + pub extern "c" fn malloc_size(?*const c_void) usize; + }, + else => struct {}, +}; + pub extern "c" fn realloc(?*c_void, usize) ?*c_void; pub extern "c" fn free(*c_void) void; pub extern "c" fn posix_memalign(memptr: **c_void, alignment: usize, size: usize) c_int; diff --git a/lib/std/heap.zig b/lib/std/heap.zig index f05378c21587e457dce8d0b164944fc87f943b05..322c24934e3385147ee20df6ff5909c19d7ce818 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -15,48 +15,88 @@ pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; const Allocator = mem.Allocator; -pub const c_allocator = &c_allocator_state; +usingnamespace if (comptime @hasDecl(c, "malloc_size")) struct { + pub const supports_malloc_size = true; + pub const malloc_size = c.malloc_size; +} else if (comptime @hasDecl(c, "malloc_usable_size")) struct { + pub const supports_malloc_size = true; + pub const malloc_size = c.malloc_usable_size; +} else struct { + pub const supports_malloc_size = false; +}; + +pub const c_allocator = mem.getAllocatorPtr(&c_allocator_state); var c_allocator_state = Allocator{ - .reallocFn = cRealloc, - .shrinkFn = cShrink, + .allocFn = cAlloc, + .resizeFn = cResize, }; -fn cRealloc(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { - assert(new_align <= @alignOf(c_longdouble)); - const old_ptr = if (old_mem.len == 0) null else @ptrCast(*c_void, old_mem.ptr); - const buf = c.realloc(old_ptr, new_size) orelse return error.OutOfMemory; - return @ptrCast([*]u8, buf)[0..new_size]; +fn cAlloc(self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 { + assert(ptr_align <= @alignOf(c_longdouble)); + const ptr = @ptrCast([*]u8, c.malloc(len) orelse return error.OutOfMemory); + if (len_align == 0) { + return ptr[0..len]; + } + const full_len = init: { + if (comptime supports_malloc_size) { + const s = malloc_size(ptr); + assert(s >= len); + break :init s; + } + break :init len; + }; + return ptr[0..mem.alignBackwardAnyAlign(full_len, len_align)]; } -fn cShrink(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - const old_ptr = @ptrCast(*c_void, old_mem.ptr); - const buf = c.realloc(old_ptr, new_size) orelse return old_mem[0..new_size]; - return @ptrCast([*]u8, buf)[0..new_size]; +fn cResize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize { + if (new_len == 0) { + c.free(buf.ptr); + return 0; + } + if (new_len <= buf.len) { + return mem.alignAllocLen(buf.len, new_len, len_align); + } + if (comptime supports_malloc_size) { + const full_len = malloc_size(buf.ptr); + if (new_len <= full_len) { + return mem.alignAllocLen(full_len, new_len, len_align); + } + } + // TODO: could we still use realloc? are there any cases where we can guarantee that realloc won't move memory? + return error.OutOfMemory; } /// This allocator makes a syscall directly for every allocation and free. /// Thread-safe and lock-free. pub const page_allocator = if (std.Target.current.isWasm()) - &wasm_page_allocator_state + mem.getAllocatorPtr(&wasm_page_allocator_state) else if (std.Target.current.os.tag == .freestanding) root.os.heap.page_allocator else - &page_allocator_state; + mem.getAllocatorPtr(&page_allocator_state); var page_allocator_state = Allocator{ - .reallocFn = PageAllocator.realloc, - .shrinkFn = PageAllocator.shrink, + .allocFn = PageAllocator.alloc, + .resizeFn = PageAllocator.resize, }; var wasm_page_allocator_state = Allocator{ - .reallocFn = WasmPageAllocator.realloc, - .shrinkFn = WasmPageAllocator.shrink, + .allocFn = WasmPageAllocator.alloc, + .resizeFn = WasmPageAllocator.resize, }; pub const direct_allocator = @compileError("deprecated; use std.heap.page_allocator"); +/// Verifies that the adjusted length will still map to the full length +pub fn alignPageAllocLen(full_len: usize, len: usize, len_align: u29) usize { + const aligned_len = mem.alignAllocLen(full_len, len, len_align); + assert(mem.alignForward(aligned_len, mem.page_size) == full_len); + return aligned_len; +} + const PageAllocator = struct { - fn alloc(allocator: *Allocator, n: usize, alignment: u29) error{OutOfMemory}![]u8 { - if (n == 0) return &[0]u8{}; + fn alloc(allocator: *Allocator, n: usize, alignment: u29, len_align: u29) error{OutOfMemory}![]u8 { + assert(n > 0); + const alignedLen = mem.alignForward(n, mem.page_size); if (builtin.os.tag == .windows) { const w = os.windows; @@ -68,21 +108,21 @@ const PageAllocator = struct { // see https://devblogs.microsoft.com/oldnewthing/?p=42223 const addr = w.VirtualAlloc( null, - n, + alignedLen, w.MEM_COMMIT | w.MEM_RESERVE, w.PAGE_READWRITE, ) catch return error.OutOfMemory; // If the allocation is sufficiently aligned, use it. if (@ptrToInt(addr) & (alignment - 1) == 0) { - return @ptrCast([*]u8, addr)[0..n]; + return @ptrCast([*]u8, addr)[0..alignPageAllocLen(alignedLen, n, len_align)]; } // If it wasn't, actually do an explicitely aligned allocation. w.VirtualFree(addr, 0, w.MEM_RELEASE); - const alloc_size = n + alignment; + const alloc_size = n + alignment - mem.page_size; - const final_addr = while (true) { + while (true) { // Reserve a range of memory large enough to find a sufficiently // aligned address. const reserved_addr = w.VirtualAlloc( @@ -102,48 +142,50 @@ const PageAllocator = struct { // until it succeeds. const ptr = w.VirtualAlloc( @intToPtr(*c_void, aligned_addr), - n, + alignedLen, w.MEM_COMMIT | w.MEM_RESERVE, w.PAGE_READWRITE, ) catch continue; - return @ptrCast([*]u8, ptr)[0..n]; - }; - - return @ptrCast([*]u8, final_addr)[0..n]; + return @ptrCast([*]u8, ptr)[0..alignPageAllocLen(alignedLen, n, len_align)]; + } } - const alloc_size = if (alignment <= mem.page_size) n else n + alignment; + const maxDropLen = alignment - std.math.min(alignment, mem.page_size); + const allocLen = if (maxDropLen <= alignedLen - n) alignedLen + else mem.alignForward(alignedLen + maxDropLen, mem.page_size); const slice = os.mmap( null, - mem.alignForward(alloc_size, mem.page_size), + allocLen, os.PROT_READ | os.PROT_WRITE, os.MAP_PRIVATE | os.MAP_ANONYMOUS, -1, 0, ) catch return error.OutOfMemory; - if (alloc_size == n) return slice[0..n]; + assert(mem.isAligned(@ptrToInt(slice.ptr), mem.page_size)); const aligned_addr = mem.alignForward(@ptrToInt(slice.ptr), alignment); // Unmap the extra bytes that were only requested in order to guarantee // that the range of memory we were provided had a proper alignment in // it somewhere. The extra bytes could be at the beginning, or end, or both. - const unused_start_len = aligned_addr - @ptrToInt(slice.ptr); - if (unused_start_len != 0) { - os.munmap(slice[0..unused_start_len]); + const dropLen = aligned_addr - @ptrToInt(slice.ptr); + if (dropLen != 0) { + os.munmap(slice[0..dropLen]); } - const aligned_end_addr = mem.alignForward(aligned_addr + n, mem.page_size); - const unused_end_len = @ptrToInt(slice.ptr) + slice.len - aligned_end_addr; - if (unused_end_len != 0) { - os.munmap(@intToPtr([*]align(mem.page_size) u8, aligned_end_addr)[0..unused_end_len]); + + // Unmap extra pages + const alignedBufferLen = allocLen - dropLen; + if (alignedBufferLen > alignedLen) { + os.munmap(@alignCast(mem.page_size, @intToPtr([*]u8, aligned_addr))[alignedLen..alignedBufferLen]); } - return @intToPtr([*]u8, aligned_addr)[0..n]; + return @intToPtr([*]u8, aligned_addr)[0..alignPageAllocLen(alignedLen, n, len_align)]; } - fn shrink(allocator: *Allocator, old_mem_unaligned: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - const old_mem = @alignCast(mem.page_size, old_mem_unaligned); + fn resize(allocator: *Allocator, buf_unaligned: []u8, new_size: usize, len_align: u29) Allocator.Error!usize { + const new_size_aligned = mem.alignForward(new_size, mem.page_size); + if (builtin.os.tag == .windows) { const w = os.windows; if (new_size == 0) { @@ -153,100 +195,45 @@ const PageAllocator = struct { // is reserved in the initial allocation call to VirtualAlloc." // So we can only use MEM_RELEASE when actually releasing the // whole allocation. - w.VirtualFree(old_mem.ptr, 0, w.MEM_RELEASE); - } else { - const base_addr = @ptrToInt(old_mem.ptr); - const old_addr_end = base_addr + old_mem.len; - const new_addr_end = base_addr + new_size; - const new_addr_end_rounded = mem.alignForward(new_addr_end, mem.page_size); - if (old_addr_end > new_addr_end_rounded) { + w.VirtualFree(buf_unaligned.ptr, 0, w.MEM_RELEASE); + return 0; + } + if (new_size < buf_unaligned.len) { + const base_addr = @ptrToInt(buf_unaligned.ptr); + const old_addr_end = base_addr + buf_unaligned.len; + const new_addr_end = mem.alignForward(base_addr + new_size, mem.page_size); + if (old_addr_end > new_addr_end) { // For shrinking that is not releasing, we will only // decommit the pages not needed anymore. w.VirtualFree( - @intToPtr(*c_void, new_addr_end_rounded), - old_addr_end - new_addr_end_rounded, + @intToPtr(*c_void, new_addr_end), + old_addr_end - new_addr_end, w.MEM_DECOMMIT, ); } + return alignPageAllocLen(new_size_aligned, new_size, len_align); } - return old_mem[0..new_size]; - } - const base_addr = @ptrToInt(old_mem.ptr); - const old_addr_end = base_addr + old_mem.len; - const new_addr_end = base_addr + new_size; - const new_addr_end_rounded = mem.alignForward(new_addr_end, mem.page_size); - if (old_addr_end > new_addr_end_rounded) { - const ptr = @intToPtr([*]align(mem.page_size) u8, new_addr_end_rounded); - os.munmap(ptr[0 .. old_addr_end - new_addr_end_rounded]); - } - return old_mem[0..new_size]; - } - - fn realloc(allocator: *Allocator, old_mem_unaligned: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { - const old_mem = @alignCast(mem.page_size, old_mem_unaligned); - if (builtin.os.tag == .windows) { - if (old_mem.len == 0) { - return alloc(allocator, new_size, new_align); - } - - if (new_size <= old_mem.len and new_align <= old_align) { - return shrink(allocator, old_mem, old_align, new_size, new_align); - } - - const w = os.windows; - const base_addr = @ptrToInt(old_mem.ptr); - - if (new_align > old_align and base_addr & (new_align - 1) != 0) { - // Current allocation doesn't satisfy the new alignment. - // For now we'll do a new one no matter what, but maybe - // there is something smarter to do instead. - const result = try alloc(allocator, new_size, new_align); - assert(old_mem.len != 0); - @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); - w.VirtualFree(old_mem.ptr, 0, w.MEM_RELEASE); - - return result; - } - - const old_addr_end = base_addr + old_mem.len; - const old_addr_end_rounded = mem.alignForward(old_addr_end, mem.page_size); - const new_addr_end = base_addr + new_size; - const new_addr_end_rounded = mem.alignForward(new_addr_end, mem.page_size); - if (new_addr_end_rounded == old_addr_end_rounded) { - // The reallocation fits in the already allocated pages. - return @ptrCast([*]u8, old_mem.ptr)[0..new_size]; + if (new_size == buf_unaligned.len) { + return alignPageAllocLen(new_size_aligned, new_size, len_align); } - assert(new_addr_end_rounded > old_addr_end_rounded); - - // We need to commit new pages. - const additional_size = new_addr_end - old_addr_end_rounded; - const realloc_addr = w.kernel32.VirtualAlloc( - @intToPtr(*c_void, old_addr_end_rounded), - additional_size, - w.MEM_COMMIT | w.MEM_RESERVE, - w.PAGE_READWRITE, - ) orelse { - // Committing new pages at the end of the existing allocation - // failed, we need to try a new one. - const new_alloc_mem = try alloc(allocator, new_size, new_align); - @memcpy(new_alloc_mem.ptr, old_mem.ptr, old_mem.len); - w.VirtualFree(old_mem.ptr, 0, w.MEM_RELEASE); + // new_size > buf_unaligned.len not implemented + return error.OutOfMemory; + } - return new_alloc_mem; - }; + const buf_aligned_len = mem.alignForward(buf_unaligned.len, mem.page_size); + if (new_size_aligned == buf_aligned_len) + return alignPageAllocLen(new_size_aligned, new_size, len_align); - assert(@ptrToInt(realloc_addr) == old_addr_end_rounded); - return @ptrCast([*]u8, old_mem.ptr)[0..new_size]; - } - if (new_size <= old_mem.len and new_align <= old_align) { - return shrink(allocator, old_mem, old_align, new_size, new_align); + if (new_size_aligned < buf_aligned_len) { + const ptr = @intToPtr([*]align(mem.page_size) u8, @ptrToInt(buf_unaligned.ptr) + new_size_aligned); + os.munmap(ptr[0 .. buf_aligned_len - new_size_aligned]); + if (new_size_aligned == 0) + return 0; + return alignPageAllocLen(new_size_aligned, new_size, len_align); } - const result = try alloc(allocator, new_size, new_align); - if (old_mem.len != 0) { - @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); - os.munmap(old_mem); - } - return result; + + // TODO: call mremap + return error.OutOfMemory; } }; @@ -338,16 +325,24 @@ const WasmPageAllocator = struct { } fn nPages(memsize: usize) usize { - return std.mem.alignForward(memsize, std.mem.page_size) / std.mem.page_size; + return mem.alignForward(memsize, mem.page_size) / mem.page_size; } - fn alloc(allocator: *Allocator, page_count: usize, alignment: u29) error{OutOfMemory}!usize { - var idx = conventional.useRecycled(page_count); - if (idx != FreeBlock.not_found) { - return idx; + fn alloc(allocator: *Allocator, len: usize, alignment: u29, len_align: u29) error{OutOfMemory}![]u8 { + const page_count = nPages(len); + const page_idx = try allocPages(page_count); + return @intToPtr([*]u8, page_idx * mem.page_size) + [0..alignPageAllocLen(page_count * mem.page_size, len, len_align)]; + } + fn allocPages(page_count: usize) !usize { + { + const idx = conventional.useRecycled(page_count); + if (idx != FreeBlock.not_found) { + return idx; + } } - idx = extended.useRecycled(page_count); + const idx = extended.useRecycled(page_count); if (idx != FreeBlock.not_found) { return idx + extendedOffset(); } @@ -360,51 +355,36 @@ const WasmPageAllocator = struct { return @intCast(usize, prev_page_count); } - pub fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) Allocator.Error![]u8 { - if (new_align > std.mem.page_size) { - return error.OutOfMemory; + fn freePages(start: usize, end: usize) void { + if (start < extendedOffset()) { + conventional.recycle(start, std.math.min(extendedOffset(), end) - start); } + if (end > extendedOffset()) { + var new_end = end; + if (!extended.isInitialized()) { + // Steal the last page from the memory currently being recycled + // TODO: would it be better if we use the first page instead? + new_end -= 1; - if (nPages(new_size) == nPages(old_mem.len)) { - return old_mem.ptr[0..new_size]; - } else if (new_size < old_mem.len) { - return shrink(allocator, old_mem, old_align, new_size, new_align); - } else { - const page_idx = try alloc(allocator, nPages(new_size), new_align); - const new_mem = @intToPtr([*]u8, page_idx * std.mem.page_size)[0..new_size]; - std.mem.copy(u8, new_mem, old_mem); - _ = shrink(allocator, old_mem, old_align, 0, 0); - return new_mem; + extended.data = @intToPtr([*]u128, new_end * mem.page_size)[0 .. mem.page_size / @sizeOf(u128)]; + // Since this is the first page being freed and we consume it, assume *nothing* is free. + mem.set(u128, extended.data, PageStatus.none_free); + } + const clamped_start = std.math.max(extendedOffset(), start); + extended.recycle(clamped_start - extendedOffset(), new_end - clamped_start); } } - pub fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - @setCold(true); - const free_start = nPages(@ptrToInt(old_mem.ptr) + new_size); - var free_end = nPages(@ptrToInt(old_mem.ptr) + old_mem.len); - - if (free_end > free_start) { - if (free_start < extendedOffset()) { - const clamped_end = std.math.min(extendedOffset(), free_end); - conventional.recycle(free_start, clamped_end - free_start); - } - - if (free_end > extendedOffset()) { - if (!extended.isInitialized()) { - // Steal the last page from the memory currently being recycled - // TODO: would it be better if we use the first page instead? - free_end -= 1; - - extended.data = @intToPtr([*]u128, free_end * std.mem.page_size)[0 .. std.mem.page_size / @sizeOf(u128)]; - // Since this is the first page being freed and we consume it, assume *nothing* is free. - std.mem.set(u128, extended.data, PageStatus.none_free); - } - const clamped_start = std.math.max(extendedOffset(), free_start); - extended.recycle(clamped_start - extendedOffset(), free_end - clamped_start); - } + fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!usize { + const aligned_len = mem.alignForward(buf.len, mem.page_size); + if (new_len > aligned_len) return error.OutOfMemory; + const current_n = nPages(aligned_len); + const new_n = nPages(new_len); + if (new_n != current_n) { + const base = nPages(@ptrToInt(buf.ptr)); + freePages(base + new_n, base + current_n); } - - return old_mem[0..new_size]; + return if (new_len == 0) 0 else alignPageAllocLen(new_n * mem.page_size, new_len, len_align); } }; @@ -418,8 +398,8 @@ pub const HeapAllocator = switch (builtin.os.tag) { pub fn init() HeapAllocator { return HeapAllocator{ .allocator = Allocator{ - .reallocFn = realloc, - .shrinkFn = shrink, + .allocFn = alloc, + .resizeFn = resize, }, .heap_handle = null, }; @@ -431,11 +411,14 @@ pub const HeapAllocator = switch (builtin.os.tag) { } } - fn alloc(allocator: *Allocator, n: usize, alignment: u29) error{OutOfMemory}![]u8 { + fn getRecordPtr(buf: []u8) *align(1) usize { + return @intToPtr(*align(1) usize, @ptrToInt(buf.ptr) + buf.len); + } + + fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 { const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); - if (n == 0) return &[0]u8{}; - const amt = n + alignment + @sizeOf(usize); + const amt = n + ptr_align - 1 + @sizeOf(usize); const optional_heap_handle = @atomicLoad(?HeapHandle, &self.heap_handle, builtin.AtomicOrder.SeqCst); const heap_handle = optional_heap_handle orelse blk: { const options = if (builtin.single_threaded) os.windows.HEAP_NO_SERIALIZE else 0; @@ -446,66 +429,60 @@ pub const HeapAllocator = switch (builtin.os.tag) { }; const ptr = os.windows.kernel32.HeapAlloc(heap_handle, 0, amt) orelse return error.OutOfMemory; const root_addr = @ptrToInt(ptr); - const adjusted_addr = mem.alignForward(root_addr, alignment); - const record_addr = adjusted_addr + n; - @intToPtr(*align(1) usize, record_addr).* = root_addr; - return @intToPtr([*]u8, adjusted_addr)[0..n]; - } - - fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - return realloc(allocator, old_mem, old_align, new_size, new_align) catch { - const old_adjusted_addr = @ptrToInt(old_mem.ptr); - const old_record_addr = old_adjusted_addr + old_mem.len; - const root_addr = @intToPtr(*align(1) usize, old_record_addr).*; - const old_ptr = @intToPtr(*c_void, root_addr); - const new_record_addr = old_record_addr - new_size + old_mem.len; - @intToPtr(*align(1) usize, new_record_addr).* = root_addr; - return old_mem[0..new_size]; + const aligned_addr = mem.alignForward(root_addr, ptr_align); + const return_len = init: { + if (len_align == 0) break :init n; + const full_len = os.windows.kernel32.HeapSize(heap_handle, 0, ptr); + assert(full_len != std.math.maxInt(usize)); + assert(full_len >= amt); + break :init mem.alignBackwardAnyAlign(full_len - (aligned_addr - root_addr), len_align); }; + const buf = @intToPtr([*]u8, aligned_addr)[0..return_len]; + getRecordPtr(buf).* = root_addr; + return buf; } - fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { - if (old_mem.len == 0) return alloc(allocator, new_size, new_align); - + fn resize(allocator: *Allocator, buf: []u8, new_size: usize, len_align: u29) error{OutOfMemory}!usize { const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); - const old_adjusted_addr = @ptrToInt(old_mem.ptr); - const old_record_addr = old_adjusted_addr + old_mem.len; - const root_addr = @intToPtr(*align(1) usize, old_record_addr).*; - const old_ptr = @intToPtr(*c_void, root_addr); - if (new_size == 0) { - os.windows.HeapFree(self.heap_handle.?, 0, old_ptr); - return old_mem[0..0]; + os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void ,getRecordPtr(buf).*)); + return 0; } - const amt = new_size + new_align + @sizeOf(usize); + const root_addr = getRecordPtr(buf).*; + const align_offset = @ptrToInt(buf.ptr) - root_addr; + const amt = align_offset + new_size + @sizeOf(usize); const new_ptr = os.windows.kernel32.HeapReAlloc( self.heap_handle.?, - 0, - old_ptr, + os.windows.HEAP_REALLOC_IN_PLACE_ONLY, + @intToPtr(*c_void, root_addr), amt, ) orelse return error.OutOfMemory; - const offset = old_adjusted_addr - root_addr; - const new_root_addr = @ptrToInt(new_ptr); - var new_adjusted_addr = new_root_addr + offset; - const offset_is_valid = new_adjusted_addr + new_size + @sizeOf(usize) <= new_root_addr + amt; - const offset_is_aligned = new_adjusted_addr % new_align == 0; - if (!offset_is_valid or !offset_is_aligned) { - // If HeapReAlloc didn't happen to move the memory to the new alignment, - // or the memory starting at the old offset would be outside of the new allocation, - // then we need to copy the memory to a valid aligned address and use that - const new_aligned_addr = mem.alignForward(new_root_addr, new_align); - @memcpy(@intToPtr([*]u8, new_aligned_addr), @intToPtr([*]u8, new_adjusted_addr), std.math.min(old_mem.len, new_size)); - new_adjusted_addr = new_aligned_addr; - } - const new_record_addr = new_adjusted_addr + new_size; - @intToPtr(*align(1) usize, new_record_addr).* = new_root_addr; - return @intToPtr([*]u8, new_adjusted_addr)[0..new_size]; + assert(new_ptr == @intToPtr(*c_void, root_addr)); + const return_len = init: { + if (len_align == 0) break :init new_size; + const full_len = os.windows.kernel32.HeapSize(self.heap_handle.?, 0, new_ptr); + assert(full_len != std.math.maxInt(usize)); + assert(full_len >= amt); + break :init mem.alignBackwardAnyAlign(full_len - align_offset, len_align); + }; + getRecordPtr(buf.ptr[0..return_len]).* = root_addr; + return return_len; } }, else => @compileError("Unsupported OS"), }; +fn sliceContainsPtr(container: []u8, ptr: [*]u8) bool { + return @ptrToInt(ptr) >= @ptrToInt(container.ptr) and + @ptrToInt(ptr) < (@ptrToInt(container.ptr) + container.len); +} + +fn sliceContainsSlice(container: []u8, slice: []u8) bool { + return @ptrToInt(slice.ptr) >= @ptrToInt(container.ptr) and + (@ptrToInt(slice.ptr) + slice.len) <= (@ptrToInt(container.ptr) + container.len); +} + pub const FixedBufferAllocator = struct { allocator: Allocator, end_index: usize, @@ -514,19 +491,33 @@ pub const FixedBufferAllocator = struct { pub fn init(buffer: []u8) FixedBufferAllocator { return FixedBufferAllocator{ .allocator = Allocator{ - .reallocFn = realloc, - .shrinkFn = shrink, + .allocFn = alloc, + .resizeFn = resize, }, .buffer = buffer, .end_index = 0, }; } - fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 { + pub fn ownsPtr(self: *FixedBufferAllocator, ptr: [*]u8) bool { + return sliceContainsPtr(self.buffer, ptr); + } + + pub fn ownsSlice(self: *FixedBufferAllocator, slice: []u8) bool { + return sliceContainsSlice(self.buffer, slice); + } + + // NOTE: this will not work in all cases, if the last allocation had an adjusted_index + // then we won't be able to determine what the last allocation was. This is because + // the alignForward operation done in alloc is not reverisible. + pub fn isLastAllocation(self: *FixedBufferAllocator, buf: []u8) bool { + return buf.ptr + buf.len == self.buffer.ptr + self.end_index; + } + + fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 { const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); - const addr = @ptrToInt(self.buffer.ptr) + self.end_index; - const adjusted_addr = mem.alignForward(addr, alignment); - const adjusted_index = self.end_index + (adjusted_addr - addr); + const aligned_addr = mem.alignForward(@ptrToInt(self.buffer.ptr) + self.end_index, ptr_align); + const adjusted_index = aligned_addr - @ptrToInt(self.buffer.ptr); const new_end_index = adjusted_index + n; if (new_end_index > self.buffer.len) { return error.OutOfMemory; @@ -534,33 +525,32 @@ pub const FixedBufferAllocator = struct { const result = self.buffer[adjusted_index..new_end_index]; self.end_index = new_end_index; - return result; + return result[0..mem.alignAllocLen(result.len, n, len_align)]; } - fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { + fn resize(allocator: *Allocator, buf: []u8, new_size: usize, len_align: u29) Allocator.Error!usize { const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator); - assert(old_mem.len <= self.end_index); - if (old_mem.ptr == self.buffer.ptr + self.end_index - old_mem.len and - mem.alignForward(@ptrToInt(old_mem.ptr), new_align) == @ptrToInt(old_mem.ptr)) - { - const start_index = self.end_index - old_mem.len; - const new_end_index = start_index + new_size; - if (new_end_index > self.buffer.len) return error.OutOfMemory; - const result = self.buffer[start_index..new_end_index]; - self.end_index = new_end_index; - return result; - } else if (new_size <= old_mem.len and new_align <= old_align) { - // We can't do anything with the memory, so tell the client to keep it. + assert(self.ownsSlice(buf)); // sanity check + + if (!self.isLastAllocation(buf)) { + if (new_size > buf.len) + return error.OutOfMemory; + return if (new_size == 0) 0 else mem.alignAllocLen(buf.len, new_size, len_align); + } + + if (new_size <= buf.len) { + const sub = buf.len - new_size; + self.end_index -= sub; + return if (new_size == 0) 0 else mem.alignAllocLen(buf.len - sub, new_size, len_align); + } + + var add = new_size - buf.len; + if (add + self.end_index > self.buffer.len) { + //add = self.buffer.len - self.end_index; return error.OutOfMemory; - } else { - const result = try alloc(allocator, new_size, new_align); - @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); - return result; } - } - - fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - return old_mem[0..new_size]; + self.end_index += add; + return mem.alignAllocLen(buf.len + add, new_size, len_align); } pub fn reset(self: *FixedBufferAllocator) void { @@ -581,20 +571,20 @@ pub const ThreadSafeFixedBufferAllocator = blk: { pub fn init(buffer: []u8) ThreadSafeFixedBufferAllocator { return ThreadSafeFixedBufferAllocator{ .allocator = Allocator{ - .reallocFn = realloc, - .shrinkFn = shrink, + .allocFn = alloc, + .resizeFn = Allocator.noResize, }, .buffer = buffer, .end_index = 0, }; } - fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 { + fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 { const self = @fieldParentPtr(ThreadSafeFixedBufferAllocator, "allocator", allocator); var end_index = @atomicLoad(usize, &self.end_index, builtin.AtomicOrder.SeqCst); while (true) { const addr = @ptrToInt(self.buffer.ptr) + end_index; - const adjusted_addr = mem.alignForward(addr, alignment); + const adjusted_addr = mem.alignForward(addr, ptr_align); const adjusted_index = end_index + (adjusted_addr - addr); const new_end_index = adjusted_index + n; if (new_end_index > self.buffer.len) { @@ -604,21 +594,6 @@ pub const ThreadSafeFixedBufferAllocator = blk: { } } - fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { - if (new_size <= old_mem.len and new_align <= old_align) { - // We can't do anything useful with the memory, tell the client to keep it. - return error.OutOfMemory; - } else { - const result = try alloc(allocator, new_size, new_align); - @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); - return result; - } - } - - fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - return old_mem[0..new_size]; - } - pub fn reset(self: *ThreadSafeFixedBufferAllocator) void { self.end_index = 0; } @@ -632,8 +607,8 @@ pub fn stackFallback(comptime size: usize, fallback_allocator: *Allocator) Stack .fallback_allocator = fallback_allocator, .fixed_buffer_allocator = undefined, .allocator = Allocator{ - .reallocFn = StackFallbackAllocator(size).realloc, - .shrinkFn = StackFallbackAllocator(size).shrink, + .allocFn = StackFallbackAllocator(size).realloc, + .resizeFn = StackFallbackAllocator(size).resize, }, }; } @@ -652,58 +627,19 @@ pub fn StackFallbackAllocator(comptime size: usize) type { return &self.allocator; } - fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { + fn alloc(allocator: *Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![*]u8 { const self = @fieldParentPtr(Self, "allocator", allocator); - const in_buffer = @ptrToInt(old_mem.ptr) >= @ptrToInt(&self.buffer) and - @ptrToInt(old_mem.ptr) < @ptrToInt(&self.buffer) + self.buffer.len; - if (in_buffer) { - return FixedBufferAllocator.realloc( - &self.fixed_buffer_allocator.allocator, - old_mem, - old_align, - new_size, - new_align, - ) catch { - const result = try self.fallback_allocator.reallocFn( - self.fallback_allocator, - &[0]u8{}, - undefined, - new_size, - new_align, - ); - mem.copy(u8, result, old_mem); - return result; - }; - } - return self.fallback_allocator.reallocFn( - self.fallback_allocator, - old_mem, - old_align, - new_size, - new_align, - ); + return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, ptr_align) catch + return fallback_allocator.alloc(len, ptr_align); } - fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { + fn resize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!void { const self = @fieldParentPtr(Self, "allocator", allocator); - const in_buffer = @ptrToInt(old_mem.ptr) >= @ptrToInt(&self.buffer) and - @ptrToInt(old_mem.ptr) < @ptrToInt(&self.buffer) + self.buffer.len; - if (in_buffer) { - return FixedBufferAllocator.shrink( - &self.fixed_buffer_allocator.allocator, - old_mem, - old_align, - new_size, - new_align, - ); + if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) { + try self.fixed_buffer_allocator.callResizeFn(buf, new_len); + } else { + try self.fallback_allocator.callResizeFn(buf, new_len); } - return self.fallback_allocator.shrinkFn( - self.fallback_allocator, - old_mem, - old_align, - new_size, - new_align, - ); } }; } @@ -718,8 +654,8 @@ test "c_allocator" { test "WasmPageAllocator internals" { if (comptime std.Target.current.isWasm()) { - const conventional_memsize = WasmPageAllocator.conventional.totalPages() * std.mem.page_size; - const initial = try page_allocator.alloc(u8, std.mem.page_size); + const conventional_memsize = WasmPageAllocator.conventional.totalPages() * mem.page_size; + const initial = try page_allocator.alloc(u8, mem.page_size); std.debug.assert(@ptrToInt(initial.ptr) < conventional_memsize); // If this isn't conventional, the rest of these tests don't make sense. Also we have a serious memory leak in the test suite. var inplace = try page_allocator.realloc(initial, 1); @@ -799,7 +735,7 @@ test "ArenaAllocator" { var test_fixed_buffer_allocator_memory: [800000 * @sizeOf(u64)]u8 = undefined; test "FixedBufferAllocator" { - var fixed_buffer_allocator = FixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..]); + var fixed_buffer_allocator = mem.sanityWrap(FixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..])); try testAllocator(&fixed_buffer_allocator.allocator); try testAllocatorAligned(&fixed_buffer_allocator.allocator, 16); @@ -865,7 +801,10 @@ test "ThreadSafeFixedBufferAllocator" { try testAllocatorAlignedShrink(&fixed_buffer_allocator.allocator); } -fn testAllocator(allocator: *mem.Allocator) !void { +fn testAllocator(base_allocator: *mem.Allocator) !void { + var sanityAllocator = mem.sanityWrap(base_allocator); + const allocator = &sanityAllocator.allocator; + var slice = try allocator.alloc(*i32, 100); testing.expect(slice.len == 100); for (slice) |*item, i| { @@ -893,7 +832,10 @@ fn testAllocator(allocator: *mem.Allocator) !void { allocator.free(slice); } -fn testAllocatorAligned(allocator: *mem.Allocator, comptime alignment: u29) !void { +fn testAllocatorAligned(base_allocator: *mem.Allocator, comptime alignment: u29) !void { + var sanityAllocator = mem.sanityWrap(base_allocator); + const allocator = &sanityAllocator.allocator; + // initial var slice = try allocator.alignedAlloc(u8, alignment, 10); testing.expect(slice.len == 10); @@ -917,7 +859,10 @@ fn testAllocatorAligned(allocator: *mem.Allocator, comptime alignment: u29) !voi testing.expect(slice.len == 0); } -fn testAllocatorLargeAlignment(allocator: *mem.Allocator) mem.Allocator.Error!void { +fn testAllocatorLargeAlignment(base_allocator: *mem.Allocator) mem.Allocator.Error!void { + var sanityAllocator = mem.sanityWrap(base_allocator); + const allocator = &sanityAllocator.allocator; + //Maybe a platform's page_size is actually the same as or // very near usize? if (mem.page_size << 2 > maxInt(usize)) return; @@ -946,7 +891,10 @@ fn testAllocatorLargeAlignment(allocator: *mem.Allocator) mem.Allocator.Error!vo allocator.free(slice); } -fn testAllocatorAlignedShrink(allocator: *mem.Allocator) mem.Allocator.Error!void { +fn testAllocatorAlignedShrink(base_allocator: *mem.Allocator) mem.Allocator.Error!void { + var sanityAllocator = mem.sanityWrap(base_allocator); + const allocator = &sanityAllocator.allocator; + var debug_buffer: [1000]u8 = undefined; const debug_allocator = &FixedBufferAllocator.init(&debug_buffer).allocator; diff --git a/lib/std/heap/arena_allocator.zig b/lib/std/heap/arena_allocator.zig index b41399772af6192bedcf3e61aadce8dd44d0b02c..ee3bfc19d4117603ec32f10828734d8dd16457f3 100644 --- a/lib/std/heap/arena_allocator.zig +++ b/lib/std/heap/arena_allocator.zig @@ -20,8 +20,8 @@ pub const ArenaAllocator = struct { pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator { return .{ .allocator = Allocator{ - .reallocFn = realloc, - .shrinkFn = shrink, + .allocFn = alloc, + .resizeFn = Allocator.noResize, }, .child_allocator = child_allocator, .state = self, @@ -61,38 +61,23 @@ pub const ArenaAllocator = struct { return buf_node; } - fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 { + fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 { const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator); - var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + alignment); + var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + ptr_align); while (true) { const cur_buf = cur_node.data[@sizeOf(BufNode)..]; const addr = @ptrToInt(cur_buf.ptr) + self.state.end_index; - const adjusted_addr = mem.alignForward(addr, alignment); + const adjusted_addr = mem.alignForward(addr, ptr_align); const adjusted_index = self.state.end_index + (adjusted_addr - addr); const new_end_index = adjusted_index + n; if (new_end_index > cur_buf.len) { - cur_node = try self.createNode(cur_buf.len, n + alignment); + cur_node = try self.createNode(cur_buf.len, n + ptr_align); continue; } const result = cur_buf[adjusted_index..new_end_index]; self.state.end_index = new_end_index; - return result; + return result[0..mem.alignAllocLen(result.len, n, len_align)]; } } - - fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { - if (new_size <= old_mem.len and new_align <= new_size) { - // We can't do anything with the memory, so tell the client to keep it. - return error.OutOfMemory; - } else { - const result = try alloc(allocator, new_size, new_align); - @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); - return result; - } - } - - fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - return old_mem[0..new_size]; - } }; diff --git a/lib/std/heap/logging_allocator.zig b/lib/std/heap/logging_allocator.zig index 0d15986a76a94ed881caad2b53cfa27c039045ea..e465795b8f8982d80f74b1433b7035dcc9136f9c 100644 --- a/lib/std/heap/logging_allocator.zig +++ b/lib/std/heap/logging_allocator.zig @@ -15,39 +15,45 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type { pub fn init(parent_allocator: *Allocator, out_stream: OutStreamType) Self { return Self{ .allocator = Allocator{ - .reallocFn = realloc, - .shrinkFn = shrink, + .allocFn = alloc, + .resizeFn = resize, }, .parent_allocator = parent_allocator, .out_stream = out_stream, }; } - fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { + fn alloc(allocator: *Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 { const self = @fieldParentPtr(Self, "allocator", allocator); - if (old_mem.len == 0) { - self.out_stream.print("allocation of {} ", .{new_size}) catch {}; - } else { - self.out_stream.print("resize from {} to {} ", .{ old_mem.len, new_size }) catch {}; - } - const result = self.parent_allocator.reallocFn(self.parent_allocator, old_mem, old_align, new_size, new_align); + self.out_stream.print("alloc : {}", .{len}) catch {}; + const result = self.parent_allocator.callAllocFn(len, ptr_align, len_align); if (result) |buff| { - self.out_stream.print("success!\n", .{}) catch {}; + self.out_stream.print(" success!\n", .{}) catch {}; } else |err| { - self.out_stream.print("failure!\n", .{}) catch {}; + self.out_stream.print(" failure!\n", .{}) catch {}; } return result; } - fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { + fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!usize { const self = @fieldParentPtr(Self, "allocator", allocator); - const result = self.parent_allocator.shrinkFn(self.parent_allocator, old_mem, old_align, new_size, new_align); - if (new_size == 0) { - self.out_stream.print("free of {} bytes success!\n", .{old_mem.len}) catch {}; + if (new_len == 0) { + self.out_stream.print("free : {}\n", .{buf.len}) catch {}; + } else if (new_len <= buf.len) { + self.out_stream.print("shrink: {} to {}\n", .{buf.len, new_len}) catch {}; } else { - self.out_stream.print("shrink from {} bytes to {} bytes success!\n", .{ old_mem.len, new_size }) catch {}; + self.out_stream.print("expand: {} to {}", .{ buf.len, new_len }) catch {}; + } + if (self.parent_allocator.callResizeFn(buf, new_len, len_align)) |resized_len| { + if (new_len > buf.len) { + self.out_stream.print(" success!\n", .{}) catch {}; + } + return resized_len; + } else |e| { + std.debug.assert(new_len > buf.len); + self.out_stream.print(" failure!\n", .{}) catch {}; + return e; } - return result; } }; } @@ -60,17 +66,24 @@ pub fn loggingAllocator( } test "LoggingAllocator" { - var buf: [255]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); + var log_buf: [255]u8 = undefined; + var fbs = std.io.fixedBufferStream(&log_buf); - const allocator = &loggingAllocator(std.testing.allocator, fbs.outStream()).allocator; + var allocator_buf: [10]u8 = undefined; + var fixedBufferAllocator = std.mem.sanityWrap(std.heap.FixedBufferAllocator.init(&allocator_buf)); + const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.outStream()).allocator; - const ptr = try allocator.alloc(u8, 10); - allocator.free(ptr); + var a = try allocator.alloc(u8, 10); + a.len = allocator.shrinkBytes(a, 5, 0); + std.debug.assert(a.len == 5); + std.testing.expectError(error.OutOfMemory, allocator.callResizeFn(a, 20, 0)); + allocator.free(a); std.testing.expectEqualSlices(u8, - \\allocation of 10 success! - \\free of 10 bytes success! + \\alloc : 10 success! + \\shrink: 10 to 5 + \\expand: 5 to 20 failure! + \\free : 5 \\ , fbs.getWritten()); } diff --git a/lib/std/mem.zig b/lib/std/mem.zig index b942fd3bf4ca151bfa2f16029d1e5874bcc752ed..6a7e846f6238838f45c865ff56632036e5ede620 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -16,6 +16,52 @@ pub const page_size = switch (builtin.arch) { pub const Allocator = struct { pub const Error = error{OutOfMemory}; + /// Attempt to allocate at least `len` bytes aligned to `ptr_align`. + /// + /// If `len_align` is `0`, then the length returned MUST be exactly `len` bytes, + /// otherwise, the length must be aligned to `len_align`. + /// + /// `len` must be greater than or equal to `len_align` and must be aligned by `len_align`. + allocFn: fn (self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Error![]u8, + + /// Attempt to expand or shrink memory in place. `buf.len` must equal the most recent + /// length returned by `allocFn` or `resizeFn`. + /// + /// Passing a `new_len` of 0 frees and invalidates the buffer such that it can no + /// longer be passed to `resizeFn`. + /// + /// error.OutOfMemory can only be returned if `new_len` is greater than `buf.len`. + /// If `buf` cannot be expanded to accomodate `new_len`, then the allocation MUST be + /// unmodified and error.OutOfMemory MUST be returned. + /// + /// If `len_align` is `0`, then the length returned MUST be exactly `len` bytes, + /// otherwise, the length must be aligned to `len_align`. + /// + /// `new_len` must be greater than or equal to `len_align` and must be aligned by `len_align`. + resizeFn: fn (self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Error!usize, + + pub fn callAllocFn(self: *Allocator, new_len: usize, alignment: u29, len_align: u29) Error![]u8 { + return self.allocFn(self, new_len, alignment, len_align); + } + + pub fn callResizeFn(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Error!usize { + return self.resizeFn(self, buf, new_len, len_align); + } + + /// Set to resizeFn if in-place resize is not supported. + pub fn noResize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Error!usize { + if (new_len > buf.len) + return error.OutOfMemory; + return new_len; + } + + /// Call `resizeFn`, but caller guarantees that `new_len` <= `buf.len` meaning + /// error.OutOfMemory should be impossible. + pub fn shrinkBytes(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) usize { + assert(new_len <= buf.len); + return self.callResizeFn(buf, new_len, len_align) catch unreachable; + } + /// Realloc is used to modify the size or alignment of an existing allocation, /// as well as to provide the allocator with an opportunity to move an allocation /// to a better location. @@ -24,7 +70,7 @@ pub const Allocator = struct { /// When the size/alignment is less than or equal to the previous allocation, /// this function returns `error.OutOfMemory` when the allocator decides the client /// would be better off keeping the extra alignment/size. Clients will call - /// `shrinkFn` when they require the allocator to track a new alignment/size, + /// `callResizeFn` when they require the allocator to track a new alignment/size, /// and so this function should only return success when the allocator considers /// the reallocation desirable from the allocator's perspective. /// As an example, `std.ArrayList` tracks a "capacity", and therefore can handle @@ -37,16 +83,15 @@ pub const Allocator = struct { /// as `old_mem` was when `reallocFn` is called. The bytes of /// `return_value[old_mem.len..]` have undefined values. /// The returned slice must have its pointer aligned at least to `new_alignment` bytes. - reallocFn: fn ( + fn reallocBytes( self: *Allocator, /// Guaranteed to be the same as what was returned from most recent call to - /// `reallocFn` or `shrinkFn`. + /// `allocFn` or `resizeFn`. /// If `old_mem.len == 0` then this is a new allocation and `new_byte_count` /// is guaranteed to be >= 1. old_mem: []u8, /// If `old_mem.len == 0` then this is `undefined`, otherwise: - /// Guaranteed to be the same as what was returned from most recent call to - /// `reallocFn` or `shrinkFn`. + /// Guaranteed to be the same as what was passed to `allocFn`. /// Guaranteed to be >= 1. /// Guaranteed to be a power of 2. old_alignment: u29, @@ -57,23 +102,49 @@ pub const Allocator = struct { /// Guaranteed to be a power of 2. /// Returned slice's pointer must have this alignment. new_alignment: u29, - ) Error![]u8, + /// 0 indicates the length of the slice returned MUST match `new_byte_count` exactly + /// non-zero means the length of the returned slice must be aligned by `len_align` + /// `new_len` must be aligned by `len_align` + len_align: u29, + ) Error![]u8 { + if (old_mem.len == 0) { + const new_mem = try self.callAllocFn(new_byte_count, new_alignment, len_align); + @memset(new_mem.ptr, undefined, new_byte_count); + return new_mem; + } - /// This function deallocates memory. It must succeed. - shrinkFn: fn ( - self: *Allocator, - /// Guaranteed to be the same as what was returned from most recent call to - /// `reallocFn` or `shrinkFn`. - old_mem: []u8, - /// Guaranteed to be the same as what was returned from most recent call to - /// `reallocFn` or `shrinkFn`. - old_alignment: u29, - /// Guaranteed to be less than or equal to `old_mem.len`. - new_byte_count: usize, - /// If `new_byte_count == 0` then this is `undefined`, otherwise: - /// Guaranteed to be less than or equal to `old_alignment`. - new_alignment: u29, - ) []u8, + if (isAligned(@ptrToInt(old_mem.ptr), new_alignment)) { + if (new_byte_count <= old_mem.len) { + const shrunk_len = self.shrinkBytes(old_mem, new_byte_count, len_align); + if (shrunk_len < old_mem.len) { + @memset(old_mem.ptr + shrunk_len, undefined, old_mem.len - shrunk_len); + } + return old_mem.ptr[0..shrunk_len]; + } + if (self.callResizeFn(old_mem, new_byte_count, len_align)) |resized_len| { + assert(resized_len >= new_byte_count); + @memset(old_mem.ptr + new_byte_count, undefined, resized_len - new_byte_count); + return old_mem.ptr[0..resized_len]; + } else |_| { } + } + if (new_byte_count <= old_mem.len and new_alignment <= old_alignment) { + return error.OutOfMemory; + } + return self.moveBytes(old_mem, new_byte_count, new_alignment, len_align); + } + + /// Move the given memory to a new location in the given allocator to accomodate a new + /// size and alignment. + fn moveBytes(self: *Allocator, old_mem: []u8, new_len: usize, new_alignment: u29, len_align: u29) Error![]u8 { + assert(old_mem.len > 0); + assert(new_len > 0); + const new_mem = try self.callAllocFn(new_len, new_alignment, len_align); + @memcpy(new_mem.ptr, old_mem.ptr, std.math.min(new_len, old_mem.len)); + // DISABLED TO AVOID BUGS IN TRANSLATE C + //@memset(old_mem.ptr, undefined, old_mem.len); + _ = self.shrinkBytes(old_mem, 0, 0); + return new_mem; + } /// Returns a pointer to undefined memory. /// Call `destroy` with the result to free the memory. @@ -89,8 +160,7 @@ pub const Allocator = struct { const T = @TypeOf(ptr).Child; if (@sizeOf(T) == 0) return; const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr)); - const shrink_result = self.shrinkFn(self, non_const_ptr[0..@sizeOf(T)], @alignOf(T), 0, 1); - assert(shrink_result.len == 0); + _ = self.shrinkBytes(non_const_ptr[0..@sizeOf(T)], 0, 0); } /// Allocates an array of `n` items of type `T` and sets all the @@ -150,9 +220,21 @@ pub const Allocator = struct { /// null means naturally aligned comptime alignment: ?u29, n: usize, + ) Error![]align(alignment orelse @alignOf(T)) T { + return self.alignedAlloc2(T, alignment, n, .exact); + } + + const Exact = enum {exact,atLeast}; + pub fn alignedAlloc2( + self: *Allocator, + comptime T: type, + /// null means naturally aligned + comptime alignment: ?u29, + n: usize, + exact: Exact, ) Error![]align(alignment orelse @alignOf(T)) T { const a = if (alignment) |a| blk: { - if (a == @alignOf(T)) return alignedAlloc(self, T, null, n); + if (a == @alignOf(T)) return alignedAlloc2(self, T, null, n, exact); break :blk a; } else @alignOf(T); @@ -161,15 +243,16 @@ pub const Allocator = struct { } const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory; - const byte_slice = try self.reallocFn(self, &[0]u8{}, undefined, byte_count, a); - assert(byte_slice.len == byte_count); + // TODO The `if (alignment == null)` blocks are workarounds for zig not being able to + // access certain type information about T without creating a circular dependency in async + // functions that heap-allocate their own frame with @Frame(func). + const sizeOfT = if (alignment == null) @intCast(u29, @divExact(byte_count, n)) else @sizeOf(T); + const byte_slice = try self.callAllocFn(byte_count, a, if (exact == .exact) @as(u29, 0) else sizeOfT); + assert(if (exact == .exact) byte_slice.len == byte_count else byte_slice.len >= byte_count); @memset(byte_slice.ptr, undefined, byte_slice.len); if (alignment == null) { - // TODO This is a workaround for zig not being able to successfully do - // @bytesToSlice(T, @alignCast(a, byte_slice)) without resolving alignment of T, - // which causes a circular dependency in async functions which try to heap-allocate - // their own frame with @Frame(func). - return @intToPtr([*]T, @ptrToInt(byte_slice.ptr))[0..n]; + // This if block is a workaround (see comment above) + return @intToPtr([*]T, @ptrToInt(byte_slice.ptr))[0..@divExact(byte_slice.len, @sizeOf(T))]; } else { return mem.bytesAsSlice(T, @alignCast(a, byte_slice)); } @@ -190,7 +273,15 @@ pub const Allocator = struct { break :t Error![]align(Slice.alignment) Slice.child; } { const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment; - return self.alignedRealloc(old_mem, old_alignment, new_n); + return self.alignedRealloc2(old_mem, old_alignment, new_n, .exact); + } + + pub fn reallocAtLeast(self: *Allocator, old_mem: var, new_n: usize) t: { + const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; + break :t Error![]align(Slice.alignment) Slice.child; + } { + const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment; + return self.alignedRealloc2(old_mem, old_alignment, new_n, .atLeast); } /// This is the same as `realloc`, except caller may additionally request @@ -201,11 +292,24 @@ pub const Allocator = struct { old_mem: var, comptime new_alignment: u29, new_n: usize, + ) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child { + return self.alignedRealloc2(old_mem, new_alignment, new_n, .exact); + } + + /// This is the same as `realloc`, except caller may additionally request + /// a new alignment, which can be larger, smaller, or the same as the old + /// allocation. + pub fn alignedRealloc2( + self: *Allocator, + old_mem: var, + comptime new_alignment: u29, + new_n: usize, + exact: Exact, ) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child { const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; const T = Slice.child; if (old_mem.len == 0) { - return self.alignedAlloc(T, new_alignment, new_n); + return self.alignedAlloc2(T, new_alignment, new_n, exact); } if (new_n == 0) { self.free(old_mem); @@ -215,12 +319,9 @@ pub const Allocator = struct { const old_byte_slice = mem.sliceAsBytes(old_mem); const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory; // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure - const byte_slice = try self.reallocFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment); - assert(byte_slice.len == byte_count); - if (new_n > old_mem.len) { - @memset(byte_slice.ptr + old_byte_slice.len, undefined, byte_slice.len - old_byte_slice.len); - } - return mem.bytesAsSlice(T, @alignCast(new_alignment, byte_slice)); + const new_byte_slice = try self.reallocBytes(old_byte_slice, Slice.alignment, byte_count, new_alignment, + if (exact == .exact) @as(u29, 0) else @sizeOf(T)); + return mem.bytesAsSlice(T, @alignCast(new_alignment, new_byte_slice)); } /// Prefer calling realloc to shrink if you can tolerate failure, such as @@ -248,12 +349,9 @@ pub const Allocator = struct { const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; const T = Slice.child; - if (new_n == 0) { - self.free(old_mem); - return old_mem[0..0]; - } - - assert(new_n <= old_mem.len); + if (new_n == old_mem.len) + return old_mem; + assert(new_n < old_mem.len); assert(new_alignment <= Slice.alignment); // Here we skip the overflow checking on the multiplication because @@ -262,9 +360,8 @@ pub const Allocator = struct { const old_byte_slice = mem.sliceAsBytes(old_mem); @memset(old_byte_slice.ptr + byte_count, undefined, old_byte_slice.len - byte_count); - const byte_slice = self.shrinkFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment); - assert(byte_slice.len == byte_count); - return mem.bytesAsSlice(T, @alignCast(new_alignment, byte_slice)); + _ = self.shrinkBytes(old_byte_slice, byte_count, 0); + return old_mem[0..new_n]; } /// Free an array allocated with `alloc`. To free a single item, @@ -276,8 +373,7 @@ pub const Allocator = struct { if (bytes_len == 0) return; const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr)); @memset(non_const_ptr, undefined, bytes_len); - const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1); - assert(shrink_result.len == 0); + _ = self.shrinkBytes(non_const_ptr[0..bytes_len], 0, 0); } /// Copies `m` to newly allocated memory. Caller owns the memory. @@ -296,16 +392,107 @@ pub const Allocator = struct { } }; +/// Given a pointer to an allocator, return the *Allocator for it. `allocatorStatePtr` can +/// either be a `*Allocator`, in which case it is returned as-is, otherwise, the address of +/// the `allocator` field is returned. +pub fn getAllocatorPtr(allocatorStatePtr: var) *Allocator { + // allocator must be a pointer or else this function will return a copy of the allocator which + // is not what this is for + const T = @TypeOf(allocatorStatePtr); + switch (@typeInfo(T)) { + .Pointer => {}, + else => @compileError("getAllocatorPtr expects a pointer to an allocator but got: " ++ @typeName(T)), + } + if (T == *Allocator) + return allocatorStatePtr; + return &allocatorStatePtr.allocator; +} + +/// Detects and asserts if the std.mem.Allocator interface is violated +pub fn SanityAllocator(comptime T: type) type { return struct { + const Self = @This(); + allocator: Allocator, + underlying_allocator: T, + pub fn init(allocator: T) @This() { + return .{ + .allocator = .{ + .allocFn = alloc, + .resizeFn = resize, + }, + .underlying_allocator = allocator, + }; + } + fn getUnderlyingAllocatorPtr(self: *@This()) *Allocator { + if (T == *Allocator) return self.underlying_allocator; + return getAllocatorPtr(&self.underlying_allocator); + } + pub fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 { + assert(n > 0); + assert(mem.isValidAlign(ptr_align)); + if (len_align != 0) { + assert(mem.isAlignedAnyAlign(n, len_align)); + assert(n >= len_align); + } + + const self = @fieldParentPtr(@This(), "allocator", allocator); + const result = try self.getUnderlyingAllocatorPtr().callAllocFn(n, ptr_align, len_align); + if (len_align == 0) { + assert(result.len == n); + } else { + assert(result.len >= n); + assert(mem.isAlignedAnyAlign(result.len, len_align)); + } + return result; + } + pub fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize { + assert(buf.len > 0); + if (len_align != 0) { + assert(mem.isAlignedAnyAlign(new_len, len_align)); + assert(new_len >= len_align); + } + const self = @fieldParentPtr(@This(), "allocator", allocator); + const result = try self.getUnderlyingAllocatorPtr().callResizeFn(buf, new_len, len_align); + if (len_align == 0) { + assert(result == new_len); + } else { + assert(result >= new_len); + assert(mem.isAlignedAnyAlign(result, len_align)); + } + return result; + } + pub usingnamespace if (T == *Allocator or !@hasDecl(T, "reset")) struct {} else struct { + pub fn reset(self: *Self) void { + self.underlying_allocator.reset(); + } + }; +};} + +pub fn sanityWrap(allocator: var) SanityAllocator(@TypeOf(allocator)) { + return SanityAllocator(@TypeOf(allocator)).init(allocator); +} + +/// An allocator helper function. Adjusts an allocation length satisfy `len_align`. +/// `full_len` should be the full capacity of the allocation which may be greater +/// than the `len` that was requsted. This function should only be used by allocators +/// that are unaffected by `len_align`. +pub fn alignAllocLen(full_len: usize, alloc_len: usize, len_align: u29) usize { + assert(alloc_len > 0); + assert(alloc_len >= len_align); + assert(full_len >= alloc_len); + if (len_align == 0) + return alloc_len; + const adjusted = alignBackwardAnyAlign(full_len, len_align); + assert(adjusted >= alloc_len); + return adjusted; +} + var failAllocator = Allocator{ - .reallocFn = failAllocatorRealloc, - .shrinkFn = failAllocatorShrink, + .allocFn = failAllocatorAlloc, + .resizeFn = Allocator.noResize, }; -fn failAllocatorRealloc(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { +fn failAllocatorAlloc(self: *Allocator, n: usize, alignment: u29, len_align: u29) Allocator.Error![]u8 { return error.OutOfMemory; } -fn failAllocatorShrink(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - @panic("failAllocatorShrink should never be called because it cannot allocate"); -} test "mem.Allocator basics" { testing.expectError(error.OutOfMemory, failAllocator.alloc(u8, 1)); @@ -2190,6 +2377,13 @@ test "alignForward" { testing.expect(alignForward(17, 8) == 24); } +pub fn alignBackwardAnyAlign(i: usize, alignment: usize) usize { + if (@popCount(usize, alignment) == 1) + return alignBackward(i, alignment); + assert(alignment != 0); + return i - @mod(i, alignment); +} + /// Round an address up to the previous aligned address /// The alignment must be a power of 2 and greater than 0. pub fn alignBackward(addr: usize, alignment: usize) usize { @@ -2206,6 +2400,19 @@ pub fn alignBackwardGeneric(comptime T: type, addr: T, alignment: T) T { return addr & ~(alignment - 1); } +/// Returns whether `alignment` is a valid alignment, meaning it is +/// a positive power of 2. +pub fn isValidAlign(alignment: u29) bool { + return @popCount(u29, alignment) == 1; +} + +pub fn isAlignedAnyAlign(i: usize, alignment: usize) bool { + if (@popCount(usize, alignment) == 1) + return isAligned(i, alignment); + assert(alignment != 0); + return 0 == @mod(i, alignment); +} + /// Given an address and an alignment, return true if the address is a multiple of the alignment /// The alignment must be a power of 2 and greater than 0. pub fn isAligned(addr: usize, alignment: usize) bool { diff --git a/lib/std/os/windows/bits.zig b/lib/std/os/windows/bits.zig index 191e8deded4b926baa60f9781abeb53ff45e794e..a6a9e50d53d41564cc9985042b265e243f3f51e4 100644 --- a/lib/std/os/windows/bits.zig +++ b/lib/std/os/windows/bits.zig @@ -593,6 +593,7 @@ pub const FILE_CURRENT = 1; pub const FILE_END = 2; pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000; +pub const HEAP_REALLOC_IN_PLACE_ONLY = 0x00000010; pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004; pub const HEAP_NO_SERIALIZE = 0x00000001; diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 117a788e1662b3bee787f9e81c52ad270ddc3454..0f811cada8e0d292eda030c535c8c8a3f75a7e18 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -11,7 +11,7 @@ pub var allocator_instance = LeakCountAllocator.init(&base_allocator_instance.al pub const failing_allocator = &failing_allocator_instance.allocator; pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0); -pub var base_allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]); +pub var base_allocator_instance = std.mem.sanityWrap(std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..])); var allocator_mem: [2 * 1024 * 1024]u8 = undefined; /// This function is intended to be used only in tests. It prints diagnostics to stderr diff --git a/lib/std/testing/failing_allocator.zig b/lib/std/testing/failing_allocator.zig index 081a29cd97576db857804eb553c95e1f2c7136a6..ade3e9d85ac283e91248427e5f2af61881a7d7cb 100644 --- a/lib/std/testing/failing_allocator.zig +++ b/lib/std/testing/failing_allocator.zig @@ -39,43 +39,38 @@ pub const FailingAllocator = struct { .allocations = 0, .deallocations = 0, .allocator = mem.Allocator{ - .reallocFn = realloc, - .shrinkFn = shrink, + .allocFn = alloc, + .resizeFn = resize, }, }; } - fn realloc(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { + fn alloc(allocator: *std.mem.Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 { const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); if (self.index == self.fail_index) { return error.OutOfMemory; } - const result = try self.internal_allocator.reallocFn( - self.internal_allocator, - old_mem, - old_align, - new_size, - new_align, - ); - if (new_size < old_mem.len) { - self.freed_bytes += old_mem.len - new_size; - if (new_size == 0) - self.deallocations += 1; - } else if (new_size > old_mem.len) { - self.allocated_bytes += new_size - old_mem.len; - if (old_mem.len == 0) - self.allocations += 1; - } + const result = try self.internal_allocator.callAllocFn(len, ptr_align, len_align); + self.allocated_bytes += result.len; + self.allocations += 1; self.index += 1; return result; } - fn shrink(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { + fn resize(allocator: *std.mem.Allocator, old_mem: []u8, new_len: usize, len_align: u29) error{OutOfMemory}!usize { const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); - const r = self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); - self.freed_bytes += old_mem.len - r.len; - if (new_size == 0) + const r = self.internal_allocator.callResizeFn(old_mem, new_len, len_align) catch |e| { + std.debug.assert(new_len > old_mem.len); + return e; + }; + if (new_len == 0) { self.deallocations += 1; + self.freed_bytes += old_mem.len; + } else if (r < old_mem.len) { + self.freed_bytes += old_mem.len - r; + } else { + self.allocated_bytes += r - old_mem.len; + } return r; } }; diff --git a/lib/std/testing/leak_count_allocator.zig b/lib/std/testing/leak_count_allocator.zig index 65244e529bcd91510ba4c1979f4d85218a35497c..87564aeea7a19cdf23df3d5fff12aa1337a3244c 100644 --- a/lib/std/testing/leak_count_allocator.zig +++ b/lib/std/testing/leak_count_allocator.zig @@ -14,23 +14,21 @@ pub const LeakCountAllocator = struct { return .{ .count = 0, .allocator = .{ - .reallocFn = realloc, - .shrinkFn = shrink, + .allocFn = alloc, + .resizeFn = resize, }, .internal_allocator = allocator, }; } - fn realloc(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { + fn alloc(allocator: *std.mem.Allocator, len: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 { const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator); - var data = try self.internal_allocator.reallocFn(self.internal_allocator, old_mem, old_align, new_size, new_align); - if (old_mem.len == 0) { - self.count += 1; - } - return data; + const ptr = try self.internal_allocator.callAllocFn(len, ptr_align, len_align); + self.count += 1; + return ptr; } - fn shrink(allocator: *std.mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { + fn resize(allocator: *std.mem.Allocator, old_mem: []u8, new_size: usize, len_align: u29) error{OutOfMemory}!usize { const self = @fieldParentPtr(LeakCountAllocator, "allocator", allocator); if (new_size == 0) { if (self.count == 0) { @@ -38,7 +36,10 @@ pub const LeakCountAllocator = struct { } self.count -= 1; } - return self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); + return self.internal_allocator.callResizeFn(old_mem, new_size, len_align) catch |e| { + std.debug.assert(new_size > old_mem.len); + return e; + }; } pub fn validate(self: LeakCountAllocator) !void { -- 2.54.0 From 14c3c47fb7315e6199751082f9ef544972bb13e6 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 26 Jun 2020 15:49:31 -0700 Subject: [PATCH 134/295] fs.deleteFile: Translate to error.IsDir when appropriate on POSIX systems Linux deviates from POSIX and returns EISDIR while other POSIX systems return EPERM. To make all platforms consistent in their errors when calling deleteFile on a directory, we have to do a stat to translate EPERM (AccessDenied) to EISDIR (IsDir). --- lib/std/fs.zig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 6cb7d478b21cb57732325475b9ff3dc4a0843d7e..c574737194ca441d624a990afb42fb39b3069df7 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1112,6 +1112,16 @@ pub const Dir = struct { pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) { error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR + error.AccessDenied => |e| switch (builtin.os.tag) { + // non-Linux POSIX systems return EPERM when trying to delete a directory, so + // we need to handle that case specifically and translate the error + .macosx, .ios, .freebsd, .netbsd, .dragonfly => { + const fstat = os.fstatat(self.fd, sub_path, 0) catch return e; + const is_dir = fstat.mode & os.S_IFMT == os.S_IFDIR; + return if (is_dir) error.IsDir else e; + }, + else => return e, + }, else => |e| return e, }; } @@ -1122,6 +1132,16 @@ pub const Dir = struct { pub fn deleteFileZ(self: Dir, sub_path_c: [*:0]const u8) DeleteFileError!void { os.unlinkatZ(self.fd, sub_path_c, 0) catch |err| switch (err) { error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR + error.AccessDenied => |e| switch (builtin.os.tag) { + // non-Linux POSIX systems return EPERM when trying to delete a directory, so + // we need to handle that case specifically and translate the error + .macosx, .ios, .freebsd, .netbsd, .dragonfly => { + const fstat = os.fstatatZ(self.fd, sub_path_c, 0) catch return e; + const is_dir = fstat.mode & os.S_IFMT == os.S_IFDIR; + return if (is_dir) error.IsDir else e; + }, + else => return e, + }, else => |e| return e, }; } -- 2.54.0 From 505bc9817a2b78c97819f481b3cd98102c7ff964 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 26 Jun 2020 16:08:26 -0700 Subject: [PATCH 135/295] Implement Dir.deleteFile in terms of deleteFileZ/deleteFileW Reduces duplicate code, consistent with other fn/fnZ/fnW implementations --- lib/std/fs.zig | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index c574737194ca441d624a990afb42fb39b3069df7..e7de8bd52ddc82ce98776d552b7e41c39665e528 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1110,20 +1110,18 @@ pub const Dir = struct { /// Delete a file name and possibly the file it refers to, based on an open directory handle. /// Asserts that the path parameter has no null bytes. pub fn deleteFile(self: Dir, sub_path: []const u8) DeleteFileError!void { - os.unlinkat(self.fd, sub_path, 0) catch |err| switch (err) { - error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR - error.AccessDenied => |e| switch (builtin.os.tag) { - // non-Linux POSIX systems return EPERM when trying to delete a directory, so - // we need to handle that case specifically and translate the error - .macosx, .ios, .freebsd, .netbsd, .dragonfly => { - const fstat = os.fstatat(self.fd, sub_path, 0) catch return e; - const is_dir = fstat.mode & os.S_IFMT == os.S_IFDIR; - return if (is_dir) error.IsDir else e; - }, - else => return e, - }, - else => |e| return e, - }; + if (builtin.os.tag == .windows) { + const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); + return self.deleteFileW(sub_path_w.span().ptr); + } else if (builtin.os.tag == .wasi) { + os.unlinkatWasi(self.fd, sub_path, 0) catch |err| switch (err) { + error.DirNotEmpty => unreachable, // not passing AT_REMOVEDIR + else => |e| return e, + }; + } else { + const sub_path_c = try os.toPosixPath(sub_path); + return self.deleteFileZ(&sub_path_c); + } } pub const deleteFileC = @compileError("deprecated: renamed to deleteFileZ"); -- 2.54.0 From 12aca758c6ee923065d1d64a62e44c8a8fd6cd99 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 26 Jun 2020 17:12:02 -0700 Subject: [PATCH 136/295] Dir.deleteFile: Fix symlink behavior when translating EPERM to EISDIR --- lib/std/fs.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index e7de8bd52ddc82ce98776d552b7e41c39665e528..28f2daa96aabc11abafec80a40069d997014aecb 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1134,7 +1134,8 @@ pub const Dir = struct { // non-Linux POSIX systems return EPERM when trying to delete a directory, so // we need to handle that case specifically and translate the error .macosx, .ios, .freebsd, .netbsd, .dragonfly => { - const fstat = os.fstatatZ(self.fd, sub_path_c, 0) catch return e; + // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them) + const fstat = os.fstatatZ(self.fd, sub_path_c, os.AT_SYMLINK_NOFOLLOW) catch return e; const is_dir = fstat.mode & os.S_IFMT == os.S_IFDIR; return if (is_dir) error.IsDir else e; }, -- 2.54.0 From 626b5eccab7264e579ce58f56be5fbc3aa42efc4 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 26 Jun 2020 23:17:40 -0700 Subject: [PATCH 137/295] Move fs-specific tests from os/test.zig to fs/test.zig The moved tests do not use `std.os` directly and instead use `std.fs` functions, so it makes more sense for them to be in `fs/test.zig` --- lib/std/fs/test.zig | 157 ++++++++++++++++++++++++++++++++++++++++++++ lib/std/os/test.zig | 157 -------------------------------------------- 2 files changed, 157 insertions(+), 157 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index b024f0f4f666feb9e834455b943bc21968698719..1220d96b5b69b322412f04f00f7a71748a44d287 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -80,6 +80,163 @@ test "openSelfExe" { self_exe_file.close(); } +test "makePath, put some files in it, deleteTree" { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); + try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); + try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); + try tmp.dir.deleteTree("os_test_tmp"); + if (tmp.dir.openDir("os_test_tmp", .{})) |dir| { + @panic("expected error"); + } else |err| { + testing.expect(err == error.FileNotFound); + } +} + +test "access file" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.makePath("os_test_tmp"); + if (tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| { + @panic("expected error"); + } else |err| { + testing.expect(err == error.FileNotFound); + } + + try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", ""); + try tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{}); + try tmp.dir.deleteTree("os_test_tmp"); +} + +test "sendfile" { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.makePath("os_test_tmp"); + defer tmp.dir.deleteTree("os_test_tmp") catch {}; + + var dir = try tmp.dir.openDir("os_test_tmp", .{}); + defer dir.close(); + + const line1 = "line1\n"; + const line2 = "second line\n"; + var vecs = [_]std.os.iovec_const{ + .{ + .iov_base = line1, + .iov_len = line1.len, + }, + .{ + .iov_base = line2, + .iov_len = line2.len, + }, + }; + + var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); + defer src_file.close(); + + try src_file.writevAll(&vecs); + + var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); + defer dest_file.close(); + + const header1 = "header1\n"; + const header2 = "second header\n"; + const trailer1 = "trailer1\n"; + const trailer2 = "second trailer\n"; + var hdtr = [_]std.os.iovec_const{ + .{ + .iov_base = header1, + .iov_len = header1.len, + }, + .{ + .iov_base = header2, + .iov_len = header2.len, + }, + .{ + .iov_base = trailer1, + .iov_len = trailer1.len, + }, + .{ + .iov_base = trailer2, + .iov_len = trailer2.len, + }, + }; + + var written_buf: [100]u8 = undefined; + try dest_file.writeFileAll(src_file, .{ + .in_offset = 1, + .in_len = 10, + .headers_and_trailers = &hdtr, + .header_count = 2, + }); + const amt = try dest_file.preadAll(&written_buf, 0); + testing.expect(mem.eql(u8, written_buf[0..amt], "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n")); +} + +test "fs.copyFile" { + const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP"; + const src_file = "tmp_test_copy_file.txt"; + const dest_file = "tmp_test_copy_file2.txt"; + const dest_file2 = "tmp_test_copy_file3.txt"; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.writeFile(src_file, data); + defer tmp.dir.deleteFile(src_file) catch {}; + + try tmp.dir.copyFile(src_file, tmp.dir, dest_file, .{}); + defer tmp.dir.deleteFile(dest_file) catch {}; + + try tmp.dir.copyFile(src_file, tmp.dir, dest_file2, .{ .override_mode = File.default_mode }); + defer tmp.dir.deleteFile(dest_file2) catch {}; + + try expectFileContents(tmp.dir, dest_file, data); + try expectFileContents(tmp.dir, dest_file2, data); +} + +fn expectFileContents(dir: fs.Dir, file_path: []const u8, data: []const u8) !void { + const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000); + defer testing.allocator.free(contents); + + testing.expectEqualSlices(u8, data, contents); +} + +test "AtomicFile" { + const test_out_file = "tmp_atomic_file_test_dest.txt"; + const test_content = + \\ hello! + \\ this is a test file + ; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + { + var af = try tmp.dir.atomicFile(test_out_file, .{}); + defer af.deinit(); + try af.file.writeAll(test_content); + try af.finish(); + } + const content = try tmp.dir.readFileAlloc(testing.allocator, test_out_file, 9999); + defer testing.allocator.free(content); + testing.expect(mem.eql(u8, content, test_content)); + + try tmp.dir.deleteFile(test_out_file); +} + +test "realpath" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf)); +} + const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.ns_per_ms; test "open file with exclusive nonblocking lock twice" { diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 34747518e4c40693c085c7f9b87bfe0ad4256f47..e508f5ae20f3fba20c742f27ba94efeb79204784 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -59,137 +59,10 @@ test "readlinkat" { expect(mem.eql(u8, "file.txt", read_link)); } -test "makePath, put some files in it, deleteTree" { - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); - try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); - try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); - try tmp.dir.deleteTree("os_test_tmp"); - if (tmp.dir.openDir("os_test_tmp", .{})) |dir| { - @panic("expected error"); - } else |err| { - expect(err == error.FileNotFound); - } -} - -test "access file" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - try tmp.dir.makePath("os_test_tmp"); - if (tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| { - @panic("expected error"); - } else |err| { - expect(err == error.FileNotFound); - } - - try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", ""); - try tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{}); - try tmp.dir.deleteTree("os_test_tmp"); -} - fn testThreadIdFn(thread_id: *Thread.Id) void { thread_id.* = Thread.getCurrentId(); } -test "sendfile" { - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - try tmp.dir.makePath("os_test_tmp"); - defer tmp.dir.deleteTree("os_test_tmp") catch {}; - - var dir = try tmp.dir.openDir("os_test_tmp", .{}); - defer dir.close(); - - const line1 = "line1\n"; - const line2 = "second line\n"; - var vecs = [_]os.iovec_const{ - .{ - .iov_base = line1, - .iov_len = line1.len, - }, - .{ - .iov_base = line2, - .iov_len = line2.len, - }, - }; - - var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); - defer src_file.close(); - - try src_file.writevAll(&vecs); - - var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); - defer dest_file.close(); - - const header1 = "header1\n"; - const header2 = "second header\n"; - const trailer1 = "trailer1\n"; - const trailer2 = "second trailer\n"; - var hdtr = [_]os.iovec_const{ - .{ - .iov_base = header1, - .iov_len = header1.len, - }, - .{ - .iov_base = header2, - .iov_len = header2.len, - }, - .{ - .iov_base = trailer1, - .iov_len = trailer1.len, - }, - .{ - .iov_base = trailer2, - .iov_len = trailer2.len, - }, - }; - - var written_buf: [100]u8 = undefined; - try dest_file.writeFileAll(src_file, .{ - .in_offset = 1, - .in_len = 10, - .headers_and_trailers = &hdtr, - .header_count = 2, - }); - const amt = try dest_file.preadAll(&written_buf, 0); - expect(mem.eql(u8, written_buf[0..amt], "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n")); -} - -test "fs.copyFile" { - const data = "u6wj+JmdF3qHsFPE BUlH2g4gJCmEz0PP"; - const src_file = "tmp_test_copy_file.txt"; - const dest_file = "tmp_test_copy_file2.txt"; - const dest_file2 = "tmp_test_copy_file3.txt"; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - try tmp.dir.writeFile(src_file, data); - defer tmp.dir.deleteFile(src_file) catch {}; - - try tmp.dir.copyFile(src_file, tmp.dir, dest_file, .{}); - defer tmp.dir.deleteFile(dest_file) catch {}; - - try tmp.dir.copyFile(src_file, tmp.dir, dest_file2, .{ .override_mode = File.default_mode }); - defer tmp.dir.deleteFile(dest_file2) catch {}; - - try expectFileContents(tmp.dir, dest_file, data); - try expectFileContents(tmp.dir, dest_file2, data); -} - -fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void { - const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000); - defer testing.allocator.free(contents); - - testing.expectEqualSlices(u8, data, contents); -} - test "std.Thread.getCurrentId" { if (builtin.single_threaded) return error.SkipZigTest; @@ -242,29 +115,6 @@ test "cpu count" { expect(cpu_count >= 1); } -test "AtomicFile" { - const test_out_file = "tmp_atomic_file_test_dest.txt"; - const test_content = - \\ hello! - \\ this is a test file - ; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - { - var af = try tmp.dir.atomicFile(test_out_file, .{}); - defer af.deinit(); - try af.file.writeAll(test_content); - try af.finish(); - } - const content = try tmp.dir.readFileAlloc(testing.allocator, test_out_file, 9999); - defer testing.allocator.free(content); - expect(mem.eql(u8, content, test_content)); - - try tmp.dir.deleteFile(test_out_file); -} - test "thread local storage" { if (builtin.single_threaded) return error.SkipZigTest; const thread1 = try Thread.spawn({}, testTls); @@ -299,13 +149,6 @@ test "getcwd" { _ = os.getcwd(&buf) catch undefined; } -test "realpath" { - if (builtin.os.tag == .wasi) return error.SkipZigTest; - - var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf)); -} - test "sigaltstack" { if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest; -- 2.54.0 From ab307a22f627fd01ef6220d62a0312fdd610eaed Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sat, 27 Jun 2020 07:16:59 -0400 Subject: [PATCH 138/295] Stage2: remove clearErrors, fix ZIR export collision detection --- src-self-hosted/Module.zig | 31 ++++--------------------------- test/stage2/compile_errors.zig | 2 +- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 8ac4e898076167bd8866a559a713d7ecc45cb7e9..b15f1d88226e23bd473c1e6781913b04fba892f8 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -851,8 +851,6 @@ pub fn update(self: *Module) !void { self.generation += 1; - self.clearErrors(); - // TODO Use the cache hash file system to detect which source files changed. // Until then we simulate a full cache miss. Source files could have been loaded for any reason; // to force a refresh we unload now. @@ -916,31 +914,6 @@ pub fn totalErrorCount(self: *Module) usize { return if (total == 0) @boolToInt(self.link_error_flags.no_entry_point_found) else total; } -pub fn clearErrors(self: *Module) void { - const allocator = self.allocator; - { - var it = self.failed_decls.iterator(); - while (it.next()) |kv| { - kv.value.destroy(allocator); - } - self.failed_decls.clear(); - } - { - var it = self.failed_files.iterator(); - while (it.next()) |kv| { - kv.value.destroy(allocator); - } - self.failed_files.clear(); - } - { - var it = self.failed_exports.iterator(); - while (it.next()) |kv| { - kv.value.destroy(allocator); - } - self.failed_exports.clear(); - } -} - pub fn getAllErrorsAlloc(self: *Module) !AllErrors { var arena = std.heap.ArenaAllocator.init(self.allocator); errdefer arena.deinit(); @@ -1899,6 +1872,9 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { } self.bin_file.deleteExport(exp.link); + if (self.failed_exports.remove(exp)) |entry| { + entry.value.destroy(self.allocator); + } self.allocator.destroy(exp); } self.allocator.free(kv.value); @@ -2191,6 +2167,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const "exported symbol collision: {}", .{symbol_name}, )); + new_export.status = .failed; } else { self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index bfe4bf7d4820cadb46f73ece4d2b199c19313df3..b324cdf5d5f4f7fc2ceb269811c8d84950afc1c5 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -70,7 +70,7 @@ pub fn addCases(ctx: *TestContext) !void { ); // TODO: need to make sure this works with other variants of export. - ctx.incrementalFailure("function redefinition", linux_x64, + ctx.incrementalFailure("exported symbol collision", linux_x64, \\export fn entry() void {} \\export fn entry() void {} , &[_][]const u8{":2:11: error: redefinition of 'entry'"}, -- 2.54.0 From a728436992415d1bce44b0c63938f6443a4e9a11 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Fri, 26 Jun 2020 19:29:06 -0600 Subject: [PATCH 139/295] new allocator interface after Andrew Kelley review --- lib/std/array_list.zig | 2 - lib/std/heap.zig | 42 ++++++++++---------- lib/std/heap/arena_allocator.zig | 2 +- lib/std/heap/logging_allocator.zig | 2 +- lib/std/mem.zig | 62 ++++++++++++++---------------- lib/std/testing.zig | 2 +- 6 files changed, 51 insertions(+), 61 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 896e5bf9dcec7463d99e30e1437a36203f50e310..b57d051d2b6c1560db53f44a5a1f853d06e034eb 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -220,7 +220,6 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { } const new_memory = try self.allocator.reallocAtLeast(self.allocatedSlice(), better_capacity); - assert(new_memory.len >= better_capacity); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } @@ -443,7 +442,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ } const new_memory = try allocator.reallocAtLeast(self.allocatedSlice(), better_capacity); - assert(new_memory.len >= better_capacity); self.items.ptr = new_memory.ptr; self.capacity = new_memory.len; } diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 322c24934e3385147ee20df6ff5909c19d7ce818..70e0e9d538aa5390429fb10479b6e9e058d184ac 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -25,7 +25,7 @@ usingnamespace if (comptime @hasDecl(c, "malloc_size")) struct { pub const supports_malloc_size = false; }; -pub const c_allocator = mem.getAllocatorPtr(&c_allocator_state); +pub const c_allocator = &c_allocator_state; var c_allocator_state = Allocator{ .allocFn = cAlloc, .resizeFn = cResize, @@ -38,7 +38,7 @@ fn cAlloc(self: *Allocator, len: usize, ptr_align: u29, len_align: u29) Allocato return ptr[0..len]; } const full_len = init: { - if (comptime supports_malloc_size) { + if (supports_malloc_size) { const s = malloc_size(ptr); assert(s >= len); break :init s; @@ -56,24 +56,23 @@ fn cResize(self: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocato if (new_len <= buf.len) { return mem.alignAllocLen(buf.len, new_len, len_align); } - if (comptime supports_malloc_size) { + if (supports_malloc_size) { const full_len = malloc_size(buf.ptr); if (new_len <= full_len) { return mem.alignAllocLen(full_len, new_len, len_align); } } - // TODO: could we still use realloc? are there any cases where we can guarantee that realloc won't move memory? return error.OutOfMemory; } /// This allocator makes a syscall directly for every allocation and free. /// Thread-safe and lock-free. pub const page_allocator = if (std.Target.current.isWasm()) - mem.getAllocatorPtr(&wasm_page_allocator_state) + &wasm_page_allocator_state else if (std.Target.current.os.tag == .freestanding) root.os.heap.page_allocator else - mem.getAllocatorPtr(&page_allocator_state); + &page_allocator_state; var page_allocator_state = Allocator{ .allocFn = PageAllocator.alloc, @@ -507,9 +506,9 @@ pub const FixedBufferAllocator = struct { return sliceContainsSlice(self.buffer, slice); } - // NOTE: this will not work in all cases, if the last allocation had an adjusted_index - // then we won't be able to determine what the last allocation was. This is because - // the alignForward operation done in alloc is not reverisible. + /// NOTE: this will not work in all cases, if the last allocation had an adjusted_index + /// then we won't be able to determine what the last allocation was. This is because + /// the alignForward operation done in alloc is not reverisible. pub fn isLastAllocation(self: *FixedBufferAllocator, buf: []u8) bool { return buf.ptr + buf.len == self.buffer.ptr + self.end_index; } @@ -525,7 +524,7 @@ pub const FixedBufferAllocator = struct { const result = self.buffer[adjusted_index..new_end_index]; self.end_index = new_end_index; - return result[0..mem.alignAllocLen(result.len, n, len_align)]; + return result; } fn resize(allocator: *Allocator, buf: []u8, new_size: usize, len_align: u29) Allocator.Error!usize { @@ -544,13 +543,12 @@ pub const FixedBufferAllocator = struct { return if (new_size == 0) 0 else mem.alignAllocLen(buf.len - sub, new_size, len_align); } - var add = new_size - buf.len; + const add = new_size - buf.len; if (add + self.end_index > self.buffer.len) { - //add = self.buffer.len - self.end_index; return error.OutOfMemory; } self.end_index += add; - return mem.alignAllocLen(buf.len + add, new_size, len_align); + return new_size; } pub fn reset(self: *FixedBufferAllocator) void { @@ -735,7 +733,7 @@ test "ArenaAllocator" { var test_fixed_buffer_allocator_memory: [800000 * @sizeOf(u64)]u8 = undefined; test "FixedBufferAllocator" { - var fixed_buffer_allocator = mem.sanityWrap(FixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..])); + var fixed_buffer_allocator = mem.validationWrap(FixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..])); try testAllocator(&fixed_buffer_allocator.allocator); try testAllocatorAligned(&fixed_buffer_allocator.allocator, 16); @@ -802,8 +800,8 @@ test "ThreadSafeFixedBufferAllocator" { } fn testAllocator(base_allocator: *mem.Allocator) !void { - var sanityAllocator = mem.sanityWrap(base_allocator); - const allocator = &sanityAllocator.allocator; + var validationAllocator = mem.validationWrap(base_allocator); + const allocator = &validationAllocator.allocator; var slice = try allocator.alloc(*i32, 100); testing.expect(slice.len == 100); @@ -833,8 +831,8 @@ fn testAllocator(base_allocator: *mem.Allocator) !void { } fn testAllocatorAligned(base_allocator: *mem.Allocator, comptime alignment: u29) !void { - var sanityAllocator = mem.sanityWrap(base_allocator); - const allocator = &sanityAllocator.allocator; + var validationAllocator = mem.validationWrap(base_allocator); + const allocator = &validationAllocator.allocator; // initial var slice = try allocator.alignedAlloc(u8, alignment, 10); @@ -860,8 +858,8 @@ fn testAllocatorAligned(base_allocator: *mem.Allocator, comptime alignment: u29) } fn testAllocatorLargeAlignment(base_allocator: *mem.Allocator) mem.Allocator.Error!void { - var sanityAllocator = mem.sanityWrap(base_allocator); - const allocator = &sanityAllocator.allocator; + var validationAllocator = mem.validationWrap(base_allocator); + const allocator = &validationAllocator.allocator; //Maybe a platform's page_size is actually the same as or // very near usize? @@ -892,8 +890,8 @@ fn testAllocatorLargeAlignment(base_allocator: *mem.Allocator) mem.Allocator.Err } fn testAllocatorAlignedShrink(base_allocator: *mem.Allocator) mem.Allocator.Error!void { - var sanityAllocator = mem.sanityWrap(base_allocator); - const allocator = &sanityAllocator.allocator; + var validationAllocator = mem.validationWrap(base_allocator); + const allocator = &validationAllocator.allocator; var debug_buffer: [1000]u8 = undefined; const debug_allocator = &FixedBufferAllocator.init(&debug_buffer).allocator; diff --git a/lib/std/heap/arena_allocator.zig b/lib/std/heap/arena_allocator.zig index ee3bfc19d4117603ec32f10828734d8dd16457f3..74ced774ed9be9d3da214c19f221ea217a719396 100644 --- a/lib/std/heap/arena_allocator.zig +++ b/lib/std/heap/arena_allocator.zig @@ -77,7 +77,7 @@ pub const ArenaAllocator = struct { } const result = cur_buf[adjusted_index..new_end_index]; self.state.end_index = new_end_index; - return result[0..mem.alignAllocLen(result.len, n, len_align)]; + return result; } } }; diff --git a/lib/std/heap/logging_allocator.zig b/lib/std/heap/logging_allocator.zig index e465795b8f8982d80f74b1433b7035dcc9136f9c..b521515a79f3239ac319d0ac2b522cf3b4a32657 100644 --- a/lib/std/heap/logging_allocator.zig +++ b/lib/std/heap/logging_allocator.zig @@ -70,7 +70,7 @@ test "LoggingAllocator" { var fbs = std.io.fixedBufferStream(&log_buf); var allocator_buf: [10]u8 = undefined; - var fixedBufferAllocator = std.mem.sanityWrap(std.heap.FixedBufferAllocator.init(&allocator_buf)); + var fixedBufferAllocator = std.mem.validationWrap(std.heap.FixedBufferAllocator.init(&allocator_buf)); const allocator = &loggingAllocator(&fixedBufferAllocator.allocator, fbs.outStream()).allocator; var a = try allocator.alloc(u8, 10); diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 6a7e846f6238838f45c865ff56632036e5ede620..bf1e00005628b91693d9f77ae531668413dbaae3 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -141,6 +141,9 @@ pub const Allocator = struct { const new_mem = try self.callAllocFn(new_len, new_alignment, len_align); @memcpy(new_mem.ptr, old_mem.ptr, std.math.min(new_len, old_mem.len)); // DISABLED TO AVOID BUGS IN TRANSLATE C + // use './zig build test-translate-c' to reproduce, some of the symbols in the + // generated C code will be a sequence of 0xaa (the undefined value), meaning + // it is printing data that has been freed //@memset(old_mem.ptr, undefined, old_mem.len); _ = self.shrinkBytes(old_mem, 0, 0); return new_mem; @@ -214,6 +217,7 @@ pub const Allocator = struct { return self.allocWithOptions(Elem, n, null, sentinel); } + /// Deprecated: use `allocAdvanced` pub fn alignedAlloc( self: *Allocator, comptime T: type, @@ -221,11 +225,11 @@ pub const Allocator = struct { comptime alignment: ?u29, n: usize, ) Error![]align(alignment orelse @alignOf(T)) T { - return self.alignedAlloc2(T, alignment, n, .exact); + return self.allocAdvanced(T, alignment, n, .exact); } - const Exact = enum {exact,atLeast}; - pub fn alignedAlloc2( + const Exact = enum {exact,at_least}; + pub fn allocAdvanced( self: *Allocator, comptime T: type, /// null means naturally aligned @@ -234,7 +238,7 @@ pub const Allocator = struct { exact: Exact, ) Error![]align(alignment orelse @alignOf(T)) T { const a = if (alignment) |a| blk: { - if (a == @alignOf(T)) return alignedAlloc2(self, T, null, n, exact); + if (a == @alignOf(T)) return allocAdvanced(self, T, null, n, exact); break :blk a; } else @alignOf(T); @@ -248,7 +252,10 @@ pub const Allocator = struct { // functions that heap-allocate their own frame with @Frame(func). const sizeOfT = if (alignment == null) @intCast(u29, @divExact(byte_count, n)) else @sizeOf(T); const byte_slice = try self.callAllocFn(byte_count, a, if (exact == .exact) @as(u29, 0) else sizeOfT); - assert(if (exact == .exact) byte_slice.len == byte_count else byte_slice.len >= byte_count); + switch (exact) { + .exact => assert(byte_slice.len == byte_count), + .at_least => assert(byte_slice.len >= byte_count), + } @memset(byte_slice.ptr, undefined, byte_slice.len); if (alignment == null) { // This if block is a workaround (see comment above) @@ -273,7 +280,7 @@ pub const Allocator = struct { break :t Error![]align(Slice.alignment) Slice.child; } { const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment; - return self.alignedRealloc2(old_mem, old_alignment, new_n, .exact); + return self.reallocAdvanced(old_mem, old_alignment, new_n, .exact); } pub fn reallocAtLeast(self: *Allocator, old_mem: var, new_n: usize) t: { @@ -281,25 +288,23 @@ pub const Allocator = struct { break :t Error![]align(Slice.alignment) Slice.child; } { const old_alignment = @typeInfo(@TypeOf(old_mem)).Pointer.alignment; - return self.alignedRealloc2(old_mem, old_alignment, new_n, .atLeast); + return self.reallocAdvanced(old_mem, old_alignment, new_n, .at_least); } - /// This is the same as `realloc`, except caller may additionally request - /// a new alignment, which can be larger, smaller, or the same as the old - /// allocation. + // Deprecated: use `reallocAdvanced` pub fn alignedRealloc( self: *Allocator, old_mem: var, comptime new_alignment: u29, new_n: usize, ) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child { - return self.alignedRealloc2(old_mem, new_alignment, new_n, .exact); + return self.reallocAdvanced(old_mem, new_alignment, new_n, .exact); } /// This is the same as `realloc`, except caller may additionally request /// a new alignment, which can be larger, smaller, or the same as the old /// allocation. - pub fn alignedRealloc2( + pub fn reallocAdvanced( self: *Allocator, old_mem: var, comptime new_alignment: u29, @@ -309,7 +314,7 @@ pub const Allocator = struct { const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; const T = Slice.child; if (old_mem.len == 0) { - return self.alignedAlloc2(T, new_alignment, new_n, exact); + return self.allocAdvanced(T, new_alignment, new_n, exact); } if (new_n == 0) { self.free(old_mem); @@ -392,24 +397,9 @@ pub const Allocator = struct { } }; -/// Given a pointer to an allocator, return the *Allocator for it. `allocatorStatePtr` can -/// either be a `*Allocator`, in which case it is returned as-is, otherwise, the address of -/// the `allocator` field is returned. -pub fn getAllocatorPtr(allocatorStatePtr: var) *Allocator { - // allocator must be a pointer or else this function will return a copy of the allocator which - // is not what this is for - const T = @TypeOf(allocatorStatePtr); - switch (@typeInfo(T)) { - .Pointer => {}, - else => @compileError("getAllocatorPtr expects a pointer to an allocator but got: " ++ @typeName(T)), - } - if (T == *Allocator) - return allocatorStatePtr; - return &allocatorStatePtr.allocator; -} - -/// Detects and asserts if the std.mem.Allocator interface is violated -pub fn SanityAllocator(comptime T: type) type { return struct { +/// Detects and asserts if the std.mem.Allocator interface is violated by the caller +/// or the allocator. +pub fn ValidationAllocator(comptime T: type) type { return struct { const Self = @This(); allocator: Allocator, underlying_allocator: T, @@ -424,7 +414,8 @@ pub fn SanityAllocator(comptime T: type) type { return struct { } fn getUnderlyingAllocatorPtr(self: *@This()) *Allocator { if (T == *Allocator) return self.underlying_allocator; - return getAllocatorPtr(&self.underlying_allocator); + if (*T == *Allocator) return &self.underlying_allocator; + return &self.underlying_allocator.allocator; } pub fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 { assert(n > 0); @@ -436,6 +427,7 @@ pub fn SanityAllocator(comptime T: type) type { return struct { const self = @fieldParentPtr(@This(), "allocator", allocator); const result = try self.getUnderlyingAllocatorPtr().callAllocFn(n, ptr_align, len_align); + assert(mem.isAligned(@ptrToInt(result.ptr), ptr_align)); if (len_align == 0) { assert(result.len == n); } else { @@ -467,8 +459,8 @@ pub fn SanityAllocator(comptime T: type) type { return struct { }; };} -pub fn sanityWrap(allocator: var) SanityAllocator(@TypeOf(allocator)) { - return SanityAllocator(@TypeOf(allocator)).init(allocator); +pub fn validationWrap(allocator: var) ValidationAllocator(@TypeOf(allocator)) { + return ValidationAllocator(@TypeOf(allocator)).init(allocator); } /// An allocator helper function. Adjusts an allocation length satisfy `len_align`. @@ -2377,6 +2369,8 @@ test "alignForward" { testing.expect(alignForward(17, 8) == 24); } +/// Round an address up to the previous aligned address +/// Unlike `alignBackward`, `alignment` can be any positive number, not just a power of 2. pub fn alignBackwardAnyAlign(i: usize, alignment: usize) usize { if (@popCount(usize, alignment) == 1) return alignBackward(i, alignment); diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 0f811cada8e0d292eda030c535c8c8a3f75a7e18..670d8fd5d6acb98322e54823a050cc59a269e1e7 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -11,7 +11,7 @@ pub var allocator_instance = LeakCountAllocator.init(&base_allocator_instance.al pub const failing_allocator = &failing_allocator_instance.allocator; pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_instance.allocator, 0); -pub var base_allocator_instance = std.mem.sanityWrap(std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..])); +pub var base_allocator_instance = std.mem.validationWrap(std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..])); var allocator_mem: [2 * 1024 * 1024]u8 = undefined; /// This function is intended to be used only in tests. It prints diagnostics to stderr -- 2.54.0 From 54148a8c888f47361c141f0635671e1ed12ddba4 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sat, 27 Jun 2020 20:01:20 -0400 Subject: [PATCH 140/295] Stage2/TestHarness: Improve progress reporting --- src-self-hosted/test.zig | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index af9ffef5096b69085fc052ffbf48f11789bcc843..248792b42c5295de4c1192e99faae333bcd2af23 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -391,8 +391,10 @@ pub const TestContext = struct { prg_node.activate(); defer prg_node.end(); - // So that we can see which test case failed when the leak checker goes off. - progress.refresh(); + // So that we can see which test case failed when the leak checker goes off, + // or there's an internal error + progress.initial_delay_ns = 0; + progress.refresh_rate_ns = 0; const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.target); try self.runOneCase(std.testing.allocator, &prg_node, case, info.target); @@ -408,10 +410,6 @@ pub const TestContext = struct { const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); - var prg_node = root_node.start(case.name, case.updates.items.len); - prg_node.activate(); - defer prg_node.end(); - const bin_name = try std.zig.binNameAlloc(allocator, "test_case", target, case.output_mode, null); defer allocator.free(bin_name); @@ -434,7 +432,7 @@ pub const TestContext = struct { defer module.deinit(); for (case.updates.items) |update, update_index| { - var update_node = prg_node.start("update", 4); + var update_node = root_node.start("update", 3); update_node.activate(); defer update_node.end(); @@ -451,6 +449,7 @@ pub const TestContext = struct { switch (update.case) { .Transformation => |expected_output| { + update_node.estimated_total_items = 5; var emit_node = update_node.start("emit", null); emit_node.activate(); var new_zir_module = try zir.emit(allocator, module); @@ -464,9 +463,15 @@ pub const TestContext = struct { try new_zir_module.writeToStream(allocator, out_zir.outStream()); write_node.end(); + var test_node = update_node.start("assert", null); + test_node.activate(); std.testing.expectEqualSlices(u8, expected_output, out_zir.items); + test_node.end(); }, .Error => |e| { + var test_node = update_node.start("assert", null); + test_node.activate(); + defer test_node.end(); var handled_errors = try allocator.alloc(bool, e.len); defer allocator.free(handled_errors); for (handled_errors) |*h| { @@ -495,6 +500,7 @@ pub const TestContext = struct { } }, .Execution => |expected_stdout| { + update_node.estimated_total_items = 4; var exec_result = x: { var exec_node = update_node.start("execute", null); exec_node.activate(); @@ -511,6 +517,10 @@ pub const TestContext = struct { .cwd_dir = tmp.dir, }); }; + var test_node = update_node.start("test", null); + test_node.activate(); + defer test_node.end(); + defer allocator.free(exec_result.stdout); defer allocator.free(exec_result.stderr); switch (exec_result.term) { -- 2.54.0 From 97c41e71521a66e3e117bc23cf51d047b119eb9a Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sat, 27 Jun 2020 21:10:44 -0400 Subject: [PATCH 141/295] Disable test --- src-self-hosted/Module.zig | 8 +------- test/stage2/compile_errors.zig | 37 +++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index b15f1d88226e23bd473c1e6781913b04fba892f8..e63dc51eec7e83c4a858c569a678f9d42c8e8504 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1696,13 +1696,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { if (src_decl.cast(ast.Node.FnProto)) |fn_proto| { // We will create a Decl for it regardless of analysis status. const name_tok = fn_proto.name_token orelse { - const err_msg = try ErrorMsg.create(self.allocator, tree.token_locs[fn_proto.firstToken()].end, "missing function name", .{}); - // TODO: cache a single invalid decl in the Module? - const new_decl = try self.createNewDecl(&root_scope.base, "", decl_i, [1]u8{0} ** 16, [1]u8{0} ** 16); - root_scope.decls.appendAssumeCapacity(new_decl); - errdefer err_msg.destroy(self.allocator); - try self.failed_decls.putNoClobber(new_decl, err_msg); - continue; + @panic("TODO missing function name"); }; const name_loc = tree.token_locs[name_tok]; diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig index b324cdf5d5f4f7fc2ceb269811c8d84950afc1c5..45e60c074198a6af8c94b24f5d166a528a456f5d 100644 --- a/test/stage2/compile_errors.zig +++ b/test/stage2/compile_errors.zig @@ -62,26 +62,31 @@ pub fn addCases(ctx: *TestContext) !void { \\@1 = export(@0, "start") ); - ctx.incrementalFailure("function redefinition", linux_x64, + ctx.compileError("function redefinition", linux_x64, \\fn entry() void {} \\fn entry() void {} - , &[_][]const u8{":2:4: error: redefinition of 'entry'"}, - \\fn entry() void {} - ); + , &[_][]const u8{":2:4: error: redefinition of 'entry'"}); + + //ctx.incrementalFailure("function redefinition", linux_x64, + // \\fn entry() void {} + // \\fn entry() void {} + //, &[_][]const u8{":2:4: error: redefinition of 'entry'"}, + // \\fn entry() void {} + //); - // TODO: need to make sure this works with other variants of export. - ctx.incrementalFailure("exported symbol collision", linux_x64, - \\export fn entry() void {} - \\export fn entry() void {} - , &[_][]const u8{":2:11: error: redefinition of 'entry'"}, - \\export fn entry() void {} - ); + //// TODO: need to make sure this works with other variants of export. + //ctx.incrementalFailure("exported symbol collision", linux_x64, + // \\export fn entry() void {} + // \\export fn entry() void {} + //, &[_][]const u8{":2:11: error: redefinition of 'entry'"}, + // \\export fn entry() void {} + //); - ctx.incrementalFailure("missing function name", linux_x64, - \\fn() void {} - , &[_][]const u8{":1:3: error: missing function name"}, - \\fn a() void {} - ); + // ctx.incrementalFailure("missing function name", linux_x64, + // \\fn() void {} + // , &[_][]const u8{":1:3: error: missing function name"}, + // \\fn a() void {} + // ); // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 -- 2.54.0 From 1861c25142650c71a99e259367ae2fd9ea601c1b Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sat, 27 Jun 2020 21:15:07 -0400 Subject: [PATCH 142/295] Improve Tranform failure output --- src-self-hosted/test.zig | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 248792b42c5295de4c1192e99faae333bcd2af23..398d988eeb06f1fa62d7b039feeefea509f465e5 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -465,8 +465,19 @@ pub const TestContext = struct { var test_node = update_node.start("assert", null); test_node.activate(); - std.testing.expectEqualSlices(u8, expected_output, out_zir.items); - test_node.end(); + defer test_node.end(); + if (expected_output.len != out_zir.items.len) { + std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); + std.process.exit(1); + } + for (expected_output) |e, i| { + if (out_zir.items[i] != e) { + if (expected_output.len != out_zir.items.len) { + std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); + std.process.exit(1); + } + } + } }, .Error => |e| { var test_node = update_node.start("assert", null); -- 2.54.0 From 38d2c5cdf1bc4b9a890a8da80baef32db5e2eb23 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sat, 27 Jun 2020 21:39:04 -0400 Subject: [PATCH 143/295] Rename type -> extension --- src-self-hosted/test.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 398d988eeb06f1fa62d7b039feeefea509f465e5..38392b8aa741df56efe16e3d7998f5cece071d19 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -67,7 +67,7 @@ pub const TestContext = struct { /// to Executable. output_mode: std.builtin.OutputMode, updates: std.ArrayList(Update), - @"type": TestType, + extension: TestType, /// Adds a subcase in which the module is updated with `src`, and the /// resulting ZIR is validated against `result`. @@ -146,7 +146,7 @@ pub const TestContext = struct { .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Exe, - .@"type" = T, + .extension = T, }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } @@ -172,7 +172,7 @@ pub const TestContext = struct { .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, - .@"type" = T, + .extension = T, }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } @@ -406,7 +406,7 @@ pub const TestContext = struct { var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); - const tmp_src_path = if (case.type == .Zig) "test_case.zig" else if (case.type == .ZIR) "test_case.zir" else unreachable; + const tmp_src_path = if (case.extension == .Zig) "test_case.zig" else if (case.extension == .ZIR) "test_case.zir" else unreachable; const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path); defer root_pkg.destroy(); -- 2.54.0 From ffca1569d12d4e8ce83418bb87e358e6ec9ac0bc Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sat, 27 Jun 2020 21:39:39 -0400 Subject: [PATCH 144/295] Return instead of branch --- src-self-hosted/Module.zig | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index e63dc51eec7e83c4a858c569a678f9d42c8e8504..a59aacc226dc924a28e5f6302679b975e5ac6532 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2162,21 +2162,21 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const .{symbol_name}, )); new_export.status = .failed; - } else { - self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); - self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( - self.allocator, - src, - "unable to export: {}", - .{@errorName(err)}, - )); - new_export.status = .failed_retryable; - }, - }; + return; } + self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); + self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( + self.allocator, + src, + "unable to export: {}", + .{@errorName(err)}, + )); + new_export.status = .failed_retryable; + }, + }; } fn addNewInstArgs( -- 2.54.0 From 80b70470c016ad0d1db47d0edc4d48f1f75af258 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Sat, 27 Jun 2020 21:50:59 -0400 Subject: [PATCH 145/295] Stage2/Module: Add symbol -> export lookup table --- src-self-hosted/Module.zig | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index a59aacc226dc924a28e5f6302679b975e5ac6532..ffaff9bc9d3c75f2691939cd3816e7e3961a12d8 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -32,6 +32,9 @@ bin_file_path: []const u8, /// Decl pointers to details about them being exported. /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. decl_exports: std.AutoHashMap(*Decl, []*Export), +/// We track which export is associated with the given symbol name for quick +/// detection of symbol collisions. +symbol_exports: std.StringHashMap(*Export), /// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that /// is performing the export of another Decl. @@ -772,6 +775,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .optimize_mode = options.optimize_mode, .decl_table = DeclTable.init(gpa), .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa), + .symbol_exports = std.StringHashMap(*Export).init(gpa), .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa), .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa), .failed_files = std.AutoHashMap(*Scope, *ErrorMsg).init(gpa), @@ -829,6 +833,7 @@ pub fn deinit(self: *Module) void { } self.export_owners.deinit(); } + self.symbol_exports.deinit(); self.root_scope.destroy(allocator); self.* = undefined; } @@ -1869,6 +1874,7 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { if (self.failed_exports.remove(exp)) |entry| { entry.value.destroy(self.allocator); } + _ = self.symbol_exports.remove(exp.options.name); self.allocator.destroy(exp); } self.allocator.free(kv.value); @@ -2104,20 +2110,6 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), } - var already_exported = false; - { - var it = self.decl_exports.iterator(); - while (it.next()) |kv| { - const export_list = kv.value; - for (export_list) |e| { - if (std.mem.eql(u8, e.options.name, symbol_name)) { - already_exported = true; - break; - } - } - } - } - try self.decl_exports.ensureCapacity(self.decl_exports.size + 1); try self.export_owners.ensureCapacity(self.export_owners.size + 1); @@ -2153,7 +2145,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const de_gop.kv.value[de_gop.kv.value.len - 1] = new_export; errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1); - if (already_exported) { + if (self.symbol_exports.get(symbol_name)) |_| { try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( self.allocator, @@ -2161,9 +2153,12 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const "exported symbol collision: {}", .{symbol_name}, )); + // TODO: add a note new_export.status = .failed; return; } + + try self.symbol_exports.putNoClobber(symbol_name, new_export); self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { -- 2.54.0 From 374e3e42e0de10d21406c077599cfc4a6a813497 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sat, 27 Jun 2020 22:19:15 -0600 Subject: [PATCH 146/295] WasmPageAllocator: fix bug not aligning allocations --- lib/std/heap.zig | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 70e0e9d538aa5390429fb10479b6e9e058d184ac..260841ad2d50ff9c10cf76cbccee0fd53b8f9332 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -285,7 +285,7 @@ const WasmPageAllocator = struct { // Revisit if this is settled: https://github.com/ziglang/zig/issues/3806 const not_found = std.math.maxInt(usize); - fn useRecycled(self: FreeBlock, num_pages: usize) usize { + fn useRecycled(self: FreeBlock, num_pages: usize, alignment: u29) usize { @setCold(true); for (self.data) |segment, i| { const spills_into_next = @bitCast(i128, segment) < 0; @@ -298,7 +298,8 @@ const WasmPageAllocator = struct { var count: usize = 0; while (j + count < self.totalPages() and self.getBit(j + count) == .free) { count += 1; - if (count >= num_pages) { + const addr = j * mem.page_size; + if (count >= num_pages and mem.isAligned(addr, alignment)) { self.setBits(j, num_pages, .used); return j; } @@ -329,29 +330,36 @@ const WasmPageAllocator = struct { fn alloc(allocator: *Allocator, len: usize, alignment: u29, len_align: u29) error{OutOfMemory}![]u8 { const page_count = nPages(len); - const page_idx = try allocPages(page_count); + const page_idx = try allocPages(page_count, alignment); return @intToPtr([*]u8, page_idx * mem.page_size) [0..alignPageAllocLen(page_count * mem.page_size, len, len_align)]; } - fn allocPages(page_count: usize) !usize { + fn allocPages(page_count: usize, alignment: u29) !usize { { - const idx = conventional.useRecycled(page_count); + const idx = conventional.useRecycled(page_count, alignment); if (idx != FreeBlock.not_found) { return idx; } } - const idx = extended.useRecycled(page_count); + const idx = extended.useRecycled(page_count, alignment); if (idx != FreeBlock.not_found) { return idx + extendedOffset(); } - const prev_page_count = @wasmMemoryGrow(0, @intCast(u32, page_count)); - if (prev_page_count <= 0) { + const next_page_idx = @wasmMemorySize(0); + const next_page_addr = next_page_idx * mem.page_size; + const aligned_addr = mem.alignForward(next_page_addr, alignment); + const drop_page_count = @divExact(aligned_addr - next_page_addr, mem.page_size); + const result = @wasmMemoryGrow(0, @intCast(u32, drop_page_count + page_count)); + if (result <= 0) return error.OutOfMemory; + assert(result == next_page_idx); + const aligned_page_idx = next_page_idx + drop_page_count; + if (drop_page_count > 0) { + freePages(next_page_idx, aligned_page_idx); } - - return @intCast(usize, prev_page_count); + return @intCast(usize, aligned_page_idx); } fn freePages(start: usize, end: usize) void { -- 2.54.0 From 74c245aea94ebcf05651a6f3420d8c3a7145f9d7 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sun, 28 Jun 2020 13:56:00 -0700 Subject: [PATCH 147/295] Disable wasi 'readFileAlloc on a directory' assertion for now --- lib/std/fs/test.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 87477549d953159f399fcea170b9bd926f2e13f6..3991ddd8a327b9d2a5b23c7345fc4aadb69d6762 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -83,8 +83,13 @@ test "file operations on directories" { testing.expectError(error.IsDir, tmp_dir.dir.createFile(test_dir_name, .{})); testing.expectError(error.IsDir, tmp_dir.dir.deleteFile(test_dir_name)); - testing.expectError(error.IsDir, tmp_dir.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize))); - // note: the `.write = true` is necessary to ensure the error occurs on all platforms + // Currently, WASI will return error.Unexpected (via ENOTCAPABLE) when attempting fd_read on a directory handle. + // TODO: Re-enable on WASI once https://github.com/bytecodealliance/wasmtime/issues/1935 is resolved. + if (builtin.os.tag != .wasi) { + testing.expectError(error.IsDir, tmp_dir.dir.readFileAlloc(testing.allocator, test_dir_name, std.math.maxInt(usize))); + } + // Note: The `.write = true` is necessary to ensure the error occurs on all platforms. + // TODO: Add a read-only test as well, see https://github.com/ziglang/zig/issues/5732 testing.expectError(error.IsDir, tmp_dir.dir.openFile(test_dir_name, .{ .write = true })); if (builtin.os.tag != .wasi) { -- 2.54.0 From c2eead9629b60a394aa61e6f96b89647eddce1ea Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sun, 28 Jun 2020 14:33:41 -0600 Subject: [PATCH 148/295] Fix issue 5741, use after free --- lib/std/heap.zig | 5 +++++ lib/std/mem.zig | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 260841ad2d50ff9c10cf76cbccee0fd53b8f9332..ea9e95c67569c164d78c6bca6952e1c59de9f3e4 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -714,6 +714,11 @@ test "PageAllocator" { slice[127] = 0x34; allocator.free(slice); } + { + var buf = try allocator.alloc(u8, mem.page_size + 1); + defer allocator.free(buf); + buf = try allocator.realloc(buf, 1); // shrink past the page boundary + } } test "HeapAllocator" { diff --git a/lib/std/mem.zig b/lib/std/mem.zig index bf1e00005628b91693d9f77ae531668413dbaae3..6bde83f782cb405dd913dd2fac602588cbbab8a8 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -116,9 +116,6 @@ pub const Allocator = struct { if (isAligned(@ptrToInt(old_mem.ptr), new_alignment)) { if (new_byte_count <= old_mem.len) { const shrunk_len = self.shrinkBytes(old_mem, new_byte_count, len_align); - if (shrunk_len < old_mem.len) { - @memset(old_mem.ptr + shrunk_len, undefined, old_mem.len - shrunk_len); - } return old_mem.ptr[0..shrunk_len]; } if (self.callResizeFn(old_mem, new_byte_count, len_align)) |resized_len| { -- 2.54.0 From e120b07a524f1accd4975f5206018c61c4be9345 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sat, 27 Jun 2020 20:36:56 -0600 Subject: [PATCH 149/295] arena_allocator: refactor and use full capacity --- lib/std/heap/arena_allocator.zig | 48 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/lib/std/heap/arena_allocator.zig b/lib/std/heap/arena_allocator.zig index 74ced774ed9be9d3da214c19f221ea217a719396..31fbad57f94cbf551068022f527011fac20e72f4 100644 --- a/lib/std/heap/arena_allocator.zig +++ b/lib/std/heap/arena_allocator.zig @@ -15,6 +15,7 @@ pub const ArenaAllocator = struct { /// as a memory-saving optimization. pub const State = struct { buffer_list: std.SinglyLinkedList([]u8) = @as(std.SinglyLinkedList([]u8), .{}), + /// The first available index in the front buffer of `buffer_list` end_index: usize = 0, pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator { @@ -45,39 +46,36 @@ pub const ArenaAllocator = struct { } } - fn createNode(self: *ArenaAllocator, prev_len: usize, minimum_size: usize) !*BufNode { - const actual_min_size = minimum_size + (@sizeOf(BufNode) + 16); - const big_enough_len = prev_len + actual_min_size; - const len = big_enough_len + big_enough_len / 2; - const buf = try self.child_allocator.alignedAlloc(u8, @alignOf(BufNode), len); - const buf_node_slice = mem.bytesAsSlice(BufNode, buf[0..@sizeOf(BufNode)]); - const buf_node = &buf_node_slice[0]; - buf_node.* = BufNode{ - .data = buf, - .next = null, - }; + fn getBufNodeAddr(buf: []u8) usize { + return mem.alignBackward(@ptrToInt(buf.ptr) + buf.len - @sizeOf(BufNode), @alignOf(BufNode)); + } + + fn allocBuf(self: *ArenaAllocator, len: usize, ptr_align: u29) ![]u8 { + const alloc_len = len + @sizeOf(BufNode) + @alignOf(BufNode) - 1; + const buf = try self.child_allocator.callAllocFn(alloc_len, ptr_align, 1); + const buf_node = @intToPtr(*BufNode, getBufNodeAddr(buf)); + buf_node.* = .{ .data = buf, .next = null }; + assert(@ptrToInt(buf_node) - @ptrToInt(buf.ptr) >= len); self.state.buffer_list.prepend(buf_node); - self.state.end_index = 0; - return buf_node; + self.state.end_index = len; + return buf[0..len]; } fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 { const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator); - var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + ptr_align); - while (true) { - const cur_buf = cur_node.data[@sizeOf(BufNode)..]; + if (self.state.buffer_list.first) |node_full_buf| { + assert(self.state.end_index > 0); + const cur_buf = node_full_buf.data[0.. + getBufNodeAddr(node_full_buf.data) - @ptrToInt(node_full_buf.data.ptr)]; const addr = @ptrToInt(cur_buf.ptr) + self.state.end_index; - const adjusted_addr = mem.alignForward(addr, ptr_align); - const adjusted_index = self.state.end_index + (adjusted_addr - addr); - const new_end_index = adjusted_index + n; - if (new_end_index > cur_buf.len) { - cur_node = try self.createNode(cur_buf.len, n + ptr_align); - continue; + const aligned_index = self.state.end_index + (mem.alignForward(addr, ptr_align) - addr); + const aligned_end_index = aligned_index + n; + if (aligned_end_index <= cur_buf.len) { + self.state.end_index = aligned_end_index; + return cur_buf[aligned_index..aligned_end_index]; } - const result = cur_buf[adjusted_index..new_end_index]; - self.state.end_index = new_end_index; - return result; } + return try self.allocBuf(n, ptr_align); } }; -- 2.54.0 From aa92446365b97e167c4162122c66a02b4cbca031 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 28 Jun 2020 19:39:54 -0400 Subject: [PATCH 150/295] stage2: implement function parameters In codegen.zig, the std.Target.Cpu.Arch is now generally available as a comptime value where needed. This is a tradeoff that causes the compiler binary to be more bloated, but gives us higher performance, since the optimizer can optimize per architecture (which is usually how compilers are designed anyway, with different code per-architecture), and it also allows us to use per-architecture types, such as a Register enum that is specific to the comptime-known architecture. Adds abiSize method to Type. --- src-self-hosted/codegen.zig | 297 ++++++++++++++++++----------- src-self-hosted/codegen/x86_64.zig | 30 ++- src-self-hosted/type.zig | 70 +++++++ 3 files changed, 286 insertions(+), 111 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 686edda373e6ea310ef72fb1763842453f168e46..ccd0da8ae009859b91cd8f56d68a7ab9c1439e7d 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -37,6 +37,69 @@ pub fn generateSymbol( .Fn => { const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; + const fn_type = module_fn.owner_decl.typed_value.most_recent.typed_value.ty; + const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen()); + defer bin_file.allocator.free(param_types); + fn_type.fnParamTypes(param_types); + // A parameter may be broken into multiple machine code parameters, so we don't + // know the size up front. + var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len); + defer mc_args.deinit(); + + var next_stack_offset: u64 = 0; + + switch (fn_type.fnCallingConvention()) { + .Naked => assert(mc_args.items.len == 0), + .Unspecified, .C => { + // Prepare the function parameters + switch (bin_file.options.target.cpu.arch) { + .x86_64 => { + const integer_registers = [_]Reg(.x86_64){.rdi, .rsi, .rdx, .rcx, .r8, .r9}; + var next_int_reg: usize = 0; + + for (param_types) |param_type, src_i| { + switch (param_type.zigTypeTag()) { + .Bool, .Int => { + if (next_int_reg >= integer_registers.len) { + try mc_args.append(.{ .stack_offset = next_stack_offset }); + next_stack_offset += param_type.abiSize(bin_file.options.target); + } else { + try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg])}); + next_int_reg += 1; + } + }, + else => return Result{ + .fail = try ErrorMsg.create( + bin_file.allocator, + src, + "TODO implement function parameters of type {}", + .{@tagName(param_type.zigTypeTag())}, + ), + } + } + } + + }, + else => return Result{ + .fail = try ErrorMsg.create( + bin_file.allocator, + src, + "TODO implement function parameters for {}", + .{bin_file.options.target.cpu.arch}, + ), + }, + } + }, + else => return Result{ + .fail = try ErrorMsg.create( + bin_file.allocator, + src, + "TODO implement {} calling convention", + .{fn_type.fnCallingConvention()}, + ), + }, + } + var function = Function{ .target = &bin_file.options.target, .bin_file = bin_file, @@ -44,16 +107,14 @@ pub fn generateSymbol( .code = code, .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(bin_file.allocator), .err_msg = null, + .args = mc_args.items, }; defer function.inst_table.deinit(); - for (module_fn.analysis.success.instructions) |inst| { - const new_inst = function.genFuncInst(inst) catch |err| switch (err) { - error.CodegenFail => return Result{ .fail = function.err_msg.? }, - else => |e| return e, - }; - try function.inst_table.putNoClobber(inst, new_inst); - } + function.gen() catch |err| switch (err) { + error.CodegenFail => return Result{ .fail = function.err_msg.? }, + else => |e| return e, + }; if (function.err_msg) |em| { return Result{ .fail = em }; @@ -157,6 +218,7 @@ const Function = struct { code: *std.ArrayList(u8), inst_table: std.AutoHashMap(*ir.Inst, MCValue), err_msg: ?*ErrorMsg, + args: []MCValue, const MCValue = union(enum) { none, @@ -170,44 +232,119 @@ const Function = struct { register: usize, /// The value is in memory at a hard-coded address. memory: u64, + /// The value is one of the stack variables. + stack_offset: u64, }; - fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { + fn gen(self: *Function) !void { + switch (self.target.cpu.arch) { + .arm => return self.genArch(.arm), + .armeb => return self.genArch(.armeb), + .aarch64 => return self.genArch(.aarch64), + .aarch64_be => return self.genArch(.aarch64_be), + .aarch64_32 => return self.genArch(.aarch64_32), + .arc => return self.genArch(.arc), + .avr => return self.genArch(.avr), + .bpfel => return self.genArch(.bpfel), + .bpfeb => return self.genArch(.bpfeb), + .hexagon => return self.genArch(.hexagon), + .mips => return self.genArch(.mips), + .mipsel => return self.genArch(.mipsel), + .mips64 => return self.genArch(.mips64), + .mips64el => return self.genArch(.mips64el), + .msp430 => return self.genArch(.msp430), + .powerpc => return self.genArch(.powerpc), + .powerpc64 => return self.genArch(.powerpc64), + .powerpc64le => return self.genArch(.powerpc64le), + .r600 => return self.genArch(.r600), + .amdgcn => return self.genArch(.amdgcn), + .riscv32 => return self.genArch(.riscv32), + .riscv64 => return self.genArch(.riscv64), + .sparc => return self.genArch(.sparc), + .sparcv9 => return self.genArch(.sparcv9), + .sparcel => return self.genArch(.sparcel), + .s390x => return self.genArch(.s390x), + .tce => return self.genArch(.tce), + .tcele => return self.genArch(.tcele), + .thumb => return self.genArch(.thumb), + .thumbeb => return self.genArch(.thumbeb), + .i386 => return self.genArch(.i386), + .x86_64 => return self.genArch(.x86_64), + .xcore => return self.genArch(.xcore), + .nvptx => return self.genArch(.nvptx), + .nvptx64 => return self.genArch(.nvptx64), + .le32 => return self.genArch(.le32), + .le64 => return self.genArch(.le64), + .amdil => return self.genArch(.amdil), + .amdil64 => return self.genArch(.amdil64), + .hsail => return self.genArch(.hsail), + .hsail64 => return self.genArch(.hsail64), + .spir => return self.genArch(.spir), + .spir64 => return self.genArch(.spir64), + .kalimba => return self.genArch(.kalimba), + .shave => return self.genArch(.shave), + .lanai => return self.genArch(.lanai), + .wasm32 => return self.genArch(.wasm32), + .wasm64 => return self.genArch(.wasm64), + .renderscript32 => return self.genArch(.renderscript32), + .renderscript64 => return self.genArch(.renderscript64), + .ve => return self.genArch(.ve), + } + } + + fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void { + for (self.mod_fn.analysis.success.instructions) |inst| { + const new_inst = try self.genFuncInst(inst, arch); + try self.inst_table.putNoClobber(inst, new_inst); + } + } + + fn genFuncInst(self: *Function, inst: *ir.Inst, comptime arch: std.Target.Cpu.Arch) !MCValue { switch (inst.tag) { - .add => return self.genAdd(inst.cast(ir.Inst.Add).?), - .arg => return self.genArg(inst.src), - .block => return self.genBlock(inst.cast(ir.Inst.Block).?), - .breakpoint => return self.genBreakpoint(inst.src), - .call => return self.genCall(inst.cast(ir.Inst.Call).?), + .add => return self.genAdd(inst.cast(ir.Inst.Add).?, arch), + .arg => return self.genArg(inst.cast(ir.Inst.Arg).?), + .block => return self.genBlock(inst.cast(ir.Inst.Block).?, arch), + .breakpoint => return self.genBreakpoint(inst.src, arch), + .call => return self.genCall(inst.cast(ir.Inst.Call).?, arch), .unreach => return MCValue{ .unreach = {} }, .constant => unreachable, // excluded from function bodies - .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), + .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?, arch), .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), - .ret => return self.genRet(inst.cast(ir.Inst.Ret).?), - .retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?), - .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?), - .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?), - .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?), - .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?), + .ret => return self.genRet(inst.cast(ir.Inst.Ret).?, arch), + .retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?, arch), + .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?, arch), + .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?, arch), + .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?, arch), + .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?, arch), } } - fn genAdd(self: *Function, inst: *ir.Inst.Add) !MCValue { - switch (self.target.cpu.arch) { + fn genAdd(self: *Function, inst: *ir.Inst.Add, comptime arch: std.Target.Cpu.Arch) !MCValue { + const lhs = try self.resolveInst(inst.args.lhs); + const rhs = try self.resolveInst(inst.args.rhs); + switch (arch) { + .i386, .x86_64 => { + // const lhs_reg = try self.instAsReg(lhs); + // const rhs_reg = try self.instAsReg(rhs); + // const result = try self.allocateReg(); + + // try self.code.append(??); + + // lhs_reg.release(); + // rhs_reg.release(); + return self.fail(inst.base.src, "TODO implement register allocation", .{}); + }, else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), } } - fn genArg(self: *Function, src: usize) !MCValue { - switch (self.target.cpu.arch) { - else => return self.fail(src, "TODO implement function parameters for {}", .{self.target.cpu.arch}), - } - return .none; + fn genArg(self: *Function, inst: *ir.Inst.Arg) !MCValue { + return self.args[inst.args.index]; } - fn genBreakpoint(self: *Function, src: usize) !MCValue { - switch (self.target.cpu.arch) { + fn genBreakpoint(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch) !MCValue { + switch (arch) { .i386, .x86_64 => { try self.code.append(0xcc); // int3 }, @@ -216,8 +353,8 @@ const Function = struct { return .none; } - fn genCall(self: *Function, inst: *ir.Inst.Call) !MCValue { - switch (self.target.cpu.arch) { + fn genCall(self: *Function, inst: *ir.Inst.Call, comptime arch: std.Target.Cpu.Arch) !MCValue { + switch (arch) { .x86_64, .i386 => { if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| { if (inst.args.args.len != 0) { @@ -251,11 +388,11 @@ const Function = struct { } } - fn ret(self: *Function, src: usize, mcv: MCValue) !MCValue { + fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue { if (mcv != .none) { return self.fail(src, "TODO implement return with non-void operand", .{}); } - switch (self.target.cpu.arch) { + switch (arch) { .i386, .x86_64 => { try self.code.append(0xc3); // ret }, @@ -264,43 +401,43 @@ const Function = struct { return .unreach; } - fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue { + fn genRet(self: *Function, inst: *ir.Inst.Ret, comptime arch: std.Target.Cpu.Arch) !MCValue { const operand = try self.resolveInst(inst.args.operand); - return self.ret(inst.base.src, operand); + return self.ret(inst.base.src, arch, operand); } - fn genRetVoid(self: *Function, inst: *ir.Inst.RetVoid) !MCValue { - return self.ret(inst.base.src, .none); + fn genRetVoid(self: *Function, inst: *ir.Inst.RetVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { + return self.ret(inst.base.src, arch, .none); } - fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue { - switch (self.target.cpu.arch) { + fn genCmp(self: *Function, inst: *ir.Inst.Cmp, comptime arch: std.Target.Cpu.Arch) !MCValue { + switch (arch) { else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), } } - fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue { - switch (self.target.cpu.arch) { + fn genCondBr(self: *Function, inst: *ir.Inst.CondBr, comptime arch: std.Target.Cpu.Arch) !MCValue { + switch (arch) { else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}), } } - fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue { - switch (self.target.cpu.arch) { + fn genIsNull(self: *Function, inst: *ir.Inst.IsNull, comptime arch: std.Target.Cpu.Arch) !MCValue { + switch (arch) { else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.target.cpu.arch}), } } - fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue { + fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull, comptime arch: std.Target.Cpu.Arch) !MCValue { // Here you can specialize this instruction if it makes sense to, otherwise the default // will call genIsNull and invert the result. - switch (self.target.cpu.arch) { + switch (arch) { else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}), } } - fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void { - switch (self.target.cpu.arch) { + fn genRelativeFwdJump(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, amount: u32) !void { + switch (arch) { .i386, .x86_64 => { // TODO x86 treats the operands as signed if (amount <= std.math.maxInt(u8)) { @@ -318,70 +455,13 @@ const Function = struct { } } - fn genBlock(self: *Function, inst: *ir.Inst.Block) !MCValue { - switch (self.target.cpu.arch) { + fn genBlock(self: *Function, inst: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { + switch (arch) { else => return self.fail(inst.base.src, "TODO implement codegen Block for {}", .{self.target.cpu.arch}), } } - fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue { - // TODO convert to inline function - switch (self.target.cpu.arch) { - .arm => return self.genAsmArch(.arm, inst), - .armeb => return self.genAsmArch(.armeb, inst), - .aarch64 => return self.genAsmArch(.aarch64, inst), - .aarch64_be => return self.genAsmArch(.aarch64_be, inst), - .aarch64_32 => return self.genAsmArch(.aarch64_32, inst), - .arc => return self.genAsmArch(.arc, inst), - .avr => return self.genAsmArch(.avr, inst), - .bpfel => return self.genAsmArch(.bpfel, inst), - .bpfeb => return self.genAsmArch(.bpfeb, inst), - .hexagon => return self.genAsmArch(.hexagon, inst), - .mips => return self.genAsmArch(.mips, inst), - .mipsel => return self.genAsmArch(.mipsel, inst), - .mips64 => return self.genAsmArch(.mips64, inst), - .mips64el => return self.genAsmArch(.mips64el, inst), - .msp430 => return self.genAsmArch(.msp430, inst), - .powerpc => return self.genAsmArch(.powerpc, inst), - .powerpc64 => return self.genAsmArch(.powerpc64, inst), - .powerpc64le => return self.genAsmArch(.powerpc64le, inst), - .r600 => return self.genAsmArch(.r600, inst), - .amdgcn => return self.genAsmArch(.amdgcn, inst), - .riscv32 => return self.genAsmArch(.riscv32, inst), - .riscv64 => return self.genAsmArch(.riscv64, inst), - .sparc => return self.genAsmArch(.sparc, inst), - .sparcv9 => return self.genAsmArch(.sparcv9, inst), - .sparcel => return self.genAsmArch(.sparcel, inst), - .s390x => return self.genAsmArch(.s390x, inst), - .tce => return self.genAsmArch(.tce, inst), - .tcele => return self.genAsmArch(.tcele, inst), - .thumb => return self.genAsmArch(.thumb, inst), - .thumbeb => return self.genAsmArch(.thumbeb, inst), - .i386 => return self.genAsmArch(.i386, inst), - .x86_64 => return self.genAsmArch(.x86_64, inst), - .xcore => return self.genAsmArch(.xcore, inst), - .nvptx => return self.genAsmArch(.nvptx, inst), - .nvptx64 => return self.genAsmArch(.nvptx64, inst), - .le32 => return self.genAsmArch(.le32, inst), - .le64 => return self.genAsmArch(.le64, inst), - .amdil => return self.genAsmArch(.amdil, inst), - .amdil64 => return self.genAsmArch(.amdil64, inst), - .hsail => return self.genAsmArch(.hsail, inst), - .hsail64 => return self.genAsmArch(.hsail64, inst), - .spir => return self.genAsmArch(.spir, inst), - .spir64 => return self.genAsmArch(.spir64, inst), - .kalimba => return self.genAsmArch(.kalimba, inst), - .shave => return self.genAsmArch(.shave, inst), - .lanai => return self.genAsmArch(.lanai, inst), - .wasm32 => return self.genAsmArch(.wasm32, inst), - .wasm64 => return self.genAsmArch(.wasm64, inst), - .renderscript32 => return self.genAsmArch(.renderscript32, inst), - .renderscript64 => return self.genAsmArch(.renderscript64, inst), - .ve => return self.genAsmArch(.ve, inst), - } - } - - fn genAsmArch(self: *Function, comptime arch: Target.Cpu.Arch, inst: *ir.Inst.Assembly) !MCValue { + fn genAsm(self: *Function, inst: *ir.Inst.Assembly, comptime arch: Target.Cpu.Arch) !MCValue { if (arch != .x86_64 and arch != .i386) { return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{}); } @@ -607,6 +687,9 @@ const Function = struct { } } }, + .stack_offset => |off| { + return self.fail(src, "TODO implement genSetReg for stack variables", .{}); + } }, else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}), } diff --git a/src-self-hosted/codegen/x86_64.zig b/src-self-hosted/codegen/x86_64.zig index 0326f99b993f2f48c4b2523df5f3dbec1302a358..8bf131dbdd84e74ffce2ea279af94a899005408f 100644 --- a/src-self-hosted/codegen/x86_64.zig +++ b/src-self-hosted/codegen/x86_64.zig @@ -1,20 +1,21 @@ +const Type = @import("../Type.zig"); + // zig fmt: off -/// Definitions of all of the x64 registers. The order is very, very important. +/// Definitions of all of the x64 registers. The order is semantically meaningful. /// The registers are defined such that IDs go in descending order of 64-bit, /// 32-bit, 16-bit, and then 8-bit, and each set contains exactly sixteen -/// registers. This results in some very, very useful properties: +/// registers. This results in some useful properties: /// /// Any 64-bit register can be turned into its 32-bit form by adding 16, and /// vice versa. This also works between 32-bit and 16-bit forms. With 8-bit, it -/// works for all except for sp, bp, si, and di, which don't *have* an 8-bit +/// works for all except for sp, bp, si, and di, which do *not* have an 8-bit /// form. /// /// If (register & 8) is set, the register is extended. /// /// The ID can be easily determined by figuring out what range the register is /// in, and then subtracting the base. -/// pub const Register = enum(u8) { // 0 through 15, 64-bit registers. 8-15 are extended. // id is just the int value. @@ -67,3 +68,24 @@ pub const Register = enum(u8) { }; // zig fmt: on + +/// After argument values have been computed, they are placed either in registers +/// or pushed on the stack. The way values are passed depends on the class. +pub const ParameterClass = enum { + /// Integral types that fit into one of the general purpose registers. + integer, + /// Types that fit into a vector register. + sse, + /// Types that fit into a vector register and can be passed + /// and returned in the upper bytes of it. + sse_up, + /// Types that will be returned via the x87FPU. + x87, + /// Types that will be returned via the x87FPU and can be passed and returned + /// in the upper bytes of it. + x87_up, + /// Types that will be returned via the x87FPU. + complex_x87, + /// Types that will be passed and returned in mem-ory via the stack. + memory, +}; diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 2a02d0d89bf1f975f8b7dfddf838887a965d6159..caacf4e7fc74967afb6b629b114bf772747dfd09 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -535,6 +535,76 @@ pub const Type = extern union { }; } + /// Asserts the type has the ABI size already resolved. + pub fn abiSize(self: Type, target: Target) u64 { + return switch (self.tag()) { + .fn_noreturn_no_args => unreachable, // represents machine code; not a pointer + .fn_void_no_args => unreachable, // represents machine code; not a pointer + .fn_naked_noreturn_no_args => unreachable, // represents machine code; not a pointer + .fn_ccc_void_no_args => unreachable, // represents machine code; not a pointer + .function => unreachable, // represents machine code; not a pointer + .c_void => unreachable, + .void => unreachable, + .type => unreachable, + .comptime_int => unreachable, + .comptime_float => unreachable, + .noreturn => unreachable, + .@"null" => unreachable, + .@"undefined" => unreachable, + + .u8, + .i8, + .bool, + => return 1, + + .array_u8_sentinel_0 => @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", self.ptr_otherwise).len, + .array => { + const payload = @fieldParentPtr(Payload.Array, "base", self.ptr_otherwise); + const elem_size = std.math.max(payload.elem_type.abiAlignment(target), payload.elem_type.abiSize(target)); + return payload.len * elem_size; + }, + .i16, .u16 => return 2, + .i32, .u32 => return 4, + .i64, .u64 => return 8, + + .isize, + .usize, + .single_const_pointer_to_comptime_int, + .const_slice_u8, + .single_const_pointer, + => return @divExact(target.cpu.arch.ptrBitWidth(), 8), + + .c_short => return @divExact(CType.short.sizeInBits(target), 8), + .c_ushort => return @divExact(CType.ushort.sizeInBits(target), 8), + .c_int => return @divExact(CType.int.sizeInBits(target), 8), + .c_uint => return @divExact(CType.uint.sizeInBits(target), 8), + .c_long => return @divExact(CType.long.sizeInBits(target), 8), + .c_ulong => return @divExact(CType.ulong.sizeInBits(target), 8), + .c_longlong => return @divExact(CType.longlong.sizeInBits(target), 8), + .c_ulonglong => return @divExact(CType.ulonglong.sizeInBits(target), 8), + + .f16 => return 2, + .f32 => return 4, + .f64 => return 8, + .f128 => return 16, + .c_longdouble => return 16, + + .anyerror => return 2, // TODO revisit this when we have the concept of the error tag type + + + .int_signed, .int_unsigned => { + const bits: u16 = if (self.cast(Payload.IntSigned)) |pl| + pl.bits + else if (self.cast(Payload.IntUnsigned)) |pl| + pl.bits + else + unreachable; + + return std.math.ceilPowerOfTwoPromote(u16, (bits + 7) / 8); + }, + }; + } + pub fn isSinglePointer(self: Type) bool { return switch (self.tag()) { .u8, -- 2.54.0 From 1eed0cf0f328372ddd8938ad3a95e37cb15ce6fb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 28 Jun 2020 19:44:50 -0400 Subject: [PATCH 151/295] zig fmt and delete unused type --- src-self-hosted/codegen.zig | 11 +++++------ src-self-hosted/codegen/x86_64.zig | 23 +---------------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index ccd0da8ae009859b91cd8f56d68a7ab9c1439e7d..73758bda87c7b212dca2c0adb805a6c9b53c3c19 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -54,7 +54,7 @@ pub fn generateSymbol( // Prepare the function parameters switch (bin_file.options.target.cpu.arch) { .x86_64 => { - const integer_registers = [_]Reg(.x86_64){.rdi, .rsi, .rdx, .rcx, .r8, .r9}; + const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; var next_int_reg: usize = 0; for (param_types) |param_type, src_i| { @@ -64,7 +64,7 @@ pub fn generateSymbol( try mc_args.append(.{ .stack_offset = next_stack_offset }); next_stack_offset += param_type.abiSize(bin_file.options.target); } else { - try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg])}); + try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg]) }); next_int_reg += 1; } }, @@ -75,10 +75,9 @@ pub fn generateSymbol( "TODO implement function parameters of type {}", .{@tagName(param_type.zigTypeTag())}, ), - } + }, } } - }, else => return Result{ .fail = try ErrorMsg.create( @@ -113,7 +112,7 @@ pub fn generateSymbol( function.gen() catch |err| switch (err) { error.CodegenFail => return Result{ .fail = function.err_msg.? }, - else => |e| return e, + else => |e| return e, }; if (function.err_msg) |em| { @@ -689,7 +688,7 @@ const Function = struct { }, .stack_offset => |off| { return self.fail(src, "TODO implement genSetReg for stack variables", .{}); - } + }, }, else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}), } diff --git a/src-self-hosted/codegen/x86_64.zig b/src-self-hosted/codegen/x86_64.zig index 8bf131dbdd84e74ffce2ea279af94a899005408f..ddcbd5320e618cf5ca20d2b40e9775268ae59a6e 100644 --- a/src-self-hosted/codegen/x86_64.zig +++ b/src-self-hosted/codegen/x86_64.zig @@ -67,25 +67,4 @@ pub const Register = enum(u8) { } }; -// zig fmt: on - -/// After argument values have been computed, they are placed either in registers -/// or pushed on the stack. The way values are passed depends on the class. -pub const ParameterClass = enum { - /// Integral types that fit into one of the general purpose registers. - integer, - /// Types that fit into a vector register. - sse, - /// Types that fit into a vector register and can be passed - /// and returned in the upper bytes of it. - sse_up, - /// Types that will be returned via the x87FPU. - x87, - /// Types that will be returned via the x87FPU and can be passed and returned - /// in the upper bytes of it. - x87_up, - /// Types that will be returned via the x87FPU. - complex_x87, - /// Types that will be passed and returned in mem-ory via the stack. - memory, -}; +// zig fmt: on \ No newline at end of file -- 2.54.0 From 35e8876c23e53c11115c78ff3984effa965a8cca Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sun, 28 Jun 2020 23:42:42 -0600 Subject: [PATCH 152/295] Revert "arena_allocator: refactor and use full capacity" This reverts commit e120b07a524f1accd4975f5206018c61c4be9345. --- lib/std/heap/arena_allocator.zig | 48 +++++++++++++++++--------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/std/heap/arena_allocator.zig b/lib/std/heap/arena_allocator.zig index 31fbad57f94cbf551068022f527011fac20e72f4..74ced774ed9be9d3da214c19f221ea217a719396 100644 --- a/lib/std/heap/arena_allocator.zig +++ b/lib/std/heap/arena_allocator.zig @@ -15,7 +15,6 @@ pub const ArenaAllocator = struct { /// as a memory-saving optimization. pub const State = struct { buffer_list: std.SinglyLinkedList([]u8) = @as(std.SinglyLinkedList([]u8), .{}), - /// The first available index in the front buffer of `buffer_list` end_index: usize = 0, pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator { @@ -46,36 +45,39 @@ pub const ArenaAllocator = struct { } } - fn getBufNodeAddr(buf: []u8) usize { - return mem.alignBackward(@ptrToInt(buf.ptr) + buf.len - @sizeOf(BufNode), @alignOf(BufNode)); - } - - fn allocBuf(self: *ArenaAllocator, len: usize, ptr_align: u29) ![]u8 { - const alloc_len = len + @sizeOf(BufNode) + @alignOf(BufNode) - 1; - const buf = try self.child_allocator.callAllocFn(alloc_len, ptr_align, 1); - const buf_node = @intToPtr(*BufNode, getBufNodeAddr(buf)); - buf_node.* = .{ .data = buf, .next = null }; - assert(@ptrToInt(buf_node) - @ptrToInt(buf.ptr) >= len); + fn createNode(self: *ArenaAllocator, prev_len: usize, minimum_size: usize) !*BufNode { + const actual_min_size = minimum_size + (@sizeOf(BufNode) + 16); + const big_enough_len = prev_len + actual_min_size; + const len = big_enough_len + big_enough_len / 2; + const buf = try self.child_allocator.alignedAlloc(u8, @alignOf(BufNode), len); + const buf_node_slice = mem.bytesAsSlice(BufNode, buf[0..@sizeOf(BufNode)]); + const buf_node = &buf_node_slice[0]; + buf_node.* = BufNode{ + .data = buf, + .next = null, + }; self.state.buffer_list.prepend(buf_node); - self.state.end_index = len; - return buf[0..len]; + self.state.end_index = 0; + return buf_node; } fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) ![]u8 { const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator); - if (self.state.buffer_list.first) |node_full_buf| { - assert(self.state.end_index > 0); - const cur_buf = node_full_buf.data[0.. - getBufNodeAddr(node_full_buf.data) - @ptrToInt(node_full_buf.data.ptr)]; + var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + ptr_align); + while (true) { + const cur_buf = cur_node.data[@sizeOf(BufNode)..]; const addr = @ptrToInt(cur_buf.ptr) + self.state.end_index; - const aligned_index = self.state.end_index + (mem.alignForward(addr, ptr_align) - addr); - const aligned_end_index = aligned_index + n; - if (aligned_end_index <= cur_buf.len) { - self.state.end_index = aligned_end_index; - return cur_buf[aligned_index..aligned_end_index]; + const adjusted_addr = mem.alignForward(addr, ptr_align); + const adjusted_index = self.state.end_index + (adjusted_addr - addr); + const new_end_index = adjusted_index + n; + if (new_end_index > cur_buf.len) { + cur_node = try self.createNode(cur_buf.len, n + ptr_align); + continue; } + const result = cur_buf[adjusted_index..new_end_index]; + self.state.end_index = new_end_index; + return result; } - return try self.allocBuf(n, ptr_align); } }; -- 2.54.0 From 67e97a1f0fe661b05234e24a58be15d9b48588f2 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Sun, 28 Jun 2020 23:44:48 -0600 Subject: [PATCH 153/295] ArenaAllocator: use full capacity --- lib/std/heap/arena_allocator.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/std/heap/arena_allocator.zig b/lib/std/heap/arena_allocator.zig index 74ced774ed9be9d3da214c19f221ea217a719396..a5d8aaea4543aaf0e975f8a4f23feec9cf7e86dd 100644 --- a/lib/std/heap/arena_allocator.zig +++ b/lib/std/heap/arena_allocator.zig @@ -49,9 +49,8 @@ pub const ArenaAllocator = struct { const actual_min_size = minimum_size + (@sizeOf(BufNode) + 16); const big_enough_len = prev_len + actual_min_size; const len = big_enough_len + big_enough_len / 2; - const buf = try self.child_allocator.alignedAlloc(u8, @alignOf(BufNode), len); - const buf_node_slice = mem.bytesAsSlice(BufNode, buf[0..@sizeOf(BufNode)]); - const buf_node = &buf_node_slice[0]; + const buf = try self.child_allocator.callAllocFn(len, @alignOf(BufNode), 1); + const buf_node = @ptrCast(*BufNode, @alignCast(@alignOf(BufNode), buf.ptr)); buf_node.* = BufNode{ .data = buf, .next = null, -- 2.54.0 From bf8bf528c6c131f61de4af24c3599486a59c5868 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Jun 2020 17:10:01 +0200 Subject: [PATCH 154/295] Handle ENOTCAPABLE in WASI This commit adds `error.NotCapable` enum value and makes sure that every applicable WASI syscall that can return `ENOTCAPABLE` errno remaps it to `error.NotCapable. --- lib/std/fs.zig | 34 ++++++++---- lib/std/os.zig | 141 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 132 insertions(+), 43 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 6cb7d478b21cb57732325475b9ff3dc4a0843d7e..08e77347da5ba8c952b10298f14cf8364ee2c7e5 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -264,7 +264,13 @@ pub const Dir = struct { pub const Kind = File.Kind; }; - const IteratorError = error{AccessDenied} || os.UnexpectedError; + const IteratorError = error{ + AccessDenied, + + /// WASI-only. This error occurs when the underlying `Dir` file descriptor does + /// not hold the required rights to call `fd_readdir` on it. + NotCapable, + } || os.UnexpectedError; pub const Iterator = switch (builtin.os.tag) { .macosx, .ios, .freebsd, .netbsd, .dragonfly => struct { @@ -535,14 +541,15 @@ pub const Dir = struct { w.EFAULT => unreachable, w.ENOTDIR => unreachable, w.EINVAL => unreachable, + w.ENOTCAPABLE => return error.NotCapable, else => |err| return os.unexpectedErrno(err), } if (bufused == 0) return null; self.index = 0; self.end_index = bufused; } - const entry = @ptrCast(*align(1) os.wasi.dirent_t, &self.buf[self.index]); - const entry_size = @sizeOf(os.wasi.dirent_t); + const entry = @ptrCast(*align(1) w.dirent_t, &self.buf[self.index]); + const entry_size = @sizeOf(w.dirent_t); const name_index = self.index + entry_size; const name = mem.span(self.buf[name_index .. name_index + entry.d_namlen]); @@ -556,12 +563,12 @@ pub const Dir = struct { } const entry_kind = switch (entry.d_type) { - wasi.FILETYPE_BLOCK_DEVICE => Entry.Kind.BlockDevice, - wasi.FILETYPE_CHARACTER_DEVICE => Entry.Kind.CharacterDevice, - wasi.FILETYPE_DIRECTORY => Entry.Kind.Directory, - wasi.FILETYPE_SYMBOLIC_LINK => Entry.Kind.SymLink, - wasi.FILETYPE_REGULAR_FILE => Entry.Kind.File, - wasi.FILETYPE_SOCKET_STREAM, wasi.FILETYPE_SOCKET_DGRAM => Entry.Kind.UnixDomainSocket, + w.FILETYPE_BLOCK_DEVICE => Entry.Kind.BlockDevice, + w.FILETYPE_CHARACTER_DEVICE => Entry.Kind.CharacterDevice, + w.FILETYPE_DIRECTORY => Entry.Kind.Directory, + w.FILETYPE_SYMBOLIC_LINK => Entry.Kind.SymLink, + w.FILETYPE_REGULAR_FILE => Entry.Kind.File, + w.FILETYPE_SOCKET_STREAM, wasi.FILETYPE_SOCKET_DGRAM => Entry.Kind.UnixDomainSocket, else => Entry.Kind.Unknown, }; return Entry{ @@ -621,6 +628,7 @@ pub const Dir = struct { InvalidUtf8, BadPathName, DeviceBusy, + NotCapable, } || os.UnexpectedError; pub fn close(self: *Dir) void { @@ -1105,7 +1113,7 @@ pub const Dir = struct { } } - pub const DeleteFileError = os.UnlinkError; + pub const DeleteFileError = os.UnlinkatError; /// Delete a file name and possibly the file it refers to, based on an open directory handle. /// Asserts that the path parameter has no null bytes. @@ -1147,6 +1155,7 @@ pub const Dir = struct { ReadOnlyFileSystem, InvalidUtf8, BadPathName, + NotCapable, Unexpected, }; @@ -1249,6 +1258,7 @@ pub const Dir = struct { /// On Windows, file paths cannot contain these characters: /// '/', '*', '?', '"', '<', '>', '|' BadPathName, + NotCapable, } || os.UnexpectedError; /// Whether `full_path` describes a symlink, file, or directory, this function @@ -1276,6 +1286,7 @@ pub const Dir = struct { error.FileBusy, error.BadPathName, error.Unexpected, + error.NotCapable, => |e| return e, } var dir = self.openDir(sub_path, .{ .iterate = true }) catch |err| switch (err) { @@ -1301,6 +1312,7 @@ pub const Dir = struct { error.InvalidUtf8, error.BadPathName, error.DeviceBusy, + error.NotCapable, => |e| return e, }; var cleanup_dir_parent: ?Dir = null; @@ -1342,6 +1354,7 @@ pub const Dir = struct { error.FileBusy, error.BadPathName, error.Unexpected, + error.NotCapable, => |e| return e, } @@ -1368,6 +1381,7 @@ pub const Dir = struct { error.InvalidUtf8, error.BadPathName, error.DeviceBusy, + error.NotCapable, => |e| return e, }; if (cleanup_dir_parent) |*d| d.close(); diff --git a/lib/std/os.zig b/lib/std/os.zig index 99d66db2bb648a195a780083016618b0a14f68a8..201cca97882385251044fd0de9594efe963d76c1 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -300,6 +300,10 @@ pub const ReadError = error{ /// This error occurs when no global event loop is configured, /// and reading from the file descriptor would block. WouldBlock, + + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to read from it. + NotCapable, } || UnexpectedError; /// Returns the number of bytes that were read, which can be less than @@ -335,6 +339,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { wasi.ENOMEM => return error.SystemResources, wasi.ECONNRESET => return error.ConnectionResetByPeer, wasi.ETIMEDOUT => return error.ConnectionTimedOut, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -402,6 +407,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { wasi.EISDIR => return error.IsDir, wasi.ENOBUFS => return error.SystemResources, wasi.ENOMEM => return error.SystemResources, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -466,6 +472,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -502,6 +509,10 @@ pub const TruncateError = error{ InputOutput, CannotTruncate, FileBusy, + + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to call `ftruncate` on it. + NotCapable, } || UnexpectedError; pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { @@ -536,6 +547,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { wasi.ETXTBSY => return error.FileBusy, wasi.EBADF => unreachable, // Handle not open for writing wasi.EINVAL => unreachable, // Handle not open for writing + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -604,6 +616,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -649,6 +662,10 @@ pub const WriteError = error{ /// This error occurs when no global event loop is configured, /// and reading from the file descriptor would block. WouldBlock, + + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to write to it. + NotCapable, } || UnexpectedError; /// Write to a file descriptor. @@ -697,6 +714,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { wasi.ENOSPC => return error.NoSpaceLeft, wasi.EPERM => return error.AccessDenied, wasi.EPIPE => return error.BrokenPipe, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -774,6 +792,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { wasi.ENOSPC => return error.NoSpaceLeft, wasi.EPERM => return error.AccessDenied, wasi.EPIPE => return error.BrokenPipe, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -856,6 +875,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -949,6 +969,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -1020,6 +1041,10 @@ pub const OpenError = error{ /// The underlying filesystem does not support file locks FileLocksNotSupported, + + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to open a new resource relative to it. + NotCapable, } || UnexpectedError; /// Open and possibly create a file. Keeps trying if it gets interrupted. @@ -1113,6 +1138,7 @@ pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags wasi.EPERM => return error.AccessDenied, wasi.EEXIST => return error.PathAlreadyExists, wasi.EBUSY => return error.DeviceBusy, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -1563,13 +1589,19 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin } } +pub const SymlinkatError = error{ + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to create a new symbolic link relative to it. + NotCapable, +} || SymlinkError; + /// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string /// `target_path` **relative** to `newdirfd` directory handle. /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent /// one; the latter case is known as a dangling link. /// If `sym_link_path` exists, it will not be overwritten. /// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. -pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { +pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkatError!void { if (builtin.os.tag == .wasi) { return symlinkatWasi(target_path, newdirfd, sym_link_path); } @@ -1587,7 +1619,7 @@ pub const symlinkatC = @compileError("deprecated: renamed to symlinkatZ"); /// WASI-only. The same as `symlinkat` but targeting WASI. /// See also `symlinkat`. -pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { +pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkatError!void { switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) { wasi.ESUCCESS => {}, wasi.EFAULT => unreachable, @@ -1604,19 +1636,20 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c wasi.ENOMEM => return error.SystemResources, wasi.ENOSPC => return error.NoSpaceLeft, wasi.EROFS => return error.ReadOnlyFileSystem, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } /// Windows-only. The same as `symlinkat` except the paths are null-terminated, WTF-16 encoded. /// See also `symlinkat`. -pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymlinkError!void { +pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymlinkatError!void { @compileError("TODO implement on Windows"); } /// The same as `symlinkat` except the parameters are null-terminated pointers. /// See also `symlinkat`. -pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { +pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkatError!void { if (builtin.os.tag == .windows) { const target_path_w = try windows.cStrToPrefixedFileW(target_path); const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); @@ -1706,6 +1739,10 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { pub const UnlinkatError = UnlinkError || error{ /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty. DirNotEmpty, + + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to unlink a resource by path relative to it. + NotCapable, }; /// Delete a file name and possibly the file it refers to, based on an open directory handle. @@ -1747,6 +1784,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro wasi.ENOMEM => return error.SystemResources, wasi.EROFS => return error.ReadOnlyFileSystem, wasi.ENOTEMPTY => return error.DirNotEmpty, + wasi.ENOTCAPABLE => return error.NotCapable, wasi.EINVAL => unreachable, // invalid flags, or pathname has . as last component wasi.EBADF => unreachable, // always a race condition @@ -1925,13 +1963,19 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v return windows.MoveFileExW(old_path, new_path, flags); } +pub const RenameatError = error{ + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to rename a resource by path relative to it. + NotCapable, +} || RenameError; + /// Change the name or location of a file based on an open directory handle. pub fn renameat( old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8, -) RenameError!void { +) RenameatError!void { if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); @@ -1947,7 +1991,7 @@ pub fn renameat( /// WASI-only. Same as `renameat` expect targeting WASI. /// See also `renameat`. -pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void { +pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameatError!void { switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) { wasi.ESUCCESS => return, wasi.EACCES => return error.AccessDenied, @@ -1968,6 +2012,7 @@ pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, ne wasi.ENOTEMPTY => return error.PathAlreadyExists, wasi.EROFS => return error.ReadOnlyFileSystem, wasi.EXDEV => return error.RenameAcrossMountPoints, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -1978,7 +2023,7 @@ pub fn renameatZ( old_path: [*:0]const u8, new_dir_fd: fd_t, new_path: [*:0]const u8, -) RenameError!void { +) RenameatError!void { if (builtin.os.tag == .windows) { const old_path_w = try windows.cStrToPrefixedFileW(old_path); const new_path_w = try windows.cStrToPrefixedFileW(new_path); @@ -2017,7 +2062,7 @@ pub fn renameatW( new_dir_fd: fd_t, new_path_w: []const u16, ReplaceIfExists: windows.BOOLEAN, -) RenameError!void { +) RenameatError!void { const src_fd = windows.OpenFile(old_path_w, .{ .dir = old_dir_fd, .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE, @@ -2066,24 +2111,13 @@ pub fn renameatW( } } -pub const MakeDirError = error{ - AccessDenied, - DiskQuota, - PathAlreadyExists, - SymLinkLoop, - LinkQuotaExceeded, - NameTooLong, - FileNotFound, - SystemResources, - NoSpaceLeft, - NotDir, - ReadOnlyFileSystem, - InvalidUtf8, - BadPathName, - NoDevice, -} || UnexpectedError; +pub const MakeDirAtError = error{ + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to create a new directory relative to it. + NotCapable, +} || MakeDirError; -pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { +pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirAtError!void { if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); @@ -2097,7 +2131,7 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v pub const mkdiratC = @compileError("deprecated: renamed to mkdiratZ"); -pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { +pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirAtError!void { switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) { wasi.ESUCCESS => return, wasi.EACCES => return error.AccessDenied, @@ -2114,11 +2148,12 @@ pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirErr wasi.ENOSPC => return error.NoSpaceLeft, wasi.ENOTDIR => return error.NotDir, wasi.EROFS => return error.ReadOnlyFileSystem, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } -pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { +pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirAtError!void { if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); @@ -2143,11 +2178,28 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr } } -pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void { +pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirAtError!void { const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null); windows.CloseHandle(sub_dir_handle); } +pub const MakeDirError = error{ + AccessDenied, + DiskQuota, + PathAlreadyExists, + SymLinkLoop, + LinkQuotaExceeded, + NameTooLong, + FileNotFound, + SystemResources, + NoSpaceLeft, + NotDir, + ReadOnlyFileSystem, + InvalidUtf8, + BadPathName, + NoDevice, +} || UnexpectedError; + /// Create a directory. /// `mode` is ignored on Windows. pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { @@ -2364,10 +2416,16 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 } } +pub const ReadLinkAtError = error{ + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to read value of a symbolic link relative to it. + NotCapable, +} || ReadLinkError; + /// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle. /// The return value is a slice of `out_buffer` from index 0. /// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. -pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { +pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkAtError![]u8 { if (builtin.os.tag == .wasi) { return readlinkatWasi(dirfd, file_path, out_buffer); } @@ -2383,7 +2441,7 @@ pub const readlinkatC = @compileError("deprecated: renamed to readlinkatZ"); /// WASI-only. Same as `readlinkat` but targets WASI. /// See also `readlinkat`. -pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { +pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkAtError![]u8 { var bufused: usize = undefined; switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) { wasi.ESUCCESS => return out_buffer[0..bufused], @@ -2396,19 +2454,20 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read wasi.ENOENT => return error.FileNotFound, wasi.ENOMEM => return error.SystemResources, wasi.ENOTDIR => return error.NotDir, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. /// See also `readlinkat`. -pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { +pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkAtError![]u8 { @compileError("TODO implement on Windows"); } /// Same as `readlinkat` except `file_path` is null-terminated. /// See also `readlinkat`. -pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { +pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkAtError![]u8 { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer); @@ -3074,6 +3133,10 @@ pub fn waitpid(pid: i32, flags: u32) u32 { pub const FStatError = error{ SystemResources, AccessDenied, + + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to get its filestat information. + NotCapable, } || UnexpectedError; /// Return information about a file descriptor. @@ -3086,6 +3149,7 @@ pub fn fstat(fd: fd_t) FStatError!Stat { wasi.EBADF => unreachable, // Always a race condition. wasi.ENOMEM => return error.SystemResources, wasi.EACCES => return error.AccessDenied, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -3136,6 +3200,7 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S wasi.ENAMETOOLONG => return error.NameTooLong, wasi.ENOENT => return error.FileNotFound, wasi.ENOTDIR => return error.FileNotFound, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -3641,7 +3706,13 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { } } -pub const SeekError = error{Unseekable} || UnexpectedError; +pub const SeekError = error{ + Unseekable, + + /// WASI-only. This error occurs when the file descriptor does + /// not hold the required rights to seek on it. + NotCapable, +} || UnexpectedError; /// Repositions read/write file offset relative to the beginning. pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { @@ -3669,6 +3740,7 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -3710,6 +3782,7 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -3750,6 +3823,7 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } @@ -3790,6 +3864,7 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, + wasi.ENOTCAPABLE => return error.NotCapable, else => |err| return unexpectedErrno(err), } } -- 2.54.0 From b96882c57a20d3f9f19640dbb6b2483d7dea5dfe Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Jun 2020 18:09:22 +0200 Subject: [PATCH 155/295] Fix compilation errors --- lib/std/child_process.zig | 1 + lib/std/elf.zig | 1 + lib/std/fs.zig | 2 ++ lib/std/os.zig | 12 ++++++------ src-self-hosted/stage2.zig | 3 +++ 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 57c1dfb945aac021ae073e476bbe9ba6f465ca7d..20293daecb436401343d33c9b221c77f2353d115 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -364,6 +364,7 @@ pub const ChildProcess = struct { error.FileTooBig => unreachable, error.DeviceBusy => unreachable, error.FileLocksNotSupported => unreachable, + error.NotCapable => unreachable, // until WASI comes up with multi-processing (if ever) else => |e| return e, } else diff --git a/lib/std/elf.zig b/lib/std/elf.zig index dd22a42304b181b2f5978a548fe1afcba6e2f84f..54d4673f64d954d3c40265310b75313090e6a8eb 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -551,6 +551,7 @@ fn preadNoEof(file: std.fs.File, buf: []u8, offset: u64) !void { error.InputOutput => return error.FileSystem, error.Unexpected => return error.Unexpected, error.WouldBlock => return error.Unexpected, + error.NotCapable => unreachable, // NotCapable mainly pertains WASI target so it's a bug if we hit it here }; if (len == 0) return error.UnexpectedEndOfFile; i += len; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 08e77347da5ba8c952b10298f14cf8364ee2c7e5..fe2e7338c8bf8658321f8f4f6d1ef746a9c8b4eb 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1272,6 +1272,7 @@ pub const Dir = struct { if (self.deleteFile(sub_path)) { return; } else |err| switch (err) { + error.DirNotEmpty => unreachable, error.FileNotFound => return, error.IsDir => {}, error.AccessDenied => got_access_denied = true, @@ -1341,6 +1342,7 @@ pub const Dir = struct { // Impossible because we do not pass any path separators. error.NotDir => unreachable, + error.DirNotEmpty => unreachable, error.IsDir => {}, error.AccessDenied => got_access_denied = true, diff --git a/lib/std/os.zig b/lib/std/os.zig index 201cca97882385251044fd0de9594efe963d76c1..a7e29555d64bab00f4a1077c41c46e98c3c2d252 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1589,11 +1589,11 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin } } -pub const SymlinkatError = error{ +pub const SymLinkAtError = error{ /// WASI-only. This error occurs when the file descriptor does /// not hold the required rights to create a new symbolic link relative to it. NotCapable, -} || SymlinkError; +} || SymLinkError; /// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string /// `target_path` **relative** to `newdirfd` directory handle. @@ -1601,7 +1601,7 @@ pub const SymlinkatError = error{ /// one; the latter case is known as a dangling link. /// If `sym_link_path` exists, it will not be overwritten. /// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. -pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkatError!void { +pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkAtError!void { if (builtin.os.tag == .wasi) { return symlinkatWasi(target_path, newdirfd, sym_link_path); } @@ -1619,7 +1619,7 @@ pub const symlinkatC = @compileError("deprecated: renamed to symlinkatZ"); /// WASI-only. The same as `symlinkat` but targeting WASI. /// See also `symlinkat`. -pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkatError!void { +pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkAtError!void { switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) { wasi.ESUCCESS => {}, wasi.EFAULT => unreachable, @@ -1643,13 +1643,13 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c /// Windows-only. The same as `symlinkat` except the paths are null-terminated, WTF-16 encoded. /// See also `symlinkat`. -pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymlinkatError!void { +pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymLinkAtError!void { @compileError("TODO implement on Windows"); } /// The same as `symlinkat` except the parameters are null-terminated pointers. /// See also `symlinkat`. -pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkatError!void { +pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkAtError!void { if (builtin.os.tag == .windows) { const target_path_w = try windows.cStrToPrefixedFileW(target_path); const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); diff --git a/src-self-hosted/stage2.zig b/src-self-hosted/stage2.zig index bd24ffb399f1663f871fb92e2c53c99de0baf08c..60076b9cdbc7947700db4ce600deeb6fa601b959 100644 --- a/src-self-hosted/stage2.zig +++ b/src-self-hosted/stage2.zig @@ -163,6 +163,7 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error { error.OutOfMemory => return .OutOfMemory, error.Unexpected => return .Unexpected, error.InputOutput => return .FileSystem, + error.NotCapable => unreachable, // we should handle this when we're able to cross-compile Zig to WASI }; return .None; } @@ -606,6 +607,7 @@ export fn stage2_libc_parse(stage1_libc: *Stage2LibCInstallation, libc_file_z: [ error.NotDir => return .NotDir, error.DeviceBusy => return .DeviceBusy, error.FileLocksNotSupported => unreachable, + error.NotCapable => unreachable, // we should handle this when we're able to cross-compile Zig to WASI }; stage1_libc.initFromStage2(libc); return .None; @@ -649,6 +651,7 @@ export fn stage2_libc_render(stage1_libc: *Stage2LibCInstallation, output_file: error.AccessDenied => return .AccessDenied, error.Unexpected => return .Unexpected, error.InputOutput => return .FileSystem, + error.NotCapable => unreachable, // we should handle this when we're able to cross-compile Zig to WASI }; return .None; } -- 2.54.0 From 5bc99dd7e8c037aa6c5f46d779fba99163d1da36 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 29 Jun 2020 18:28:28 +0200 Subject: [PATCH 156/295] Fix more compilation errors --- lib/std/os.zig | 1 + lib/std/zig/system.zig | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/lib/std/os.zig b/lib/std/os.zig index a7e29555d64bab00f4a1077c41c46e98c3c2d252..a1b3665845906565b29773404ebde46ac48c47f0 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4012,6 +4012,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP if (builtin.os.tag == .linux and !builtin.link_libc) { const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) { error.FileLocksNotSupported => unreachable, + error.NotCapable => unreachable, // WASI only else => |e| return e, }; defer close(fd); diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 64c9401dbc03f1321b95c08bfea9cb12e7d05568..166dbf4c4351a376d89b1751304e0ec0d18c8d4d 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -499,6 +499,7 @@ pub const NativeTargetInfo = struct { error.PipeBusy => unreachable, error.FileLocksNotSupported => unreachable, error.WouldBlock => unreachable, + error.NotCapable => unreachable, // we don't support WASI here (not yet at least) error.IsDir, error.NotDir, @@ -790,6 +791,7 @@ pub const NativeTargetInfo = struct { var it = mem.tokenize(rpath_list, ":"); while (it.next()) |rpath| { var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { + error.NotCapable => unreachable, // we don't support WASI here (not yet at least) error.NameTooLong => unreachable, error.InvalidUtf8 => unreachable, error.BadPathName => unreachable, @@ -817,6 +819,7 @@ pub const NativeTargetInfo = struct { &link_buf, ) catch |err| switch (err) { error.NameTooLong => unreachable, + error.NotCapable => unreachable, // we don't support WASI here (not yet at least) error.AccessDenied, error.FileNotFound, @@ -851,6 +854,7 @@ pub const NativeTargetInfo = struct { const len = file.pread(buf[i .. buf.len - i], offset + i) catch |err| switch (err) { error.OperationAborted => unreachable, // Windows-only error.WouldBlock => unreachable, // Did not request blocking mode + error.NotCapable => unreachable, // WASI only, and we don't support it here yet error.SystemResources => return error.SystemResources, error.IsDir => return error.UnableToReadElfFile, error.BrokenPipe => return error.UnableToReadElfFile, -- 2.54.0 From 8306826d53bf6dd0c5ea5ea27963374b31aa6dd1 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 30 Jun 2020 18:18:25 +0200 Subject: [PATCH 157/295] Map ENOTCAPABLE into error.AccessDenied instead of error.NotCapable This is direct result of review comments left by andrewrk and daurnimator. It makes sense to map `ENOTCAPABLE` into a more generic `error.AccessDenied`. --- lib/std/child_process.zig | 1 - lib/std/elf.zig | 2 +- lib/std/fs.zig | 21 +---- lib/std/os.zig | 163 ++++++++++++++++--------------------- lib/std/zig/system.zig | 5 +- src-self-hosted/stage2.zig | 3 - 6 files changed, 75 insertions(+), 120 deletions(-) diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 20293daecb436401343d33c9b221c77f2353d115..57c1dfb945aac021ae073e476bbe9ba6f465ca7d 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -364,7 +364,6 @@ pub const ChildProcess = struct { error.FileTooBig => unreachable, error.DeviceBusy => unreachable, error.FileLocksNotSupported => unreachable, - error.NotCapable => unreachable, // until WASI comes up with multi-processing (if ever) else => |e| return e, } else diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 54d4673f64d954d3c40265310b75313090e6a8eb..b6609d8b311fe11a5bc7b44c60d8e956547535ae 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -551,7 +551,7 @@ fn preadNoEof(file: std.fs.File, buf: []u8, offset: u64) !void { error.InputOutput => return error.FileSystem, error.Unexpected => return error.Unexpected, error.WouldBlock => return error.Unexpected, - error.NotCapable => unreachable, // NotCapable mainly pertains WASI target so it's a bug if we hit it here + error.AccessDenied => return error.Unexpected, }; if (len == 0) return error.UnexpectedEndOfFile; i += len; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index fe2e7338c8bf8658321f8f4f6d1ef746a9c8b4eb..caa414ac1759b2a90e68599d7a592db144fa1583 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -264,13 +264,7 @@ pub const Dir = struct { pub const Kind = File.Kind; }; - const IteratorError = error{ - AccessDenied, - - /// WASI-only. This error occurs when the underlying `Dir` file descriptor does - /// not hold the required rights to call `fd_readdir` on it. - NotCapable, - } || os.UnexpectedError; + const IteratorError = error{AccessDenied} || os.UnexpectedError; pub const Iterator = switch (builtin.os.tag) { .macosx, .ios, .freebsd, .netbsd, .dragonfly => struct { @@ -541,7 +535,7 @@ pub const Dir = struct { w.EFAULT => unreachable, w.ENOTDIR => unreachable, w.EINVAL => unreachable, - w.ENOTCAPABLE => return error.NotCapable, + w.ENOTCAPABLE => return error.AccessDenied, else => |err| return os.unexpectedErrno(err), } if (bufused == 0) return null; @@ -628,7 +622,6 @@ pub const Dir = struct { InvalidUtf8, BadPathName, DeviceBusy, - NotCapable, } || os.UnexpectedError; pub fn close(self: *Dir) void { @@ -1113,7 +1106,7 @@ pub const Dir = struct { } } - pub const DeleteFileError = os.UnlinkatError; + pub const DeleteFileError = os.UnlinkError; /// Delete a file name and possibly the file it refers to, based on an open directory handle. /// Asserts that the path parameter has no null bytes. @@ -1155,7 +1148,6 @@ pub const Dir = struct { ReadOnlyFileSystem, InvalidUtf8, BadPathName, - NotCapable, Unexpected, }; @@ -1258,7 +1250,6 @@ pub const Dir = struct { /// On Windows, file paths cannot contain these characters: /// '/', '*', '?', '"', '<', '>', '|' BadPathName, - NotCapable, } || os.UnexpectedError; /// Whether `full_path` describes a symlink, file, or directory, this function @@ -1272,7 +1263,6 @@ pub const Dir = struct { if (self.deleteFile(sub_path)) { return; } else |err| switch (err) { - error.DirNotEmpty => unreachable, error.FileNotFound => return, error.IsDir => {}, error.AccessDenied => got_access_denied = true, @@ -1287,7 +1277,6 @@ pub const Dir = struct { error.FileBusy, error.BadPathName, error.Unexpected, - error.NotCapable, => |e| return e, } var dir = self.openDir(sub_path, .{ .iterate = true }) catch |err| switch (err) { @@ -1313,7 +1302,6 @@ pub const Dir = struct { error.InvalidUtf8, error.BadPathName, error.DeviceBusy, - error.NotCapable, => |e| return e, }; var cleanup_dir_parent: ?Dir = null; @@ -1342,7 +1330,6 @@ pub const Dir = struct { // Impossible because we do not pass any path separators. error.NotDir => unreachable, - error.DirNotEmpty => unreachable, error.IsDir => {}, error.AccessDenied => got_access_denied = true, @@ -1356,7 +1343,6 @@ pub const Dir = struct { error.FileBusy, error.BadPathName, error.Unexpected, - error.NotCapable, => |e| return e, } @@ -1383,7 +1369,6 @@ pub const Dir = struct { error.InvalidUtf8, error.BadPathName, error.DeviceBusy, - error.NotCapable, => |e| return e, }; if (cleanup_dir_parent) |*d| d.close(); diff --git a/lib/std/os.zig b/lib/std/os.zig index a1b3665845906565b29773404ebde46ac48c47f0..1e1049ae51cee701038f1d408f7e9632d3107c25 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -301,9 +301,9 @@ pub const ReadError = error{ /// and reading from the file descriptor would block. WouldBlock, - /// WASI-only. This error occurs when the file descriptor does + /// In WASI, this error occurs when the file descriptor does /// not hold the required rights to read from it. - NotCapable, + AccessDenied, } || UnexpectedError; /// Returns the number of bytes that were read, which can be less than @@ -339,7 +339,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { wasi.ENOMEM => return error.SystemResources, wasi.ECONNRESET => return error.ConnectionResetByPeer, wasi.ETIMEDOUT => return error.ConnectionTimedOut, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -407,7 +407,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { wasi.EISDIR => return error.IsDir, wasi.ENOBUFS => return error.SystemResources, wasi.ENOMEM => return error.SystemResources, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -472,7 +472,7 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -507,12 +507,11 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { pub const TruncateError = error{ FileTooBig, InputOutput, - CannotTruncate, FileBusy, - /// WASI-only. This error occurs when the file descriptor does + /// In WASI, this error occurs when the file descriptor does /// not hold the required rights to call `ftruncate` on it. - NotCapable, + AccessDenied, } || UnexpectedError; pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { @@ -533,7 +532,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { switch (rc) { .SUCCESS => return, .INVALID_HANDLE => unreachable, // Handle not open for writing - .ACCESS_DENIED => return error.CannotTruncate, + .ACCESS_DENIED => return error.AccessDenied, else => return windows.unexpectedStatus(rc), } } @@ -543,11 +542,11 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { wasi.EINTR => unreachable, wasi.EFBIG => return error.FileTooBig, wasi.EIO => return error.InputOutput, - wasi.EPERM => return error.CannotTruncate, + wasi.EPERM => return error.AccessDenied, wasi.ETXTBSY => return error.FileBusy, wasi.EBADF => unreachable, // Handle not open for writing wasi.EINVAL => unreachable, // Handle not open for writing - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -566,7 +565,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { EINTR => continue, EFBIG => return error.FileTooBig, EIO => return error.InputOutput, - EPERM => return error.CannotTruncate, + EPERM => return error.AccessDenied, ETXTBSY => return error.FileBusy, EBADF => unreachable, // Handle not open for writing EINVAL => unreachable, // Handle not open for writing @@ -616,7 +615,7 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -654,18 +653,17 @@ pub const WriteError = error{ FileTooBig, InputOutput, NoSpaceLeft, - AccessDenied, - BrokenPipe, - SystemResources, - OperationAborted, - /// This error occurs when no global event loop is configured, - /// and reading from the file descriptor would block. - WouldBlock, - - /// WASI-only. This error occurs when the file descriptor does + /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to write to it. - NotCapable, + AccessDenied, + BrokenPipe, + SystemResources, + OperationAborted, + + /// This error occurs when no global event loop is configured, + /// and reading from the file descriptor would block. + WouldBlock, } || UnexpectedError; /// Write to a file descriptor. @@ -714,7 +712,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { wasi.ENOSPC => return error.NoSpaceLeft, wasi.EPERM => return error.AccessDenied, wasi.EPIPE => return error.BrokenPipe, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -792,7 +790,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { wasi.ENOSPC => return error.NoSpaceLeft, wasi.EPERM => return error.AccessDenied, wasi.EPIPE => return error.BrokenPipe, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -875,7 +873,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -969,7 +967,7 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz wasi.ENXIO => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.EOVERFLOW => return error.Unseekable, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -1005,6 +1003,8 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz } pub const OpenError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to open a new resource relative to it. AccessDenied, SymLinkLoop, ProcessFdQuotaExceeded, @@ -1041,10 +1041,6 @@ pub const OpenError = error{ /// The underlying filesystem does not support file locks FileLocksNotSupported, - - /// WASI-only. This error occurs when the file descriptor does - /// not hold the required rights to open a new resource relative to it. - NotCapable, } || UnexpectedError; /// Open and possibly create a file. Keeps trying if it gets interrupted. @@ -1138,7 +1134,7 @@ pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags wasi.EPERM => return error.AccessDenied, wasi.EEXIST => return error.PathAlreadyExists, wasi.EBUSY => return error.DeviceBusy, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -1525,6 +1521,8 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { } pub const SymLinkError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to create a new symbolic link relative to it. AccessDenied, DiskQuota, PathAlreadyExists, @@ -1589,19 +1587,13 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin } } -pub const SymLinkAtError = error{ - /// WASI-only. This error occurs when the file descriptor does - /// not hold the required rights to create a new symbolic link relative to it. - NotCapable, -} || SymLinkError; - /// Similar to `symlink`, however, creates a symbolic link named `sym_link_path` which contains the string /// `target_path` **relative** to `newdirfd` directory handle. /// A symbolic link (also known as a soft link) may point to an existing file or to a nonexistent /// one; the latter case is known as a dangling link. /// If `sym_link_path` exists, it will not be overwritten. /// See also `symlinkatWasi`, `symlinkatZ` and `symlinkatW`. -pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkAtError!void { +pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { if (builtin.os.tag == .wasi) { return symlinkatWasi(target_path, newdirfd, sym_link_path); } @@ -1619,7 +1611,7 @@ pub const symlinkatC = @compileError("deprecated: renamed to symlinkatZ"); /// WASI-only. The same as `symlinkat` but targeting WASI. /// See also `symlinkat`. -pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkAtError!void { +pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { switch (wasi.path_symlink(target_path.ptr, target_path.len, newdirfd, sym_link_path.ptr, sym_link_path.len)) { wasi.ESUCCESS => {}, wasi.EFAULT => unreachable, @@ -1636,20 +1628,20 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c wasi.ENOMEM => return error.SystemResources, wasi.ENOSPC => return error.NoSpaceLeft, wasi.EROFS => return error.ReadOnlyFileSystem, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } /// Windows-only. The same as `symlinkat` except the paths are null-terminated, WTF-16 encoded. /// See also `symlinkat`. -pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymLinkAtError!void { +pub fn symlinkatW(target_path: [*:0]const u16, newdirfd: fd_t, sym_link_path: [*:0]const u16) SymLinkError!void { @compileError("TODO implement on Windows"); } /// The same as `symlinkat` except the parameters are null-terminated pointers. /// See also `symlinkat`. -pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkAtError!void { +pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { if (builtin.os.tag == .windows) { const target_path_w = try windows.cStrToPrefixedFileW(target_path); const sym_link_path_w = try windows.cStrToPrefixedFileW(sym_link_path); @@ -1677,6 +1669,9 @@ pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*: pub const UnlinkError = error{ FileNotFound, + + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to unlink a resource by path relative to it. AccessDenied, FileBusy, FileSystem, @@ -1739,10 +1734,6 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { pub const UnlinkatError = UnlinkError || error{ /// When passing `AT_REMOVEDIR`, this error occurs when the named directory is not empty. DirNotEmpty, - - /// WASI-only. This error occurs when the file descriptor does - /// not hold the required rights to unlink a resource by path relative to it. - NotCapable, }; /// Delete a file name and possibly the file it refers to, based on an open directory handle. @@ -1784,7 +1775,7 @@ pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatErro wasi.ENOMEM => return error.SystemResources, wasi.EROFS => return error.ReadOnlyFileSystem, wasi.ENOTEMPTY => return error.DirNotEmpty, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, wasi.EINVAL => unreachable, // invalid flags, or pathname has . as last component wasi.EBADF => unreachable, // always a race condition @@ -1887,6 +1878,8 @@ pub fn unlinkatW(dirfd: fd_t, sub_path_w: [*:0]const u16, flags: u32) UnlinkatEr } const RenameError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to rename a resource by path relative to it. AccessDenied, FileBusy, DiskQuota, @@ -1963,19 +1956,13 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v return windows.MoveFileExW(old_path, new_path, flags); } -pub const RenameatError = error{ - /// WASI-only. This error occurs when the file descriptor does - /// not hold the required rights to rename a resource by path relative to it. - NotCapable, -} || RenameError; - /// Change the name or location of a file based on an open directory handle. pub fn renameat( old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8, -) RenameatError!void { +) RenameError!void { if (builtin.os.tag == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); @@ -1991,7 +1978,7 @@ pub fn renameat( /// WASI-only. Same as `renameat` expect targeting WASI. /// See also `renameat`. -pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameatError!void { +pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void { switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) { wasi.ESUCCESS => return, wasi.EACCES => return error.AccessDenied, @@ -2012,7 +1999,7 @@ pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, ne wasi.ENOTEMPTY => return error.PathAlreadyExists, wasi.EROFS => return error.ReadOnlyFileSystem, wasi.EXDEV => return error.RenameAcrossMountPoints, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -2023,7 +2010,7 @@ pub fn renameatZ( old_path: [*:0]const u8, new_dir_fd: fd_t, new_path: [*:0]const u8, -) RenameatError!void { +) RenameError!void { if (builtin.os.tag == .windows) { const old_path_w = try windows.cStrToPrefixedFileW(old_path); const new_path_w = try windows.cStrToPrefixedFileW(new_path); @@ -2062,7 +2049,7 @@ pub fn renameatW( new_dir_fd: fd_t, new_path_w: []const u16, ReplaceIfExists: windows.BOOLEAN, -) RenameatError!void { +) RenameError!void { const src_fd = windows.OpenFile(old_path_w, .{ .dir = old_dir_fd, .access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE, @@ -2111,13 +2098,7 @@ pub fn renameatW( } } -pub const MakeDirAtError = error{ - /// WASI-only. This error occurs when the file descriptor does - /// not hold the required rights to create a new directory relative to it. - NotCapable, -} || MakeDirError; - -pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirAtError!void { +pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); @@ -2131,7 +2112,7 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirAtError pub const mkdiratC = @compileError("deprecated: renamed to mkdiratZ"); -pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirAtError!void { +pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) { wasi.ESUCCESS => return, wasi.EACCES => return error.AccessDenied, @@ -2148,12 +2129,12 @@ pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirAtE wasi.ENOSPC => return error.NoSpaceLeft, wasi.ENOTDIR => return error.NotDir, wasi.EROFS => return error.ReadOnlyFileSystem, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } -pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirAtError!void { +pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); @@ -2178,12 +2159,14 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirAtE } } -pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirAtError!void { +pub fn mkdiratW(dir_fd: fd_t, sub_path_w: [*:0]const u16, mode: u32) MakeDirError!void { const sub_dir_handle = try windows.CreateDirectoryW(dir_fd, sub_path_w, null); windows.CloseHandle(sub_dir_handle); } pub const MakeDirError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to create a new directory relative to it. AccessDenied, DiskQuota, PathAlreadyExists, @@ -2363,6 +2346,8 @@ pub fn fchdir(dirfd: fd_t) FchdirError!void { } pub const ReadLinkError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to read value of a symbolic link relative to it. AccessDenied, FileSystem, SymLinkLoop, @@ -2416,16 +2401,10 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 } } -pub const ReadLinkAtError = error{ - /// WASI-only. This error occurs when the file descriptor does - /// not hold the required rights to read value of a symbolic link relative to it. - NotCapable, -} || ReadLinkError; - /// Similar to `readlink` except reads value of a symbolink link **relative** to `dirfd` directory handle. /// The return value is a slice of `out_buffer` from index 0. /// See also `readlinkatWasi`, `realinkatZ` and `realinkatW`. -pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkAtError![]u8 { +pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .wasi) { return readlinkatWasi(dirfd, file_path, out_buffer); } @@ -2441,7 +2420,7 @@ pub const readlinkatC = @compileError("deprecated: renamed to readlinkatZ"); /// WASI-only. Same as `readlinkat` but targets WASI. /// See also `readlinkat`. -pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkAtError![]u8 { +pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { var bufused: usize = undefined; switch (wasi.path_readlink(dirfd, file_path.ptr, file_path.len, out_buffer.ptr, out_buffer.len, &bufused)) { wasi.ESUCCESS => return out_buffer[0..bufused], @@ -2454,20 +2433,20 @@ pub fn readlinkatWasi(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) Read wasi.ENOENT => return error.FileNotFound, wasi.ENOMEM => return error.SystemResources, wasi.ENOTDIR => return error.NotDir, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } /// Windows-only. Same as `readlinkat` except `file_path` is null-terminated, WTF16 encoded. /// See also `readlinkat`. -pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkAtError![]u8 { +pub fn readlinkatW(dirfd: fd_t, file_path: [*:0]const u16, out_buffer: []u8) ReadLinkError![]u8 { @compileError("TODO implement on Windows"); } /// Same as `readlinkat` except `file_path` is null-terminated. /// See also `readlinkat`. -pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkAtError![]u8 { +pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 { if (builtin.os.tag == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(file_path); return readlinkatW(dirfd, file_path_w.span().ptr, out_buffer); @@ -3132,11 +3111,10 @@ pub fn waitpid(pid: i32, flags: u32) u32 { pub const FStatError = error{ SystemResources, - AccessDenied, - /// WASI-only. This error occurs when the file descriptor does + /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to get its filestat information. - NotCapable, + AccessDenied, } || UnexpectedError; /// Return information about a file descriptor. @@ -3149,7 +3127,7 @@ pub fn fstat(fd: fd_t) FStatError!Stat { wasi.EBADF => unreachable, // Always a race condition. wasi.ENOMEM => return error.SystemResources, wasi.EACCES => return error.AccessDenied, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -3200,7 +3178,7 @@ pub fn fstatatWasi(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!S wasi.ENAMETOOLONG => return error.NameTooLong, wasi.ENOENT => return error.FileNotFound, wasi.ENOTDIR => return error.FileNotFound, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -3709,9 +3687,9 @@ pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { pub const SeekError = error{ Unseekable, - /// WASI-only. This error occurs when the file descriptor does + /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to seek on it. - NotCapable, + AccessDenied, } || UnexpectedError; /// Repositions read/write file offset relative to the beginning. @@ -3740,7 +3718,7 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -3782,7 +3760,7 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -3823,7 +3801,7 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -3864,7 +3842,7 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { wasi.EOVERFLOW => return error.Unseekable, wasi.ESPIPE => return error.Unseekable, wasi.ENXIO => return error.Unseekable, - wasi.ENOTCAPABLE => return error.NotCapable, + wasi.ENOTCAPABLE => return error.AccessDenied, else => |err| return unexpectedErrno(err), } } @@ -4012,7 +3990,6 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP if (builtin.os.tag == .linux and !builtin.link_libc) { const fd = openZ(pathname, linux.O_PATH | linux.O_NONBLOCK | linux.O_CLOEXEC, 0) catch |err| switch (err) { error.FileLocksNotSupported => unreachable, - error.NotCapable => unreachable, // WASI only else => |e| return e, }; defer close(fd); diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 166dbf4c4351a376d89b1751304e0ec0d18c8d4d..2b32e3962487d1fdd233537df0e270141bd94868 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -499,7 +499,6 @@ pub const NativeTargetInfo = struct { error.PipeBusy => unreachable, error.FileLocksNotSupported => unreachable, error.WouldBlock => unreachable, - error.NotCapable => unreachable, // we don't support WASI here (not yet at least) error.IsDir, error.NotDir, @@ -791,7 +790,6 @@ pub const NativeTargetInfo = struct { var it = mem.tokenize(rpath_list, ":"); while (it.next()) |rpath| { var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { - error.NotCapable => unreachable, // we don't support WASI here (not yet at least) error.NameTooLong => unreachable, error.InvalidUtf8 => unreachable, error.BadPathName => unreachable, @@ -819,7 +817,6 @@ pub const NativeTargetInfo = struct { &link_buf, ) catch |err| switch (err) { error.NameTooLong => unreachable, - error.NotCapable => unreachable, // we don't support WASI here (not yet at least) error.AccessDenied, error.FileNotFound, @@ -854,7 +851,6 @@ pub const NativeTargetInfo = struct { const len = file.pread(buf[i .. buf.len - i], offset + i) catch |err| switch (err) { error.OperationAborted => unreachable, // Windows-only error.WouldBlock => unreachable, // Did not request blocking mode - error.NotCapable => unreachable, // WASI only, and we don't support it here yet error.SystemResources => return error.SystemResources, error.IsDir => return error.UnableToReadElfFile, error.BrokenPipe => return error.UnableToReadElfFile, @@ -863,6 +859,7 @@ pub const NativeTargetInfo = struct { error.ConnectionTimedOut => return error.UnableToReadElfFile, error.Unexpected => return error.Unexpected, error.InputOutput => return error.FileSystem, + error.AccessDenied => return error.Unexpected, }; if (len == 0) return error.UnexpectedEndOfFile; i += len; diff --git a/src-self-hosted/stage2.zig b/src-self-hosted/stage2.zig index 60076b9cdbc7947700db4ce600deeb6fa601b959..bd24ffb399f1663f871fb92e2c53c99de0baf08c 100644 --- a/src-self-hosted/stage2.zig +++ b/src-self-hosted/stage2.zig @@ -163,7 +163,6 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error { error.OutOfMemory => return .OutOfMemory, error.Unexpected => return .Unexpected, error.InputOutput => return .FileSystem, - error.NotCapable => unreachable, // we should handle this when we're able to cross-compile Zig to WASI }; return .None; } @@ -607,7 +606,6 @@ export fn stage2_libc_parse(stage1_libc: *Stage2LibCInstallation, libc_file_z: [ error.NotDir => return .NotDir, error.DeviceBusy => return .DeviceBusy, error.FileLocksNotSupported => unreachable, - error.NotCapable => unreachable, // we should handle this when we're able to cross-compile Zig to WASI }; stage1_libc.initFromStage2(libc); return .None; @@ -651,7 +649,6 @@ export fn stage2_libc_render(stage1_libc: *Stage2LibCInstallation, output_file: error.AccessDenied => return .AccessDenied, error.Unexpected => return .Unexpected, error.InputOutput => return .FileSystem, - error.NotCapable => unreachable, // we should handle this when we're able to cross-compile Zig to WASI }; return .None; } -- 2.54.0 From f69875d85cc80e37df4572772ec1488ba67de262 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Jul 2020 21:56:30 +0000 Subject: [PATCH 158/295] build: -Dlib-files-only prevents self-hosted compiler from being built closes #5756 --- build.zig | 53 ++++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/build.zig b/build.zig index fd513de54fc0e5ec3dd9aa1879eabd69928434a1..3daef75a58befeb8da329614905ad4a72c827643 100644 --- a/build.zig +++ b/build.zig @@ -49,11 +49,6 @@ pub fn build(b: *Builder) !void { const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"}); - var exe = b.addExecutable("zig", "src-self-hosted/main.zig"); - exe.setBuildMode(mode); - test_step.dependOn(&exe.step); - b.default_step.dependOn(&exe.step); - const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release; const skip_release_fast = b.option(bool, "skip-release-fast", "Main test suite skips release-fast builds") orelse skip_release; @@ -63,29 +58,37 @@ pub fn build(b: *Builder) !void { const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse false; - if (enable_llvm) { - var ctx = parseConfigH(b, config_h_text); - ctx.llvm = try findLLVM(b, ctx.llvm_config_exe); - try configureStage2(b, exe, ctx); - } if (!only_install_lib_files) { - exe.install(); - } - const tracy = b.option([]const u8, "tracy", "Enable Tracy integration. Supply path to Tracy source"); - const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false; - if (link_libc) exe.linkLibC(); + var exe = b.addExecutable("zig", "src-self-hosted/main.zig"); + exe.setBuildMode(mode); + test_step.dependOn(&exe.step); + b.default_step.dependOn(&exe.step); - exe.addBuildOption(bool, "enable_tracy", tracy != null); - if (tracy) |tracy_path| { - const client_cpp = fs.path.join( - b.allocator, - &[_][]const u8{ tracy_path, "TracyClient.cpp" }, - ) catch unreachable; - exe.addIncludeDir(tracy_path); - exe.addCSourceFile(client_cpp, &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" }); - exe.linkSystemLibraryName("c++"); - exe.linkLibC(); + if (enable_llvm) { + var ctx = parseConfigH(b, config_h_text); + ctx.llvm = try findLLVM(b, ctx.llvm_config_exe); + + try configureStage2(b, exe, ctx); + } + if (!only_install_lib_files) { + exe.install(); + } + const tracy = b.option([]const u8, "tracy", "Enable Tracy integration. Supply path to Tracy source"); + const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false; + if (link_libc) exe.linkLibC(); + + exe.addBuildOption(bool, "enable_tracy", tracy != null); + if (tracy) |tracy_path| { + const client_cpp = fs.path.join( + b.allocator, + &[_][]const u8{ tracy_path, "TracyClient.cpp" }, + ) catch unreachable; + exe.addIncludeDir(tracy_path); + exe.addCSourceFile(client_cpp, &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" }); + exe.linkSystemLibraryName("c++"); + exe.linkLibC(); + } } b.installDirectory(InstallDirectoryOptions{ -- 2.54.0 From 7eed220924062aa68586278568df6731afb2a20e Mon Sep 17 00:00:00 2001 From: CodeMyst Date: Wed, 1 Jul 2020 21:44:18 +0200 Subject: [PATCH 159/295] in docs removed "path can be absolute" for imports --- doc/langref.html.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 95b7171c44a0d8692cb3721d69701bbb5767eb44..aef7b097988bd6e06abd1fb777f19ca71fc16583 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7530,7 +7530,7 @@ test "@hasDecl" { source file than the one they are declared in.

- {#syntax#}path{#endsyntax#} can be a relative or absolute path, or it can be the name of a package. + {#syntax#}path{#endsyntax#} can be a relative path or it can be the name of a package. If it is a relative path, it is relative to the file that contains the {#syntax#}@import{#endsyntax#} function call.

-- 2.54.0 From b8d5b3e6110023e17e1c191afa1d143f8b71fe4f Mon Sep 17 00:00:00 2001 From: Chris Watson Date: Tue, 30 Jun 2020 19:52:02 -0600 Subject: [PATCH 160/295] Add documentation for @src() builtin --- doc/langref.html.in | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/doc/langref.html.in b/doc/langref.html.in index aef7b097988bd6e06abd1fb777f19ca71fc16583..dfbb93decfb5529d0e114b07569c0a21bfd2e668 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8030,7 +8030,29 @@ test "vector @splat" {

{#see_also|Vectors|@shuffle#} {#header_close#} + {#header_open|@src#} +
{#syntax#}@src() std.builtin.SourceLocation{#endsyntax#}
+

+ Returns a {#syntax#}SourceLocation{#endsyntax#} struct representing the function's name and location in the source code. This must be called in a function. +

+ {#code_begin|test#} +const std = @import("std"); +const expect = std.testing.expect; +test "@src" { + doTheTest(); +} + +fn doTheTest() void { + const src = @src(); + + expect(src.line == 9); + expect(src.column == 17); + expect(std.mem.endsWith(u8, src.fn_name, "doTheTest")); + expect(std.mem.endsWith(u8, src.file, "test.zig")); +} + {#code_end#} + {#header_close#} {#header_open|@sqrt#}
{#syntax#}@sqrt(value: var) @TypeOf(value){#endsyntax#}

-- 2.54.0 From 30ae7f7573b7dcc3cd85f9e4cbab4e5608fdb3dd Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Wed, 1 Jul 2020 00:33:35 +0300 Subject: [PATCH 161/295] Corrected default value field initialization in std.zeroInit --- lib/std/mem.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 6bde83f782cb405dd913dd2fac602588cbbab8a8..08ecc5167fac65a81a35660f5bc953af236bc20d 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -720,8 +720,8 @@ pub fn zeroInit(comptime T: type, init: var) T { @field(value, field.name) = @field(init, field.name); }, } - } else if (field.default_value != null) { - @field(value, field.name) = field.default_value; + } else if (field.default_value) |default_value| { + @field(value, field.name) = default_value; } } @@ -748,7 +748,7 @@ test "zeroInit" { b: ?bool, c: I, e: [3]u8, - f: i64, + f: i64 = -1, }; const s = zeroInit(S, .{ @@ -762,7 +762,7 @@ test "zeroInit" { .d = 0, }, .e = [3]u8{ 0, 0, 0 }, - .f = 0, + .f = -1, }); } -- 2.54.0 From 8b82c40104802c9351b30ef7c5a41e7829ab6d2a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 1 Jul 2020 10:14:27 +0000 Subject: [PATCH 162/295] stage1: reimplement HashMap The indexes are stored separately using an array of uint8_t, uint16_t, uint32_t, or size_t, depending on the number of entries in the map. Entries only contain a key and a value, no longer have distance_from_start_index or is_used. In theory this should be both faster and use less memory. In practice it seems to have little to no effect. For the standard library tests, vs master branch, the time had no discernable difference, and it shaved off only 13 MiB of peak rss usage. --- src/hash_map.hpp | 323 +++++++++++++++++------------ test/stage1/behavior/type_info.zig | 3 +- 2 files changed, 197 insertions(+), 129 deletions(-) diff --git a/src/hash_map.hpp b/src/hash_map.hpp index 69e5568093eb6aa79f3d58ff7e6145cfd8bb13dc..45440d61e7ec189e03590882096a5fe3c01921ff 100644 --- a/src/hash_map.hpp +++ b/src/hash_map.hpp @@ -19,45 +19,64 @@ public: init_capacity(capacity); } void deinit(void) { - heap::c_allocator.deallocate(_entries, _capacity); + _entries.deinit(); + heap::c_allocator.deallocate(_index_bytes, + _indexes_len * capacity_index_size(_indexes_len)); } struct Entry { K key; V value; - bool used; - int distance_from_start_index; }; void clear() { - for (int i = 0; i < _capacity; i += 1) { - _entries[i].used = false; - } - _size = 0; + _entries.clear(); + memset(_index_bytes, 0, _indexes_len * capacity_index_size(_indexes_len)); _max_distance_from_start_index = 0; _modification_count += 1; } - int size() const { - return _size; + size_t size() const { + return _entries.length; } void put(const K &key, const V &value) { _modification_count += 1; - internal_put(key, value); - // if we get too full (60%), double the capacity - if (_size * 5 >= _capacity * 3) { - Entry *old_entries = _entries; - int old_capacity = _capacity; - init_capacity(_capacity * 2); - // dump all of the old elements into the new table - for (int i = 0; i < old_capacity; i += 1) { - Entry *old_entry = &old_entries[i]; - if (old_entry->used) - internal_put(old_entry->key, old_entry->value); + // if we would get too full (60%), double the indexes size + if ((_entries.length + 1) * 5 >= _indexes_len * 3) { + heap::c_allocator.deallocate(_index_bytes, + _indexes_len * capacity_index_size(_indexes_len)); + _indexes_len *= 2; + size_t sz = capacity_index_size(_indexes_len); + // This zero initializes the bytes, setting them all empty. + _index_bytes = heap::c_allocator.allocate(_indexes_len * sz); + _max_distance_from_start_index = 0; + for (size_t i = 0; i < _entries.length; i += 1) { + Entry *entry = &_entries.items[i]; + switch (sz) { + case 1: + put_index(key_to_index(entry->key), i, (uint8_t*)_index_bytes); + continue; + case 2: + put_index(key_to_index(entry->key), i, (uint16_t*)_index_bytes); + continue; + case 4: + put_index(key_to_index(entry->key), i, (uint32_t*)_index_bytes); + continue; + default: + put_index(key_to_index(entry->key), i, (size_t*)_index_bytes); + continue; + } } - heap::c_allocator.deallocate(old_entries, old_capacity); + } + + + switch (capacity_index_size(_indexes_len)) { + case 1: return internal_put(key, value, (uint8_t*)_index_bytes); + case 2: return internal_put(key, value, (uint16_t*)_index_bytes); + case 4: return internal_put(key, value, (uint32_t*)_index_bytes); + default: return internal_put(key, value, (size_t*)_index_bytes); } } @@ -81,40 +100,21 @@ public: return internal_get(key); } - void maybe_remove(const K &key) { - if (maybe_get(key)) { - remove(key); - } + bool remove(const K &key) { + bool deleted_something = maybe_remove(key); + if (!deleted_something) + zig_panic("key not found"); + return deleted_something; } - void remove(const K &key) { + bool maybe_remove(const K &key) { _modification_count += 1; - int start_index = key_to_index(key); - for (int roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) { - int index = (start_index + roll_over) % _capacity; - Entry *entry = &_entries[index]; - - if (!entry->used) - zig_panic("key not found"); - - if (!EqualFn(entry->key, key)) - continue; - - for (; roll_over < _capacity; roll_over += 1) { - int next_index = (start_index + roll_over + 1) % _capacity; - Entry *next_entry = &_entries[next_index]; - if (!next_entry->used || next_entry->distance_from_start_index == 0) { - entry->used = false; - _size -= 1; - return; - } - *entry = *next_entry; - entry->distance_from_start_index -= 1; - entry = next_entry; - } - zig_panic("shifting everything in the table"); + switch (capacity_index_size(_indexes_len)) { + case 1: return internal_remove(key, (uint8_t*)_index_bytes); + case 2: return internal_remove(key, (uint16_t*)_index_bytes); + case 4: return internal_remove(key, (uint32_t*)_index_bytes); + default: return internal_remove(key, (size_t*)_index_bytes); } - zig_panic("key not found"); } class Iterator { @@ -122,24 +122,16 @@ public: Entry *next() { if (_inital_modification_count != _table->_modification_count) zig_panic("concurrent modification"); - if (_count >= _table->size()) - return NULL; - for (; _index < _table->_capacity; _index += 1) { - Entry *entry = &_table->_entries[_index]; - if (entry->used) { - _index += 1; - _count += 1; - return entry; - } - } - zig_panic("no next item"); + if (_index >= _table->_entries.length) + return nullptr; + Entry *entry = &_table->_entries.items[_index]; + _index += 1; + return entry; } private: const HashMap * _table; - // how many items have we returned - int _count = 0; // iterator through the entry array - int _index = 0; + size_t _index = 0; // used to detect concurrent modification uint32_t _inital_modification_count; Iterator(const HashMap * table) : @@ -154,89 +146,166 @@ public: } private: + // Maintains insertion order. + ZigList _entries; + // If _indexes_len is less than 2**8, this is an array of uint8_t. + // If _indexes_len is less than 2**16, it is an array of uint16_t. + // If _indexes_len is less than 2**32, it is an array of uint32_t. + // Otherwise it is size_t. + // It's off by 1. 0 means empty slot, 1 means index 0, etc. + uint8_t *_index_bytes; + // This is the number of indexes. When indexes are bytes, it equals number of bytes. + // When indexes are uint16_t, _indexes_len is half the number of bytes. + size_t _indexes_len; - Entry *_entries; - int _capacity; - int _size; - int _max_distance_from_start_index; - // this is used to detect bugs where a hashtable is edited while an iterator is running. + size_t _max_distance_from_start_index; + // This is used to detect bugs where a hashtable is edited while an iterator is running. uint32_t _modification_count; - void init_capacity(int capacity) { - _capacity = capacity; - _entries = heap::c_allocator.allocate(_capacity); - _size = 0; + void init_capacity(size_t capacity) { + _entries = {}; + _entries.ensure_capacity(capacity); + // So that at capacity it will only be 60% full. + _indexes_len = capacity * 5 / 3; + size_t sz = capacity_index_size(_indexes_len); + // This zero initializes _index_bytes which sets them all to empty. + _index_bytes = heap::c_allocator.allocate(_indexes_len * sz); + _max_distance_from_start_index = 0; - for (int i = 0; i < _capacity; i += 1) { - _entries[i].used = false; - } + _modification_count = 0; } - void internal_put(K key, V value) { - int start_index = key_to_index(key); - for (int roll_over = 0, distance_from_start_index = 0; - roll_over < _capacity; roll_over += 1, distance_from_start_index += 1) - { - int index = (start_index + roll_over) % _capacity; - Entry *entry = &_entries[index]; + static size_t capacity_index_size(size_t len) { + if (len < UINT8_MAX) + return 1; + if (len < UINT16_MAX) + return 2; + if (len < UINT32_MAX) + return 4; + return sizeof(size_t); + } - if (entry->used && !EqualFn(entry->key, key)) { - if (entry->distance_from_start_index < distance_from_start_index) { - // robin hood to the rescue - Entry tmp = *entry; - if (distance_from_start_index > _max_distance_from_start_index) - _max_distance_from_start_index = distance_from_start_index; - *entry = { - key, - value, - true, - distance_from_start_index, - }; - key = tmp.key; - value = tmp.value; - distance_from_start_index = tmp.distance_from_start_index; - } - continue; + template + void internal_put(const K &key, const V &value, I *indexes) { + size_t start_index = key_to_index(key); + for (size_t roll_over = 0, distance_from_start_index = 0; + roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1) + { + size_t index_index = (start_index + roll_over) % _indexes_len; + I index_data = indexes[index_index]; + if (index_data == 0) { + _entries.append({key, value}); + indexes[index_index] = _entries.length; + if (distance_from_start_index > _max_distance_from_start_index) + _max_distance_from_start_index = distance_from_start_index; + return; } - - if (!entry->used) { - // adding an entry. otherwise overwriting old value with - // same key - _size += 1; + Entry *entry = &_entries.items[index_data - 1]; + if (EqualFn(entry->key, key)) { + *entry = {key, value}; + if (distance_from_start_index > _max_distance_from_start_index) + _max_distance_from_start_index = distance_from_start_index; + return; } - - if (distance_from_start_index > _max_distance_from_start_index) - _max_distance_from_start_index = distance_from_start_index; - *entry = { - key, - value, - true, - distance_from_start_index, - }; - return; } - zig_panic("put into a full HashMap"); + zig_unreachable(); } + template + void put_index(size_t start_index, size_t entry_index, I *indexes) { + for (size_t roll_over = 0, distance_from_start_index = 0; + roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1) + { + size_t index_index = (start_index + roll_over) % _indexes_len; + if (indexes[index_index] == 0) { + indexes[index_index] = entry_index + 1; + if (distance_from_start_index > _max_distance_from_start_index) + _max_distance_from_start_index = distance_from_start_index; + return; + } + } + zig_unreachable(); + } Entry *internal_get(const K &key) const { - int start_index = key_to_index(key); - for (int roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) { - int index = (start_index + roll_over) % _capacity; - Entry *entry = &_entries[index]; + switch (capacity_index_size(_indexes_len)) { + case 1: return internal_get2(key, (uint8_t*)_index_bytes); + case 2: return internal_get2(key, (uint16_t*)_index_bytes); + case 4: return internal_get2(key, (uint32_t*)_index_bytes); + default: return internal_get2(key, (size_t*)_index_bytes); + } + } - if (!entry->used) - return NULL; + template + Entry *internal_get2(const K &key, I *indexes) const { + size_t start_index = key_to_index(key); + for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) { + size_t index_index = (start_index + roll_over) % _indexes_len; + size_t index_data = indexes[index_index]; + if (index_data == 0) + return nullptr; + Entry *entry = &_entries.items[index_data - 1]; if (EqualFn(entry->key, key)) return entry; } - return NULL; + return nullptr; } - int key_to_index(const K &key) const { - return (int)(HashFunction(key) % ((uint32_t)_capacity)); + size_t key_to_index(const K &key) const { + return ((size_t)HashFunction(key)) % _indexes_len; + } + + template + bool internal_remove(const K &key, I *indexes) { + size_t start_index = key_to_index(key); + for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) { + size_t index_index = (start_index + roll_over) % _indexes_len; + size_t index_data = indexes[index_index]; + if (index_data == 0) + return false; + + size_t index = index_data - 1; + Entry *entry = &_entries.items[index]; + if (!EqualFn(entry->key, key)) + continue; + + indexes[index_index] = 0; + _entries.swap_remove(index); + if (_entries.length > 0 && _entries.length != index) { + // Because of the swap remove, now we need to update the index that was + // pointing to the last entry and is now pointing to this removed item slot. + update_entry_index(_entries.length, index, indexes); + } + + // Now we have to shift over the following indexes. + roll_over += 1; + for (; roll_over <= _max_distance_from_start_index; roll_over += 1) { + size_t next_index = (start_index + roll_over) % _indexes_len; + if (indexes[next_index] == 0) + break; + size_t next_start_index = key_to_index(_entries.items[indexes[next_index]].key); + if (next_start_index != start_index) + break; + indexes[next_index - 1] = indexes[next_index]; + } + + return true; + } + return false; + } + + template + void update_entry_index(size_t old_entry_index, size_t new_entry_index, I *indexes) { + size_t start_index = key_to_index(_entries.items[new_entry_index].key); + for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) { + size_t index_index = (start_index + roll_over) % _indexes_len; + if (indexes[index_index] == old_entry_index + 1) { + indexes[index_index] = new_entry_index + 1; + return; + } + } + zig_unreachable(); } }; - #endif diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index 68ff3aa31030f361962aa13ceb3345367f81f6a6..2685a3552e3e750edb7d9e5b5b0cd7db3a8292ed 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -251,14 +251,13 @@ fn testStruct() void { } const TestStruct = packed struct { - const Self = @This(); - fieldA: usize, fieldB: void, fieldC: *Self, fieldD: u32 = 4, pub fn foo(self: *const Self) void {} + const Self = @This(); }; test "type info: function type info" { -- 2.54.0 From 64bd134818b77ba56deb613112ec8cdb0c967703 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 30 Jun 2020 17:43:00 +0200 Subject: [PATCH 163/295] Add Dir.Iterator tests This commit adds some `std.fs.Dir.Iterator` tests. --- lib/std/fs/test.zig | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 52f823a32f8ced68385320f4653cf3182faab1cd..ea08b8f064da4f9174a4cc4a368addd1a84d166f 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -3,10 +3,47 @@ const testing = std.testing; const builtin = std.builtin; const fs = std.fs; const mem = std.mem; +const wasi = std.os.wasi; +const Dir = std.fs.Dir; const File = std.fs.File; const tmpDir = testing.tmpDir; +test "Dir.Iterator" { + var tmp_dir = tmpDir(.{ .iterate = true }); + defer tmp_dir.cleanup(); + + // First, create a couple of entries to iterate over. + const file = try tmp_dir.dir.createFile("some_file", .{}); + file.close(); + + try tmp_dir.dir.makeDir("some_dir"); + + // Create iterator. + var iter = tmp_dir.dir.iterate(); + var entries = std.ArrayList(Dir.Entry).init(testing.allocator); + defer entries.deinit(); + + while (try iter.next()) |entry| { + try entries.append(entry); + } + + testing.expect(entries.items.len == 2); // note that the Iterator skips '.' and '..' + testing.expect(contains(&entries, Dir.Entry{ .name = "some_file", .kind = Dir.Entry.Kind.File })); + testing.expect(contains(&entries, Dir.Entry{ .name = "some_dir", .kind = Dir.Entry.Kind.Directory })); +} + +fn entry_eql(lhs: Dir.Entry, rhs: Dir.Entry) bool { + return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind; +} + +fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool { + for (entries.items) |entry| { + if (entry_eql(entry, el)) return true; + } + return false; +} + test "readAllAlloc" { var tmp_dir = tmpDir(.{}); defer tmp_dir.cleanup(); @@ -237,7 +274,7 @@ test "fs.copyFile" { try expectFileContents(tmp.dir, dest_file2, data); } -fn expectFileContents(dir: fs.Dir, file_path: []const u8, data: []const u8) !void { +fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void { const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000); defer testing.allocator.free(contents); -- 2.54.0 From b5badd112288b528d75085d82d5f7f1b109e87dc Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 1 Jul 2020 14:15:58 +0200 Subject: [PATCH 164/295] Fix memory corruption in Dir.Iterator test --- lib/std/fs/test.zig | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index ea08b8f064da4f9174a4cc4a368addd1a84d166f..ddf4a282131c24be625470b1eeebac38293d2d94 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -5,6 +5,7 @@ const fs = std.fs; const mem = std.mem; const wasi = std.os.wasi; +const ArenaAllocator = std.heap.ArenaAllocator; const Dir = std.fs.Dir; const File = std.fs.File; const tmpDir = testing.tmpDir; @@ -19,13 +20,18 @@ test "Dir.Iterator" { try tmp_dir.dir.makeDir("some_dir"); + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + + var entries = std.ArrayList(Dir.Entry).init(&arena.allocator); + // Create iterator. var iter = tmp_dir.dir.iterate(); - var entries = std.ArrayList(Dir.Entry).init(testing.allocator); - defer entries.deinit(); - while (try iter.next()) |entry| { - try entries.append(entry); + // We cannot just store `entry` as on Windows, we're re-using the name buffer + // which means we'll actually share the `name` pointer between entries! + const name = try mem.dupe(&arena.allocator, u8, entry.name); + try entries.append(Dir.Entry{ .name = name, .kind = entry.kind }); } testing.expect(entries.items.len == 2); // note that the Iterator skips '.' and '..' -- 2.54.0 From 70cc1751ca69dd77625c703445713d067215b5d9 Mon Sep 17 00:00:00 2001 From: Ian Simonson Date: Wed, 24 Jun 2020 19:04:56 +1000 Subject: [PATCH 165/295] Translate-c fix rhs not cast on array access Closes #5671. Checks if the rhs is integral and of differing or the same signedness. If they are different does an @intCast to the lhs type --- src-self-hosted/translate_c.zig | 29 +++++++++---- test/run_translated_c.zig | 74 ++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index e492edb3795fc9e424b535529303e55873f4a2da..171846d380e7329c80d70bcd97da6b87ad6eeacc 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -3268,7 +3268,14 @@ fn transCreateCompoundAssign( const lhs = ZigClangCompoundAssignOperator_getLHS(stmt); const rhs = ZigClangCompoundAssignOperator_getRHS(stmt); const loc = ZigClangCompoundAssignOperator_getBeginLoc(stmt); - const is_signed = cIsSignedInteger(getExprQualType(rp.c, lhs)); + const lhs_qt = getExprQualType(rp.c, lhs); + const rhs_qt = getExprQualType(rp.c, rhs); + const is_signed = cIsSignedInteger(lhs_qt); + const requires_int_cast = blk: { + const are_integers = cIsInteger(lhs_qt) and cIsInteger(rhs_qt); + const are_same_sign = cIsSignedInteger(lhs_qt) == cIsSignedInteger(rhs_qt); + break :blk are_integers and !are_same_sign; + }; if (used == .unused) { // common case // c: lhs += rhs @@ -3295,15 +3302,18 @@ fn transCreateCompoundAssign( const lhs_node = try transExpr(rp, scope, lhs, .used, .l_value); const eq_token = try appendToken(rp.c, assign_tok_id, assign_bytes); - var rhs_node = if (is_shift) + var rhs_node = if (is_shift or requires_int_cast) try transExprCoercing(rp, scope, rhs, .used, .r_value) else try transExpr(rp, scope, rhs, .used, .r_value); - if (is_shift) { + if (is_shift or requires_int_cast) { const cast_node = try rp.c.createBuiltinCall("@intCast", 2); - const rhs_type = try qualTypeToLog2IntRef(rp, getExprQualType(rp.c, rhs), loc); - cast_node.params()[0] = rhs_type; + const cast_to_type = if (is_shift) + try qualTypeToLog2IntRef(rp, getExprQualType(rp.c, rhs), loc) + else + try transQualType(rp, getExprQualType(rp.c, lhs), loc); + cast_node.params()[0] = cast_to_type; _ = try appendToken(rp.c, .Comma, ","); cast_node.params()[1] = rhs_node; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); @@ -3358,10 +3368,13 @@ fn transCreateCompoundAssign( const bin_token = try appendToken(rp.c, bin_tok_id, bin_bytes); var rhs_node = try transExpr(rp, scope, rhs, .used, .r_value); - if (is_shift) { + if (is_shift or requires_int_cast) { const cast_node = try rp.c.createBuiltinCall("@intCast", 2); - const rhs_type = try qualTypeToLog2IntRef(rp, getExprQualType(rp.c, rhs), loc); - cast_node.params()[0] = rhs_type; + const cast_to_type = if (is_shift) + try qualTypeToLog2IntRef(rp, getExprQualType(rp.c, rhs), loc) + else + try transQualType(rp, getExprQualType(rp.c, lhs), loc); + cast_node.params()[0] = cast_to_type; _ = try appendToken(rp.c, .Comma, ","); cast_node.params()[1] = rhs_node; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index c7f7c2c5c66f90e4147d3ed2255a8a6535c1d2c8..efdc9702a43659f77d4b6ae4fe3233cd27d4e1f3 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -268,5 +268,77 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\ if (count != 4) abort(); \\ return 0; \\} - ,""); + , ""); + + cases.add("array value type casts properly", + \\#include + \\unsigned int choose[53][10]; + \\static int hash_binary(int k) + \\{ + \\ choose[0][k] = 3; + \\ int sum = 0; + \\ sum += choose[0][k]; + \\ return sum; + \\} + \\ + \\int main() { + \\ int s = hash_binary(4); + \\ if (s != 3) abort(); + \\ return 0; + \\} + , ""); + + cases.add("array value type casts properly use +=", + \\#include + \\static int hash_binary(int k) + \\{ + \\ unsigned int choose[1][1] = {{3}}; + \\ int sum = -1; + \\ int prev = 0; + \\ prev = sum += choose[0][0]; + \\ if (sum != 2) abort(); + \\ return sum + prev; + \\} + \\ + \\int main() { + \\ int x = hash_binary(4); + \\ if (x != 4) abort(); + \\ return 0; + \\} + , ""); + + cases.add("ensure array casts outisde +=", + \\#include + \\static int hash_binary(int k) + \\{ + \\ unsigned int choose[3] = {1, 2, 3}; + \\ int sum = -2; + \\ int prev = sum + choose[k]; + \\ if (prev != 0) abort(); + \\ return sum + prev; + \\} + \\ + \\int main() { + \\ int x = hash_binary(1); + \\ if (x != -2) abort(); + \\ return 0; + \\} + , ""); + + cases.add("array cast int to uint", + \\#include + \\static unsigned int hash_binary(int k) + \\{ + \\ int choose[3] = {-1, -2, 3}; + \\ unsigned int sum = 2; + \\ sum += choose[k]; + \\ return sum; + \\} + \\ + \\int main() { + \\ unsigned int x = hash_binary(1); + \\ if (x != 0) abort(); + \\ return 0; + \\} + , ""); } -- 2.54.0 From e7d02eae4d99fbae11e91ddd7601e5ac2a392292 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 2 Jul 2020 20:54:57 +0200 Subject: [PATCH 166/295] Update lib/std/fs/test.zig Co-authored-by: Joachim Schmidt --- lib/std/fs/test.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index ddf4a282131c24be625470b1eeebac38293d2d94..a3cf2e800268cba5c8a4317f01843ddf8364d39a 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -30,7 +30,7 @@ test "Dir.Iterator" { while (try iter.next()) |entry| { // We cannot just store `entry` as on Windows, we're re-using the name buffer // which means we'll actually share the `name` pointer between entries! - const name = try mem.dupe(&arena.allocator, u8, entry.name); + const name = try arena.allocator.dupe(u8, entry.name); try entries.append(Dir.Entry{ .name = name, .kind = entry.kind }); } -- 2.54.0 From df2c27eb486383a291dfe1db3dfda51f830f59c5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Jul 2020 22:33:52 +0000 Subject: [PATCH 167/295] stage1 HashMap: store hash & do robin hood hashing This adds these two fields to a HashMap Entry: uint32_t hash uint32_t distance_from_start_index Compared to master branch, standard library tests compiled 8.4% faster and took negligible (0.001%) more memory to complete. The amount of memory used is still down from before 8b82c4010480 which moved indexes to be stored separately from entries. So, it turns out, keeping robin hood hashing plus separating indexes did result in a performance improvement. What happened previously is that the gains from separating indexes balanced out the losses from removing robin hood hashing, resulting in a wash. This also serves as an inspiration for adding a benchmark to std.AutoHashMap and improving the implementation. --- src/hash_map.hpp | 129 ++++++++++++++++++++++++++++++++++++----------- src/list.hpp | 3 ++ 2 files changed, 102 insertions(+), 30 deletions(-) diff --git a/src/hash_map.hpp b/src/hash_map.hpp index 45440d61e7ec189e03590882096a5fe3c01921ff..ce5369eac84a10bc9af4c0f328cd6d56532c8664 100644 --- a/src/hash_map.hpp +++ b/src/hash_map.hpp @@ -25,6 +25,8 @@ public: } struct Entry { + uint32_t hash; + uint32_t distance_from_start_index; K key; V value; }; @@ -56,21 +58,24 @@ public: Entry *entry = &_entries.items[i]; switch (sz) { case 1: - put_index(key_to_index(entry->key), i, (uint8_t*)_index_bytes); + put_index(entry, i, (uint8_t*)_index_bytes); continue; case 2: - put_index(key_to_index(entry->key), i, (uint16_t*)_index_bytes); + put_index(entry, i, (uint16_t*)_index_bytes); continue; case 4: - put_index(key_to_index(entry->key), i, (uint32_t*)_index_bytes); + put_index(entry, i, (uint32_t*)_index_bytes); continue; default: - put_index(key_to_index(entry->key), i, (size_t*)_index_bytes); + put_index(entry, i, (size_t*)_index_bytes); continue; } } } + // This allows us to take a pointer to an entry in `internal_put` which + // will not become a dead pointer when the array list is appended. + _entries.ensure_capacity(_entries.length + 1); switch (capacity_index_size(_indexes_len)) { case 1: return internal_put(key, value, (uint8_t*)_index_bytes); @@ -187,42 +192,99 @@ private: template void internal_put(const K &key, const V &value, I *indexes) { - size_t start_index = key_to_index(key); - for (size_t roll_over = 0, distance_from_start_index = 0; - roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1) + uint32_t hash = HashFunction(key); + uint32_t distance_from_start_index = 0; + size_t start_index = hash_to_index(hash); + for (size_t roll_over = 0; roll_over < _indexes_len; + roll_over += 1, distance_from_start_index += 1) { size_t index_index = (start_index + roll_over) % _indexes_len; I index_data = indexes[index_index]; if (index_data == 0) { - _entries.append({key, value}); + _entries.append_assuming_capacity({ hash, distance_from_start_index, key, value }); indexes[index_index] = _entries.length; if (distance_from_start_index > _max_distance_from_start_index) _max_distance_from_start_index = distance_from_start_index; return; } + // This pointer survives the following append because we call + // _entries.ensure_capacity before internal_put. Entry *entry = &_entries.items[index_data - 1]; - if (EqualFn(entry->key, key)) { - *entry = {key, value}; + if (entry->hash == hash && EqualFn(entry->key, key)) { + *entry = {hash, distance_from_start_index, key, value}; if (distance_from_start_index > _max_distance_from_start_index) _max_distance_from_start_index = distance_from_start_index; return; } + if (entry->distance_from_start_index < distance_from_start_index) { + // In this case, we did not find the item. We will put a new entry. + // However, we will use this index for the new entry, and move + // the previous index down the line, to keep the _max_distance_from_start_index + // as small as possible. + _entries.append_assuming_capacity({ hash, distance_from_start_index, key, value }); + indexes[index_index] = _entries.length; + if (distance_from_start_index > _max_distance_from_start_index) + _max_distance_from_start_index = distance_from_start_index; + + distance_from_start_index = entry->distance_from_start_index; + + // Find somewhere to put the index we replaced by shifting + // following indexes backwards. + roll_over += 1; + distance_from_start_index += 1; + for (; roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1) { + size_t index_index = (start_index + roll_over) % _indexes_len; + I next_index_data = indexes[index_index]; + if (next_index_data == 0) { + if (distance_from_start_index > _max_distance_from_start_index) + _max_distance_from_start_index = distance_from_start_index; + entry->distance_from_start_index = distance_from_start_index; + indexes[index_index] = index_data; + return; + } + Entry *next_entry = &_entries.items[next_index_data - 1]; + if (next_entry->distance_from_start_index < distance_from_start_index) { + if (distance_from_start_index > _max_distance_from_start_index) + _max_distance_from_start_index = distance_from_start_index; + entry->distance_from_start_index = distance_from_start_index; + indexes[index_index] = index_data; + distance_from_start_index = next_entry->distance_from_start_index; + entry = next_entry; + index_data = next_index_data; + } + } + zig_unreachable(); + } } zig_unreachable(); } template - void put_index(size_t start_index, size_t entry_index, I *indexes) { + void put_index(Entry *entry, size_t entry_index, I *indexes) { + size_t start_index = hash_to_index(entry->hash); + size_t index_data = entry_index + 1; for (size_t roll_over = 0, distance_from_start_index = 0; roll_over < _indexes_len; roll_over += 1, distance_from_start_index += 1) { size_t index_index = (start_index + roll_over) % _indexes_len; - if (indexes[index_index] == 0) { - indexes[index_index] = entry_index + 1; + size_t next_index_data = indexes[index_index]; + if (next_index_data == 0) { if (distance_from_start_index > _max_distance_from_start_index) _max_distance_from_start_index = distance_from_start_index; + entry->distance_from_start_index = distance_from_start_index; + indexes[index_index] = index_data; return; } + Entry *next_entry = &_entries.items[next_index_data - 1]; + if (next_entry->distance_from_start_index < distance_from_start_index) { + if (distance_from_start_index > _max_distance_from_start_index) + _max_distance_from_start_index = distance_from_start_index; + entry->distance_from_start_index = distance_from_start_index; + indexes[index_index] = index_data; + distance_from_start_index = next_entry->distance_from_start_index; + entry = next_entry; + index_data = next_index_data; + } } zig_unreachable(); } @@ -238,7 +300,8 @@ private: template Entry *internal_get2(const K &key, I *indexes) const { - size_t start_index = key_to_index(key); + uint32_t hash = HashFunction(key); + size_t start_index = hash_to_index(hash); for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) { size_t index_index = (start_index + roll_over) % _indexes_len; size_t index_data = indexes[index_index]; @@ -246,19 +309,20 @@ private: return nullptr; Entry *entry = &_entries.items[index_data - 1]; - if (EqualFn(entry->key, key)) + if (entry->hash == hash && EqualFn(entry->key, key)) return entry; } return nullptr; } - size_t key_to_index(const K &key) const { - return ((size_t)HashFunction(key)) % _indexes_len; + size_t hash_to_index(uint32_t hash) const { + return ((size_t)hash) % _indexes_len; } template bool internal_remove(const K &key, I *indexes) { - size_t start_index = key_to_index(key); + uint32_t hash = HashFunction(key); + size_t start_index = hash_to_index(hash); for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) { size_t index_index = (start_index + roll_over) % _indexes_len; size_t index_data = indexes[index_index]; @@ -267,10 +331,10 @@ private: size_t index = index_data - 1; Entry *entry = &_entries.items[index]; - if (!EqualFn(entry->key, key)) + if (entry->hash != hash || !EqualFn(entry->key, key)) continue; - indexes[index_index] = 0; + size_t prev_index = index_index; _entries.swap_remove(index); if (_entries.length > 0 && _entries.length != index) { // Because of the swap remove, now we need to update the index that was @@ -280,24 +344,29 @@ private: // Now we have to shift over the following indexes. roll_over += 1; - for (; roll_over <= _max_distance_from_start_index; roll_over += 1) { + for (; roll_over < _indexes_len; roll_over += 1) { size_t next_index = (start_index + roll_over) % _indexes_len; - if (indexes[next_index] == 0) - break; - size_t next_start_index = key_to_index(_entries.items[indexes[next_index]].key); - if (next_start_index != start_index) - break; - indexes[next_index - 1] = indexes[next_index]; + if (indexes[next_index] == 0) { + indexes[prev_index] = 0; + return true; + } + Entry *next_entry = &_entries.items[indexes[next_index] - 1]; + if (next_entry->distance_from_start_index == 0) { + indexes[prev_index] = 0; + return true; + } + indexes[prev_index] = indexes[next_index]; + prev_index = next_index; + next_entry->distance_from_start_index -= 1; } - - return true; + zig_unreachable(); } return false; } template void update_entry_index(size_t old_entry_index, size_t new_entry_index, I *indexes) { - size_t start_index = key_to_index(_entries.items[new_entry_index].key); + size_t start_index = hash_to_index(_entries.items[new_entry_index].hash); for (size_t roll_over = 0; roll_over <= _max_distance_from_start_index; roll_over += 1) { size_t index_index = (start_index + roll_over) % _indexes_len; if (indexes[index_index] == old_entry_index + 1) { diff --git a/src/list.hpp b/src/list.hpp index 2c6d90c8554afb923a599e75330f596eee8e282e..803a2514371d17c9a228169eb38a2c5269b0bf6b 100644 --- a/src/list.hpp +++ b/src/list.hpp @@ -19,6 +19,9 @@ struct ZigList { ensure_capacity(length + 1); items[length++] = item; } + void append_assuming_capacity(const T& item) { + items[length++] = item; + } // remember that the pointer to this item is invalid after you // modify the length of the list const T & at(size_t index) const { -- 2.54.0 From 22f0a103c39f84140ee1fbfe2bffed5fcec19a26 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Jul 2020 03:49:03 +0000 Subject: [PATCH 168/295] stage1 HashMap: linear scan for < 16 entries --- src/hash_map.hpp | 58 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/hash_map.hpp b/src/hash_map.hpp index ce5369eac84a10bc9af4c0f328cd6d56532c8664..8681e5b7613453b1d1b3446e363d257581e8dc4b 100644 --- a/src/hash_map.hpp +++ b/src/hash_map.hpp @@ -45,6 +45,26 @@ public: void put(const K &key, const V &value) { _modification_count += 1; + // This allows us to take a pointer to an entry in `internal_put` which + // will not become a dead pointer when the array list is appended. + _entries.ensure_capacity(_entries.length + 1); + + if (_index_bytes == nullptr) { + if (_entries.length < 16) { + _entries.append({HashFunction(key), 0, key, value}); + return; + } else { + _indexes_len = 32; + _index_bytes = heap::c_allocator.allocate(_indexes_len); + _max_distance_from_start_index = 0; + for (size_t i = 0; i < _entries.length; i += 1) { + Entry *entry = &_entries.items[i]; + put_index(entry, i, _index_bytes); + } + return internal_put(key, value, _index_bytes); + } + } + // if we would get too full (60%), double the indexes size if ((_entries.length + 1) * 5 >= _indexes_len * 3) { heap::c_allocator.deallocate(_index_bytes, @@ -73,10 +93,6 @@ public: } } - // This allows us to take a pointer to an entry in `internal_put` which - // will not become a dead pointer when the array list is appended. - _entries.ensure_capacity(_entries.length + 1); - switch (capacity_index_size(_indexes_len)) { case 1: return internal_put(key, value, (uint8_t*)_index_bytes); case 2: return internal_put(key, value, (uint16_t*)_index_bytes); @@ -114,6 +130,16 @@ public: bool maybe_remove(const K &key) { _modification_count += 1; + if (_index_bytes == nullptr) { + uint32_t hash = HashFunction(key); + for (size_t i = 0; i < _entries.length; i += 1) { + if (_entries.items[i].hash == hash && EqualFn(_entries.items[i].key, key)) { + _entries.swap_remove(i); + return true; + } + } + return false; + } switch (capacity_index_size(_indexes_len)) { case 1: return internal_remove(key, (uint8_t*)_index_bytes); case 2: return internal_remove(key, (uint16_t*)_index_bytes); @@ -170,11 +196,16 @@ private: void init_capacity(size_t capacity) { _entries = {}; _entries.ensure_capacity(capacity); - // So that at capacity it will only be 60% full. - _indexes_len = capacity * 5 / 3; - size_t sz = capacity_index_size(_indexes_len); - // This zero initializes _index_bytes which sets them all to empty. - _index_bytes = heap::c_allocator.allocate(_indexes_len * sz); + _indexes_len = 0; + if (capacity >= 16) { + // So that at capacity it will only be 60% full. + _indexes_len = capacity * 5 / 3; + size_t sz = capacity_index_size(_indexes_len); + // This zero initializes _index_bytes which sets them all to empty. + _index_bytes = heap::c_allocator.allocate(_indexes_len * sz); + } else { + _index_bytes = nullptr; + } _max_distance_from_start_index = 0; _modification_count = 0; @@ -290,6 +321,15 @@ private: } Entry *internal_get(const K &key) const { + if (_index_bytes == nullptr) { + uint32_t hash = HashFunction(key); + for (size_t i = 0; i < _entries.length; i += 1) { + if (_entries.items[i].hash == hash && EqualFn(_entries.items[i].key, key)) { + return &_entries.items[i]; + } + } + return nullptr; + } switch (capacity_index_size(_indexes_len)) { case 1: return internal_get2(key, (uint8_t*)_index_bytes); case 2: return internal_get2(key, (uint16_t*)_index_bytes); -- 2.54.0 From 9a7f05378f4497d1ebe1ee6f5ca17390121665a9 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Fri, 17 Apr 2020 17:22:00 -0700 Subject: [PATCH 169/295] fix https://github.com/ziglang/zig/issues/4799 --- CMakeLists.txt | 7 +++++++ README.md | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94219c16315e4b621a5275b244ad4b47bf400121..6deeafd235b4d311a31335e6ea19dd20fef68459 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ set(ZIG_STATIC off CACHE BOOL "Attempt to build a static zig executable (not com set(ZIG_STATIC_LLVM off CACHE BOOL "Prefer linking against static LLVM libraries") set(ZIG_ENABLE_MEM_PROFILE off CACHE BOOL "Activate memory usage instrumentation") set(ZIG_PREFER_CLANG_CPP_DYLIB off CACHE BOOL "Try to link against -lclang-cpp") +set(ZIG_WORKAROUND_4799 off CACHE BOOL "workaround for https://github.com/ziglang/zig/issues/4799") set(ZIG_USE_CCACHE off CACHE BOOL "Use ccache if available") if(CCACHE_PROGRAM AND ZIG_USE_CCACHE) @@ -88,6 +89,11 @@ if(APPLE AND ZIG_STATIC) list(APPEND LLVM_LIBRARIES "${ZLIB}") endif() +if(APPLE AND ZIG_WORKAROUND_4799) + # eg: ${CMAKE_PREFIX_PATH} could be /usr/local/opt/llvm/ + list(APPEND LLVM_LIBRARIES "-Wl,${CMAKE_PREFIX_PATH}/lib/libPolly.a" "-Wl,${CMAKE_PREFIX_PATH}/lib/libPollyPPCG.a" "-Wl,${CMAKE_PREFIX_PATH}/lib/libPollyISL.a") +endif() + set(ZIG_CPP_LIB_DIR "${CMAKE_BINARY_DIR}/zig_cpp") # Handle multi-config builds and place each into a common lib. The VS generator @@ -397,6 +403,7 @@ add_library(zig_cpp STATIC ${ZIG_CPP_SOURCES}) set_target_properties(zig_cpp PROPERTIES COMPILE_FLAGS ${EXE_CFLAGS} ) + target_link_libraries(zig_cpp LINK_PUBLIC ${CLANG_LIBRARIES} ${LLD_LIBRARIES} diff --git a/README.md b/README.md index e7ed9b746df77953f1f902a2cda7a98daaa0e73a..5bc59840d6f389e9ff240ae9b1f6ea4b452a4d71 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,13 @@ brew outdated llvm || brew upgrade llvm mkdir build cd build cmake .. -DCMAKE_PREFIX_PATH=$(brew --prefix llvm) -make install +make -j install ``` You will now run into this issue: [homebrew and llvm 10 packages in apt.llvm.org are broken with undefined reference to getPollyPluginInfo](https://github.com/ziglang/zig/issues/4799) +or this https://github.com/ziglang/zig/issues/5055, in which case try `-DZIG_WORKAROUND_4799=ON` + Please help upstream LLVM and Homebrew solve this issue, there is nothing Zig can do about it. See that issue for a workaround you can do in the meantime. -- 2.54.0 From 14e07e9a72ca9e1d01e64998d40705979fa0c456 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Jul 2020 04:43:05 +0000 Subject: [PATCH 170/295] clean up readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5bc59840d6f389e9ff240ae9b1f6ea4b452a4d71..ddafd68057424eced7319bc2053eaa2402c02078 100644 --- a/README.md +++ b/README.md @@ -61,16 +61,16 @@ brew outdated llvm || brew upgrade llvm mkdir build cd build cmake .. -DCMAKE_PREFIX_PATH=$(brew --prefix llvm) -make -j install +make install ``` You will now run into this issue: [homebrew and llvm 10 packages in apt.llvm.org are broken with undefined reference to getPollyPluginInfo](https://github.com/ziglang/zig/issues/4799) -or this https://github.com/ziglang/zig/issues/5055, in which case try `-DZIG_WORKAROUND_4799=ON` +or +[error: unable to create target: 'Unable to find target for this triple (no targets are registered)'](https://github.com/ziglang/zig/issues/5055), +in which case try `-DZIG_WORKAROUND_4799=ON` - -Please help upstream LLVM and Homebrew solve this issue, there is nothing Zig -can do about it. See that issue for a workaround you can do in the meantime. +Hopefully this will be fixed upstream with LLVM 10.0.1. ##### Windows -- 2.54.0 From f281b928d995ff68f4115fb5a9b7aa10f8c60322 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Jul 2020 04:48:48 +0000 Subject: [PATCH 171/295] cmake: add -DZIG_WORKAROUND_POLLY_SO to work around #4799 until a newer llvm version is released. --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6deeafd235b4d311a31335e6ea19dd20fef68459..46791855731a47a6fa62590ffba3d91703033d16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ set(ZIG_STATIC_LLVM off CACHE BOOL "Prefer linking against static LLVM libraries set(ZIG_ENABLE_MEM_PROFILE off CACHE BOOL "Activate memory usage instrumentation") set(ZIG_PREFER_CLANG_CPP_DYLIB off CACHE BOOL "Try to link against -lclang-cpp") set(ZIG_WORKAROUND_4799 off CACHE BOOL "workaround for https://github.com/ziglang/zig/issues/4799") +set(ZIG_WORKAROUND_POLLY_SO off CACHE STRING "workaround for https://github.com/ziglang/zig/issues/4799") set(ZIG_USE_CCACHE off CACHE BOOL "Use ccache if available") if(CCACHE_PROGRAM AND ZIG_USE_CCACHE) @@ -409,6 +410,9 @@ target_link_libraries(zig_cpp LINK_PUBLIC ${LLD_LIBRARIES} ${LLVM_LIBRARIES} ) +if(ZIG_WORKAROUND_POLLY_SO) + target_link_libraries(zig_cpp LINK_PUBLIC "-Wl,${ZIG_WORKAROUND_POLLY_SO}") +endif() add_library(opt_c_util STATIC ${OPTIMIZED_C_SOURCES}) set_target_properties(opt_c_util PROPERTIES -- 2.54.0 From 65aac132571a887d181120ba37197e3e4d931c9c Mon Sep 17 00:00:00 2001 From: pfg Date: Fri, 3 Jul 2020 18:15:01 -0700 Subject: [PATCH 172/295] don't try to find config_h if it's not needed --- build.zig | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/build.zig b/build.zig index 3daef75a58befeb8da329614905ad4a72c827643..f9cd1b0dea68a475d70d4a3e3a3d2d48b6ce83c3 100644 --- a/build.zig +++ b/build.zig @@ -34,15 +34,6 @@ pub fn build(b: *Builder) !void { const test_step = b.step("test", "Run all the tests"); - const config_h_text = if (b.option( - []const u8, - "config_h", - "Path to the generated config.h", - )) |config_h_path| - try std.fs.cwd().readFileAlloc(b.allocator, toNativePathSep(b, config_h_path), max_config_h_bytes) - else - try findAndReadConfigH(b); - var test_stage2 = b.addTest("src-self-hosted/test.zig"); test_stage2.setBuildMode(.Debug); // note this is only the mode of the test harness test_stage2.addPackagePath("stage2_tests", "test/stage2/test.zig"); @@ -58,6 +49,7 @@ pub fn build(b: *Builder) !void { const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse false; + const config_h_path_option = b.option([]const u8, "config_h", "Path to the generated config.h"); if (!only_install_lib_files) { var exe = b.addExecutable("zig", "src-self-hosted/main.zig"); @@ -66,6 +58,11 @@ pub fn build(b: *Builder) !void { b.default_step.dependOn(&exe.step); if (enable_llvm) { + const config_h_text = if (config_h_path_option) |config_h_path| + try std.fs.cwd().readFileAlloc(b.allocator, toNativePathSep(b, config_h_path), max_config_h_bytes) + else + try findAndReadConfigH(b); + var ctx = parseConfigH(b, config_h_text); ctx.llvm = try findLLVM(b, ctx.llvm_config_exe); -- 2.54.0 From 672b4d5c24ab6c4d8ddf1ec7a1a7753a2d21dc87 Mon Sep 17 00:00:00 2001 From: heidezomp Date: Thu, 2 Jul 2020 17:22:04 +0200 Subject: [PATCH 173/295] zig build --help: Consistent capitalization/punctuation --- lib/std/build.zig | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index df1dc6d73a61bc0f46f7120698b1129fb3c6b0a1..dc8487cc09387328990a5aca2cc7c72044c14154 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -512,7 +512,7 @@ pub const Builder = struct { if (self.release_mode != null) { @panic("setPreferredReleaseMode must be called before standardReleaseOptions and may not be called twice"); } - const description = self.fmt("create a release build ({})", .{@tagName(mode)}); + const description = self.fmt("Create a release build ({})", .{@tagName(mode)}); self.is_release = self.option(bool, "release", description) orelse false; self.release_mode = if (self.is_release) mode else builtin.Mode.Debug; } @@ -522,9 +522,9 @@ pub const Builder = struct { pub fn standardReleaseOptions(self: *Builder) builtin.Mode { if (self.release_mode) |mode| return mode; - const release_safe = self.option(bool, "release-safe", "optimizations on and safety on") orelse false; - const release_fast = self.option(bool, "release-fast", "optimizations on and safety off") orelse false; - const release_small = self.option(bool, "release-small", "size optimizations on and safety off") orelse false; + const release_safe = self.option(bool, "release-safe", "Optimizations on and safety on") orelse false; + const release_fast = self.option(bool, "release-fast", "Optimizations on and safety off") orelse false; + const release_small = self.option(bool, "release-small", "Size optimizations on and safety off") orelse false; const mode = if (release_safe and !release_fast and !release_small) builtin.Mode.ReleaseSafe @@ -555,7 +555,7 @@ pub const Builder = struct { const triple = self.option( []const u8, "target", - "The CPU architecture, OS, and ABI to build for.", + "The CPU architecture, OS, and ABI to build for", ) orelse return args.default_target; // TODO add cpu and features as part of the target triple -- 2.54.0 From 0ae1157e4553d6f54e0d489daebb006c402e0f63 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sat, 4 Jul 2020 13:44:28 +0200 Subject: [PATCH 174/295] std.mem.dupe is deprecated, move all references in std Replaced all occurences of std.mem.dupe in stdlib with Allocator.dupe/std.mem.dupeZ -> Allocator.dupeZ --- lib/std/buf_map.zig | 2 +- lib/std/build.zig | 2 +- lib/std/cache_hash.zig | 2 +- lib/std/fs.zig | 6 +++--- lib/std/fs/path.zig | 4 ++-- lib/std/fs/watch.zig | 2 +- lib/std/http/headers.zig | 4 ++-- lib/std/json.zig | 4 ++-- lib/std/math/big/int.zig | 2 +- lib/std/net.zig | 2 +- lib/std/priority_queue.zig | 2 +- lib/std/process.zig | 10 +++++----- lib/std/zig/cross_target.zig | 2 +- lib/std/zig/system.zig | 2 +- 14 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/std/buf_map.zig b/lib/std/buf_map.zig index e8bc735b572c45b3bed6efc05ee88054e2816987..71d8a218fae2a3e80c632896f260869dd42fdf7b 100644 --- a/lib/std/buf_map.zig +++ b/lib/std/buf_map.zig @@ -79,7 +79,7 @@ pub const BufMap = struct { } fn copy(self: BufMap, value: []const u8) ![]u8 { - return mem.dupe(self.hash_map.allocator, u8, value); + return self.hash_map.allocator.dupe(u8, value); } }; diff --git a/lib/std/build.zig b/lib/std/build.zig index dc8487cc09387328990a5aca2cc7c72044c14154..46d4d4baaeb26193d1a218886a6aaef42cf1a624 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -286,7 +286,7 @@ pub const Builder = struct { } pub fn dupe(self: *Builder, bytes: []const u8) []u8 { - return mem.dupe(self.allocator, u8, bytes) catch unreachable; + return self.allocator.dupe(u8, bytes) catch unreachable; } pub fn dupePath(self: *Builder, bytes: []const u8) []u8 { diff --git a/lib/std/cache_hash.zig b/lib/std/cache_hash.zig index d160c4ebb22b494ef913640c2917d65709ee7552..257f407826a9efbf96d2a2001316436c50597a9e 100644 --- a/lib/std/cache_hash.zig +++ b/lib/std/cache_hash.zig @@ -207,7 +207,7 @@ pub const CacheHash = struct { } if (cache_hash_file.path == null) { - cache_hash_file.path = try mem.dupe(self.allocator, u8, file_path); + cache_hash_file.path = try self.allocator.dupe(u8, file_path); } const this_file = fs.cwd().openFile(cache_hash_file.path.?, .{ .read = true }) catch { diff --git a/lib/std/fs.zig b/lib/std/fs.zig index e932253bdba90161969edd023a58a77c161a43ee..3c0ae8f2651195ab005ee827238ea1943ac6b150 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -1825,7 +1825,7 @@ pub fn selfExePathAlloc(allocator: *Allocator) ![]u8 { // TODO(#4812): Investigate other systems and whether it is possible to get // this path by trying larger and larger buffers until one succeeds. var buf: [MAX_PATH_BYTES]u8 = undefined; - return mem.dupe(allocator, u8, try selfExePath(&buf)); + return allocator.dupe(u8, try selfExePath(&buf)); } /// Get the path to the current executable. @@ -1888,7 +1888,7 @@ pub fn selfExeDirPathAlloc(allocator: *Allocator) ![]u8 { // TODO(#4812): Investigate other systems and whether it is possible to get // this path by trying larger and larger buffers until one succeeds. var buf: [MAX_PATH_BYTES]u8 = undefined; - return mem.dupe(allocator, u8, try selfExeDirPath(&buf)); + return allocator.dupe(u8, try selfExeDirPath(&buf)); } /// Get the directory path that contains the current executable. @@ -1910,7 +1910,7 @@ pub fn realpathAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 { // paths. musl supports passing NULL but restricts the output to PATH_MAX // anyway. var buf: [MAX_PATH_BYTES]u8 = undefined; - return mem.dupe(allocator, u8, try os.realpath(pathname, &buf)); + return allocator.dupe(u8, try os.realpath(pathname, &buf)); } test "" { diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index 34326f28726be3ddd3633284709f5e636ac09549..2176498feb938cf7f0f9008755bbb331097b1a30 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -1034,7 +1034,7 @@ pub fn relativeWindows(allocator: *Allocator, from: []const u8, to: []const u8) var from_it = mem.tokenize(resolved_from, "/\\"); var to_it = mem.tokenize(resolved_to, "/\\"); while (true) { - const from_component = from_it.next() orelse return mem.dupe(allocator, u8, to_it.rest()); + const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest()); const to_rest = to_it.rest(); if (to_it.next()) |to_component| { // TODO ASCII is wrong, we actually need full unicode support to compare paths. @@ -1085,7 +1085,7 @@ pub fn relativePosix(allocator: *Allocator, from: []const u8, to: []const u8) ![ var from_it = mem.tokenize(resolved_from, "/"); var to_it = mem.tokenize(resolved_to, "/"); while (true) { - const from_component = from_it.next() orelse return mem.dupe(allocator, u8, to_it.rest()); + const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest()); const to_rest = to_it.rest(); if (to_it.next()) |to_component| { if (mem.eql(u8, from_component, to_component)) diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig index 8c5a9837353f8e6b3695c9106b3673067b4f5a92..b161e45c71adb797d668199af9638707b7d10440 100644 --- a/lib/std/fs/watch.zig +++ b/lib/std/fs/watch.zig @@ -360,7 +360,7 @@ pub fn Watch(comptime V: type) type { fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V { // TODO we might need to convert dirname and basename to canonical file paths ("short"?) - const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse "."); + const dirname = try self.allocator.dupe(u8, std.fs.path.dirname(file_path) orelse "."); var dirname_consumed = false; defer if (!dirname_consumed) self.allocator.free(dirname); diff --git a/lib/std/http/headers.zig b/lib/std/http/headers.zig index ba929a446c1a4e382835ecafc73838a6011accd5..571e746631aeb7c6743fa0d8dfbf1cfdd48d7ec7 100644 --- a/lib/std/http/headers.zig +++ b/lib/std/http/headers.zig @@ -38,7 +38,7 @@ const HeaderEntry = struct { return Self{ .allocator = allocator, .name = name, // takes reference - .value = try mem.dupe(allocator, u8, value), + .value = try allocator.dupe(u8, value), .never_index = never_index orelse never_index_default(name), }; } @@ -161,7 +161,7 @@ pub const Headers = struct { var dex = &kv.value; try dex.append(n - 1); } else { - const name_dup = try mem.dupe(self.allocator, u8, name); + const name_dup = try self.allocator.dupe(u8, name); errdefer self.allocator.free(name_dup); entry = try HeaderEntry.init(self.allocator, name_dup, value, never_index); errdefer entry.deinit(); diff --git a/lib/std/json.zig b/lib/std/json.zig index 6377b69a80445338193f5dac9a16ef4f6ab897d4..20eed05737d8898b0bd6741e4e03d42602bf60de 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -1567,7 +1567,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options: if (ptrInfo.child != u8) return error.UnexpectedToken; const source_slice = stringToken.slice(tokens.slice, tokens.i - 1); switch (stringToken.escapes) { - .None => return mem.dupe(allocator, u8, source_slice), + .None => return allocator.dupe(u8, source_slice), .Some => |some_escapes| { const output = try allocator.alloc(u8, stringToken.decodedLength()); errdefer allocator.free(output); @@ -2043,7 +2043,7 @@ pub const Parser = struct { fn parseString(p: *Parser, allocator: *Allocator, s: std.meta.TagPayloadType(Token, Token.String), input: []const u8, i: usize) !Value { const slice = s.slice(input, i); switch (s.escapes) { - .None => return Value{ .String = if (p.copy_strings) try mem.dupe(allocator, u8, slice) else slice }, + .None => return Value{ .String = if (p.copy_strings) try allocator.dupe(u8, slice) else slice }, .Some => |some_escapes| { const output = try allocator.alloc(u8, s.decodedLength()); errdefer allocator.free(output); diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 9379f881db8fcde7f7271864a6e154c1461aa5ce..85e14bc55ca91656955b82d051123be06f241d70 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -1105,7 +1105,7 @@ pub const Const = struct { assert(base <= 16); if (self.eqZero()) { - return mem.dupe(allocator, u8, "0"); + return allocator.dupe(u8, "0"); } const string = try allocator.alloc(u8, self.sizeInBaseUpperBound(base)); errdefer allocator.free(string); diff --git a/lib/std/net.zig b/lib/std/net.zig index 229731b6176b58663958c3dcf1ebb29bd89efbae..a9dcf53f9218ece06c1c9fefd95dcfc7093a339e 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -682,7 +682,7 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* if (info.canonname) |n| { if (result.canon_name == null) { - result.canon_name = try mem.dupe(arena, u8, mem.spanZ(n)); + result.canon_name = try arena.dupe(u8, mem.spanZ(n)); } } i += 1; diff --git a/lib/std/priority_queue.zig b/lib/std/priority_queue.zig index dfd2379da2cd497a1af2632b946d840af7059e09..69b9e06a5be906461b808bca900219734039e65e 100644 --- a/lib/std/priority_queue.zig +++ b/lib/std/priority_queue.zig @@ -333,7 +333,7 @@ test "std.PriorityQueue: addSlice" { test "std.PriorityQueue: fromOwnedSlice" { const items = [_]u32{ 15, 7, 21, 14, 13, 22, 12, 6, 7, 25, 5, 24, 11, 16, 15, 24, 2, 1 }; - const heap_items = try std.mem.dupe(testing.allocator, u32, items[0..]); + const heap_items = try testing.allocator.dupe(u32, items[0..]); var queue = PQ.fromOwnedSlice(testing.allocator, lessThan, heap_items[0..]); defer queue.deinit(); diff --git a/lib/std/process.zig b/lib/std/process.zig index 520b219f979e290a2ec08478abf1fb2f3db79d15..1a0dfad474f037b7ee33b8075ab37fec403f4069 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -30,7 +30,7 @@ pub fn getCwdAlloc(allocator: *Allocator) ![]u8 { var current_buf: []u8 = &stack_buf; while (true) { if (os.getcwd(current_buf)) |slice| { - return mem.dupe(allocator, u8, slice); + return allocator.dupe(u8, slice); } else |err| switch (err) { error.NameTooLong => { // The path is too long to fit in stack_buf. Allocate geometrically @@ -169,7 +169,7 @@ pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwned }; } else { const result = os.getenv(key) orelse return error.EnvironmentVariableNotFound; - return mem.dupe(allocator, u8, result); + return allocator.dupe(u8, result); } } @@ -436,7 +436,7 @@ pub const ArgIterator = struct { if (builtin.os.tag == .windows) { return self.inner.next(allocator); } else { - return mem.dupe(allocator, u8, self.inner.next() orelse return null); + return allocator.dupe(u8, self.inner.next() orelse return null); } } @@ -718,7 +718,7 @@ pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0] fn callback(info: *os.dl_phdr_info, size: usize, list: *List) !void { const name = info.dlpi_name orelse return; if (name[0] == '/') { - const item = try mem.dupeZ(list.allocator, u8, mem.spanZ(name)); + const item = try list.allocator.dupeZ(u8, mem.spanZ(name)); errdefer list.allocator.free(item); try list.append(item); } @@ -739,7 +739,7 @@ pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0] var i: u32 = 0; while (i < img_count) : (i += 1) { const name = std.c._dyld_get_image_name(i); - const item = try mem.dupeZ(allocator, u8, mem.spanZ(name)); + const item = try allocator.dupeZ(u8, mem.spanZ(name)); errdefer allocator.free(item); try paths.append(item); } diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index 1909a07df0ad5631b7c40744b459c153214af691..910d263c6a6f3d770220d38f9203408fc2e91852 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -497,7 +497,7 @@ pub const CrossTarget = struct { pub fn zigTriple(self: CrossTarget, allocator: *mem.Allocator) error{OutOfMemory}![]u8 { if (self.isNative()) { - return mem.dupe(allocator, u8, "native"); + return allocator.dupe(u8, "native"); } const arch_name = if (self.cpu_arch) |arch| @tagName(arch) else "native"; diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 2b32e3962487d1fdd233537df0e270141bd94868..898d376cc7622fc7f845cafdef3f24a3098b72db 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -161,7 +161,7 @@ pub const NativePaths = struct { } fn appendArray(self: *NativePaths, array: *ArrayList([:0]u8), s: []const u8) !void { - const item = try std.mem.dupeZ(array.allocator, u8, s); + const item = try array.allocator.dupeZ(u8, s); errdefer array.allocator.free(item); try array.append(item); } -- 2.54.0 From 1d52438bd54adcb5cf5dfb7b664a5d4c3bcaee03 Mon Sep 17 00:00:00 2001 From: pfg Date: Sat, 4 Jul 2020 01:58:35 -0700 Subject: [PATCH 175/295] stage2: InfixOp add --- src-self-hosted/Module.zig | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 7a61cd5ccdee421991f8abbea459c7ee03af5048..da5e575587957b5e7910fd00b40b7351bbefde7f 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1317,10 +1317,28 @@ fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir .Unreachable => return self.astGenUnreachable(scope, @fieldParentPtr(ast.Node.Unreachable, "base", ast_node)), .ControlFlowExpression => return self.astGenControlFlowExpression(scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)), .If => return self.astGenIf(scope, @fieldParentPtr(ast.Node.If, "base", ast_node)), + .InfixOp => return self.astGenInfixOp(scope, @fieldParentPtr(ast.Node.InfixOp, "base", ast_node)), else => return self.failNode(scope, ast_node, "TODO implement astGenExpr for {}", .{@tagName(ast_node.id)}), } } +fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) InnerError!*zir.Inst { + switch (infix_node.op) { + .Add => { + const lhs = try self.astGenExpr(scope, infix_node.lhs); + const rhs = try self.astGenExpr(scope, infix_node.rhs); + + const tree = scope.tree(); + const src = tree.token_locs[infix_node.op_token].start; + + return self.addZIRInst(scope, src, zir.Inst.Add, .{ .lhs = lhs, .rhs = rhs }, .{}); + }, + else => |op| { + return self.failNode(scope, &infix_node.base, "TODO implement infix operator {}", .{op}); + }, + } +} + fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.Inst { if (if_node.payload) |payload| { return self.failNode(scope, payload, "TODO implement astGenIf for optionals", .{}); @@ -2933,7 +2951,9 @@ fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError! const lhs = try self.resolveInst(scope, inst.positionals.lhs); const rhs = try self.resolveInst(scope, inst.positionals.rhs); - if (lhs.ty.zigTypeTag() == .Int and rhs.ty.zigTypeTag() == .Int) { + if ((lhs.ty.zigTypeTag() == .Int or lhs.ty.zigTypeTag() == .ComptimeInt) and + (rhs.ty.zigTypeTag() == .Int or rhs.ty.zigTypeTag() == .ComptimeInt)) + { if (!lhs.ty.eql(rhs.ty)) { return self.fail(scope, inst.base.src, "TODO implement peer type resolution", .{}); } @@ -2977,8 +2997,7 @@ fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError! .rhs = rhs, }); } - - return self.fail(scope, inst.base.src, "TODO implement more analyze add", .{}); + return self.fail(scope, inst.base.src, "TODO analyze add for {} + {}", .{ lhs.ty.zigTypeTag(), rhs.ty.zigTypeTag() }); } fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *zir.Inst.Deref) InnerError!*Inst { -- 2.54.0 From d4456d92f55aea0273d0d55df2946d50e2aa22c8 Mon Sep 17 00:00:00 2001 From: pfg Date: Sat, 4 Jul 2020 02:27:12 -0700 Subject: [PATCH 176/295] stage2: builtin @as --- src-self-hosted/Module.zig | 7 ++++++- src-self-hosted/zir.zig | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index da5e575587957b5e7910fd00b40b7351bbefde7f..5fc1f7d67e24d3d56edb685a7cdd1158919b5460 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1534,7 +1534,12 @@ fn astGenBuiltinCall(self: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) const arg_count: ?usize = if (positionals.fields[0].field_type == []*zir.Inst) null else positionals.fields.len; if (arg_count) |some| { if (call.params_len != some) { - return self.failTok(scope, call.builtin_token, "expected {} parameter, found {}", .{ some, call.params_len }); + return self.failTok( + scope, + call.builtin_token, + "expected {} parameter{}, found {}", + .{ some, if (some == 1) "" else "s", call.params_len }, + ); } const params = call.params(); inline for (positionals.fields) |p, i| { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 92dbc66e2b9f928f9dca1dcfee5e3c0a93645ff1..7b77575f55ed0e5028999787570bf9f12beef0f3 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -290,6 +290,7 @@ pub const Inst = struct { pub const As = struct { pub const base_tag = Tag.as; + pub const builtin_name = "@as"; base: Inst, positionals: struct { -- 2.54.0 From 4a63189bf1275aed3f3f170e46eff10b15e61157 Mon Sep 17 00:00:00 2001 From: pfg Date: Sat, 4 Jul 2020 15:30:17 -0700 Subject: [PATCH 177/295] stage2: add and @as tests --- test/stage2/compare_output.zig | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index d49f16876ef0d1c34a9e44acb94d59bfb6cf2c6a..1f2853707a3e045fcfe5cb7d4add303c0e8f75fd 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -118,4 +118,29 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); } + + { + var case = ctx.exe("adding numbers", linux_x64); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (1), + \\ [arg1] "{rdi}" (1), + \\ [arg2] "{rsi}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{rdx}" (10 + 4) + \\ : "rcx", "r11", "memory" + \\ ); + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (@as(usize, 230) + @as(usize, 1)), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + } } -- 2.54.0 From 51f8c306d9fa32c849b26c2d95a45901a02f448e Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Sun, 5 Jul 2020 17:58:21 +0200 Subject: [PATCH 178/295] stage1: add missing runtime safety for @intCast unsigned -> signed of same bit count --- src/codegen.cpp | 10 ++++++---- test/runtime_safety.zig | 10 ++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 18ef7d918261313def94e6dcb43d429b9b9ce981..2f72861bc2a9dd4e66136f4006ffdc749e012b1c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1535,9 +1535,11 @@ static LLVMValueRef gen_widen_or_shorten(CodeGen *g, bool want_runtime_safety, Z zig_unreachable(); } - if (actual_type->id == ZigTypeIdInt && - !wanted_type->data.integral.is_signed && actual_type->data.integral.is_signed && - want_runtime_safety) + if (actual_type->id == ZigTypeIdInt && want_runtime_safety && ( + // negative to unsigned + (!wanted_type->data.integral.is_signed && actual_type->data.integral.is_signed) || + // unsigned would become negative + (wanted_type->data.integral.is_signed && !actual_type->data.integral.is_signed && actual_bits == wanted_bits))) { LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, actual_type)); LLVMValueRef ok_bit = LLVMBuildICmp(g->builder, LLVMIntSGE, expr_val, zero, ""); @@ -1547,7 +1549,7 @@ static LLVMValueRef gen_widen_or_shorten(CodeGen *g, bool want_runtime_safety, Z LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); LLVMPositionBuilderAtEnd(g->builder, fail_block); - gen_safety_crash(g, PanicMsgIdCastNegativeToUnsigned); + gen_safety_crash(g, actual_type->data.integral.is_signed ? PanicMsgIdCastNegativeToUnsigned : PanicMsgIdCastTruncatedData); LLVMPositionBuilderAtEnd(g->builder, ok_block); } diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index e60d115a006e72c8f7b74ad704560f75f14b7df2..e27ae7e16ecf16140d70258520b7af64b11115b4 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -757,6 +757,16 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\} ); + cases.addRuntimeSafety("unsigned integer not fitting in cast to signed integer - same bit count", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\pub fn main() void { + \\ var value: u8 = 245; + \\ var casted = @intCast(i8, value); + \\} + ); + cases.addRuntimeSafety("unwrap error", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ if (@import("std").mem.eql(u8, message, "attempt to unwrap error: Whatever")) { -- 2.54.0 From b8553b4813e971e50dcae7b8181e5f6771f3dbff Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Sun, 5 Jul 2020 20:44:08 +0200 Subject: [PATCH 179/295] compiler-rt: fix bugs uncovered by previous commit --- lib/std/special/compiler_rt/clzsi2_test.zig | 2 +- lib/std/special/compiler_rt/int.zig | 2 +- lib/std/special/compiler_rt/udivmod.zig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/special/compiler_rt/clzsi2_test.zig b/lib/std/special/compiler_rt/clzsi2_test.zig index ff944558460c5f5f90ed052fa3779733b4fb87d1..8a2896fcb6cac6e8dd0bd56f67104bea914b82b6 100644 --- a/lib/std/special/compiler_rt/clzsi2_test.zig +++ b/lib/std/special/compiler_rt/clzsi2_test.zig @@ -4,7 +4,7 @@ const testing = @import("std").testing; fn test__clzsi2(a: u32, expected: i32) void { var nakedClzsi2 = clzsi2.__clzsi2; var actualClzsi2 = @ptrCast(fn (a: i32) callconv(.C) i32, nakedClzsi2); - var x = @intCast(i32, a); + var x = @bitCast(i32, a); var result = actualClzsi2(x); testing.expectEqual(expected, result); } diff --git a/lib/std/special/compiler_rt/int.zig b/lib/std/special/compiler_rt/int.zig index eb731ee89814dfc400c53a2d8e3b415c84d08525..a72c13e2331e6c2b1bc705a2405fbfe334acebe0 100644 --- a/lib/std/special/compiler_rt/int.zig +++ b/lib/std/special/compiler_rt/int.zig @@ -244,7 +244,7 @@ pub fn __udivsi3(n: u32, d: u32) callconv(.C) u32 { // r.all -= d.all; // carry = 1; // } - const s = @intCast(i32, d -% r -% 1) >> @intCast(u5, n_uword_bits - 1); + const s = @bitCast(i32, d -% r -% 1) >> @intCast(u5, n_uword_bits - 1); carry = @intCast(u32, s & 1); r -= d & @bitCast(u32, s); } diff --git a/lib/std/special/compiler_rt/udivmod.zig b/lib/std/special/compiler_rt/udivmod.zig index 7d519c34cb6cfd56a1d723758d9b91d5a01a6715..ba53d1dee01cf9b00358cdb568b9e43ef0c02e87 100644 --- a/lib/std/special/compiler_rt/udivmod.zig +++ b/lib/std/special/compiler_rt/udivmod.zig @@ -184,7 +184,7 @@ pub fn udivmod(comptime DoubleInt: type, a: DoubleInt, b: DoubleInt, maybe_rem: // carry = 1; // } r_all = @ptrCast(*align(@alignOf(SingleInt)) DoubleInt, &r[0]).*; // TODO issue #421 - const s: SignedDoubleInt = @intCast(SignedDoubleInt, b -% r_all -% 1) >> (DoubleInt.bit_count - 1); + const s: SignedDoubleInt = @bitCast(SignedDoubleInt, b -% r_all -% 1) >> (DoubleInt.bit_count - 1); carry = @intCast(u32, s & 1); r_all -= b & @bitCast(DoubleInt, s); r = @ptrCast(*[2]SingleInt, &r_all).*; // TODO issue #421 -- 2.54.0 From b3b6ccba50ef7a683ad05546cba2b71e7d10489f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 3 Jul 2020 23:57:24 +0000 Subject: [PATCH 180/295] reimplement std.HashMap * breaking changes to the API. Some of the weird decisions from before are changed to what would be more expected. - `get` returns `?V`, use `getEntry` for the old API. - `put` returns `!void`, use `fetchPut` for the old API. * HashMap now has a comptime parameter of whether to store hashes with entries. AutoHashMap has heuristics on whether to set this parameter. For example, for integers, it is false, since equality checking is cheap, but for strings, it is true, since equality checking is probably expensive. * The implementation has a separate array for entry_index / distance_from_start_index. Entries no longer has holes; it is an ArrayList, and iteration is simpler and more cache coherent. This is inspired by Python's new dictionaries. * HashMap is separated into an "unmanaged" and a "managed" API. The unmanaged API is where the actual implementation is; the managed API wraps it and provides a more convenient API, storing the allocator. * Memory usage: When there are less than or equal to 8 entries, HashMap now incurs only a single pointer-size integer as overhead, opposed to using an ArrayList. * Since the entries array is separate from the indexes array, the holes in the indexes array take up less room than the holes in the entries array otherwise would. However the entries array also allocates additional capacity for appending into the array. * HashMap now maintains insertion order. Deletion performs a "swap remove". It's now possible to modify the HashMap while iterating. --- lib/std/array_list.zig | 16 + lib/std/debug.zig | 2 +- lib/std/hash_map.zig | 1146 +++++++++++++++++++++++++++------------- 3 files changed, 810 insertions(+), 354 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index b57d051d2b6c1560db53f44a5a1f853d06e034eb..a68c1fa9d6873f914e1fac4902a3fcc95968bf06 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -210,6 +210,14 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { self.capacity = new_len; } + /// Reduce length to `new_len`. + /// Invalidates element pointers. + /// Keeps capacity the same. + pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { + assert(new_len <= self.items.len); + self.items.len = new_len; + } + pub fn ensureCapacity(self: *Self, new_capacity: usize) !void { var better_capacity = self.capacity; if (better_capacity >= new_capacity) return; @@ -432,6 +440,14 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ self.capacity = new_len; } + /// Reduce length to `new_len`. + /// Invalidates element pointers. + /// Keeps capacity the same. + pub fn shrinkRetainingCapacity(self: *Self, new_len: usize) void { + assert(new_len <= self.items.len); + self.items.len = new_len; + } + pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void { var better_capacity = self.capacity; if (better_capacity >= new_capacity) return; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 92b79be35cacd0ad5af869bf705d9e301fabfebb..e9bafec94c967a8a93a38a6117595b97e0bdd8e6 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1278,7 +1278,7 @@ pub const DebugInfo = struct { else => return error.MissingDebugInfo, } - if (self.address_map.getValue(ctx.base_address)) |obj_di| { + if (self.address_map.get(ctx.base_address)) |obj_di| { return obj_di; } diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index bcd4280153e74ccb237a755b829a863297b2ec70..4b91a83ba2dbab22e1d7a62d68d2d460aba4a8b5 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -9,17 +9,15 @@ const autoHash = std.hash.autoHash; const Wyhash = std.hash.Wyhash; const Allocator = mem.Allocator; const builtin = @import("builtin"); - -const want_modification_safety = std.debug.runtime_safety; -const debug_u32 = if (want_modification_safety) u32 else void; +const hash_map = @This(); pub fn AutoHashMap(comptime K: type, comptime V: type) type { - return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K)); + return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K)); } /// Builtin hashmap for strings as keys. pub fn StringHashMap(comptime V: type) type { - return HashMap([]const u8, V, hashString, eqlString); + return HashMap([]const u8, V, hashString, eqlString, true); } pub fn eqlString(a: []const u8, b: []const u8) bool { @@ -30,422 +28,846 @@ pub fn hashString(s: []const u8) u32 { return @truncate(u32, std.hash.Wyhash.hash(0, s)); } -pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u32, comptime eql: fn (a: K, b: K) bool) type { +/// Insertion order is preserved. +/// Deletions perform a "swap removal" on the entries list. +/// Modifying the hash map while iterating is allowed, however one must understand +/// the (well defined) behavior when mixing insertions and deletions with iteration. +/// For a hash map that can be initialized directly that does not store an Allocator +/// field, see `HashMapUnmanaged`. +/// When `store_hash` is `false`, this data structure is biased towards cheap `eql` +/// functions. It does not store each item's hash in the table. Setting `store_hash` +/// to `true` incurs slightly more memory cost by storing each key's hash in the table +/// but only has to call `eql` for hash collisions. +pub fn HashMap( + comptime K: type, + comptime V: type, + comptime hash: fn (key: K) u32, + comptime eql: fn (a: K, b: K) bool, + comptime store_hash: bool, +) type { return struct { - entries: []Entry, - size: usize, - max_distance_from_start_index: usize, + unmanaged: Unmanaged, allocator: *Allocator, - /// This is used to detect bugs where a hashtable is edited while an iterator is running. - modification_count: debug_u32, - - const Self = @This(); - - /// A *KV is a mutable pointer into this HashMap's internal storage. - /// Modifying the key is undefined behavior. - /// Modifying the value is harmless. - /// *KV pointers become invalid whenever this HashMap is modified, - /// and then any access to the *KV is undefined behavior. - pub const KV = struct { - key: K, - value: V, - }; - - const Entry = struct { - used: bool, - distance_from_start_index: usize, - kv: KV, - }; - - pub const GetOrPutResult = struct { - kv: *KV, - found_existing: bool, - }; + pub const Unmanaged = HashMapUnmanaged(K, V, hash, eql, store_hash); + pub const Entry = Unmanaged.Entry; + pub const Hash = Unmanaged.Hash; + pub const GetOrPutResult = Unmanaged.GetOrPutResult; + /// Deprecated. Iterate using `items`. pub const Iterator = struct { hm: *const Self, - // how many items have we returned - count: usize, - // iterator through the entry array + /// Iterator through the entry array. index: usize, - // used to detect concurrent modification - initial_modification_count: debug_u32, - pub fn next(it: *Iterator) ?*KV { - if (want_modification_safety) { - assert(it.initial_modification_count == it.hm.modification_count); // concurrent modification - } - if (it.count >= it.hm.size) return null; - while (it.index < it.hm.entries.len) : (it.index += 1) { - const entry = &it.hm.entries[it.index]; - if (entry.used) { - it.index += 1; - it.count += 1; - return &entry.kv; - } - } - unreachable; // no next item + pub fn next(it: *Iterator) ?*Entry { + if (it.index >= it.hm.unmanaged.entries.items.len) return null; + const result = &it.hm.unmanaged.entries.items[it.index]; + it.index += 1; + return result; } - // Reset the iterator to the initial index + /// Reset the iterator to the initial index pub fn reset(it: *Iterator) void { - it.count = 0; it.index = 0; - // Resetting the modification count too - it.initial_modification_count = it.hm.modification_count; } }; + const Self = @This(); + const Index = Unmanaged.Index; + pub fn init(allocator: *Allocator) Self { - return Self{ - .entries = &[_]Entry{}, + return .{ + .unmanaged = .{}, .allocator = allocator, - .size = 0, - .max_distance_from_start_index = 0, - .modification_count = if (want_modification_safety) 0 else {}, }; } - pub fn deinit(hm: Self) void { - hm.allocator.free(hm.entries); + pub fn deinit(self: *Self) void { + self.unmanaged.deinit(self.allocator); + self.* = undefined; } - pub fn clear(hm: *Self) void { - for (hm.entries) |*entry| { - entry.used = false; - } - hm.size = 0; - hm.max_distance_from_start_index = 0; - hm.incrementModificationCount(); + pub fn clearRetainingCapacity(self: *Self) void { + return self.unmanaged.clearRetainingCapacity(); } + pub fn clearAndFree(self: *Self, allocator: *Allocator) void { + return self.unmanaged.clearAndFree(self.allocator); + } + + /// Deprecated. Use `items().len`. pub fn count(self: Self) usize { - return self.size; + return self.items().len; } - /// If key exists this function cannot fail. - /// If there is an existing item with `key`, then the result - /// kv pointer points to it, and found_existing is true. - /// Otherwise, puts a new item with undefined value, and - /// the kv pointer points to it. Caller should then initialize - /// the data. - pub fn getOrPut(self: *Self, key: K) !GetOrPutResult { - // TODO this implementation can be improved - we should only - // have to hash once and find the entry once. - if (self.get(key)) |kv| { - return GetOrPutResult{ - .kv = kv, - .found_existing = true, - }; - } - self.incrementModificationCount(); - try self.autoCapacity(); - const put_result = self.internalPut(key); - assert(put_result.old_kv == null); - return GetOrPutResult{ - .kv = &put_result.new_entry.kv, - .found_existing = false, - }; - } - - pub fn getOrPutValue(self: *Self, key: K, value: V) !*KV { - const res = try self.getOrPut(key); - if (!res.found_existing) - res.kv.value = value; - - return res.kv; - } - - fn optimizedCapacity(expected_count: usize) usize { - // ensure that the hash map will be at most 60% full if - // expected_count items are put into it - var optimized_capacity = expected_count * 5 / 3; - // an overflow here would mean the amount of memory required would not - // be representable in the address space - return math.ceilPowerOfTwo(usize, optimized_capacity) catch unreachable; - } - - /// Increases capacity so that the hash map will be at most - /// 60% full when expected_count items are put into it - pub fn ensureCapacity(self: *Self, expected_count: usize) !void { - if (expected_count == 0) return; - const optimized_capacity = optimizedCapacity(expected_count); - return self.ensureCapacityExact(optimized_capacity); - } - - /// Sets the capacity to the new capacity if the new - /// capacity is greater than the current capacity. - /// New capacity must be a power of two. - fn ensureCapacityExact(self: *Self, new_capacity: usize) !void { - // capacity must always be a power of two to allow for modulo - // optimization in the constrainIndex fn - assert(math.isPowerOfTwo(new_capacity)); - - if (new_capacity <= self.entries.len) { - return; - } - - const old_entries = self.entries; - try self.initCapacity(new_capacity); - self.incrementModificationCount(); - if (old_entries.len > 0) { - // dump all of the old elements into the new table - for (old_entries) |*old_entry| { - if (old_entry.used) { - self.internalPut(old_entry.kv.key).new_entry.kv.value = old_entry.kv.value; - } - } - self.allocator.free(old_entries); - } - } - - /// Returns the kv pair that was already there. - pub fn put(self: *Self, key: K, value: V) !?KV { - try self.autoCapacity(); - return putAssumeCapacity(self, key, value); - } - - /// Calls put() and asserts that no kv pair is clobbered. - pub fn putNoClobber(self: *Self, key: K, value: V) !void { - assert((try self.put(key, value)) == null); - } - - pub fn putAssumeCapacity(self: *Self, key: K, value: V) ?KV { - assert(self.count() < self.entries.len); - self.incrementModificationCount(); - - const put_result = self.internalPut(key); - put_result.new_entry.kv.value = value; - return put_result.old_kv; - } - - pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { - assert(self.putAssumeCapacity(key, value) == null); - } - - pub fn get(hm: *const Self, key: K) ?*KV { - if (hm.entries.len == 0) { - return null; - } - return hm.internalGet(key); - } - - pub fn getValue(hm: *const Self, key: K) ?V { - return if (hm.get(key)) |kv| kv.value else null; - } - - pub fn contains(hm: *const Self, key: K) bool { - return hm.get(key) != null; - } - - /// Returns any kv pair that was removed. - pub fn remove(hm: *Self, key: K) ?KV { - if (hm.entries.len == 0) return null; - hm.incrementModificationCount(); - const start_index = hm.keyToIndex(key); - { - var roll_over: usize = 0; - while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) { - const index = hm.constrainIndex(start_index + roll_over); - var entry = &hm.entries[index]; - - if (!entry.used) return null; - - if (!eql(entry.kv.key, key)) continue; - - const removed_kv = entry.kv; - while (roll_over < hm.entries.len) : (roll_over += 1) { - const next_index = hm.constrainIndex(start_index + roll_over + 1); - const next_entry = &hm.entries[next_index]; - if (!next_entry.used or next_entry.distance_from_start_index == 0) { - entry.used = false; - hm.size -= 1; - return removed_kv; - } - entry.* = next_entry.*; - entry.distance_from_start_index -= 1; - entry = next_entry; - } - unreachable; // shifting everything in the table - } - } - return null; - } - - /// Calls remove(), asserts that a kv pair is removed, and discards it. - pub fn removeAssertDiscard(hm: *Self, key: K) void { - assert(hm.remove(key) != null); - } - - pub fn iterator(hm: *const Self) Iterator { + /// Deprecated. Iterate using `items`. + pub fn iterator(self: *const Self) Iterator { return Iterator{ - .hm = hm, - .count = 0, + .hm = self, .index = 0, - .initial_modification_count = hm.modification_count, }; } + /// If key exists this function cannot fail. + /// If there is an existing item with `key`, then the result + /// `Entry` pointer points to it, and found_existing is true. + /// Otherwise, puts a new item with undefined value, and + /// the `Entry` pointer points to it. Caller should then initialize + /// the value (but not the key). + pub fn getOrPut(self: *Self, key: K) !GetOrPutResult { + return self.unmanaged.getOrPut(self.allocator, key); + } + + /// If there is an existing item with `key`, then the result + /// `Entry` pointer points to it, and found_existing is true. + /// Otherwise, puts a new item with undefined value, and + /// the `Entry` pointer points to it. Caller should then initialize + /// the value (but not the key). + /// If a new entry needs to be stored, this function asserts there + /// is enough capacity to store it. + pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult { + return self.unmanaged.getOrPutAssumeCapacity(key); + } + + pub fn getOrPutValue(self: *Self, key: K, value: V) !*Entry { + return self.unmanaged.getOrPutValue(self.allocator, key, value); + } + + /// Increases capacity, guaranteeing that insertions up until the + /// `expected_count` will not cause an allocation, and therefore cannot fail. + pub fn ensureCapacity(self: *Self, new_capacity: usize) !void { + return self.unmanaged.ensureCapacity(self.allocator, new_capacity); + } + + /// Returns the number of total elements which may be present before it is + /// no longer guaranteed that no allocations will be performed. + pub fn capacity(self: *Self) usize { + return self.unmanaged.capacity(); + } + + /// Clobbers any existing data. To detect if a put would clobber + /// existing data, see `getOrPut`. + pub fn put(self: *Self, key: K, value: V) !void { + return self.unmanaged.put(self.allocator, key, value); + } + + /// Inserts a key-value pair into the hash map, asserting that no previous + /// entry with the same key is already present + pub fn putNoClobber(self: *Self, key: K, value: V) !void { + return self.unmanaged.putNoClobber(self.allocator, key, value); + } + + /// Asserts there is enough capacity to store the new key-value pair. + /// Clobbers any existing data. To detect if a put would clobber + /// existing data, see `getOrPutAssumeCapacity`. + pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { + return self.unmanaged.putAssumeCapacity(key, value); + } + + /// Asserts there is enough capacity to store the new key-value pair. + /// Asserts that it does not clobber any existing data. + /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`. + pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { + return self.unmanaged.putAssumeCapacityNoClobber(key, value); + } + + /// Inserts a new `Entry` into the hash map, returning the previous one, if any. + pub fn fetchPut(self: *Self, key: K, value: V) !?Entry { + return self.unmanaged.fetchPut(self.allocator, key, value); + } + + /// Inserts a new `Entry` into the hash map, returning the previous one, if any. + /// If insertion happuns, asserts there is enough capacity without allocating. + pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry { + return self.unmanaged.fetchPutAssumeCapacity(key, value); + } + + pub fn getEntry(self: Self, key: K) ?*Entry { + return self.unmanaged.getEntry(key); + } + + pub fn get(self: Self, key: K) ?V { + return self.unmanaged.get(key); + } + + pub fn contains(self: Self, key: K) bool { + return self.unmanaged.contains(key); + } + + /// If there is an `Entry` with a matching key, it is deleted from + /// the hash map, and then returned from this function. + pub fn remove(self: *Self, key: K) ?Entry { + return self.unmanaged.remove(key); + } + + /// Asserts there is an `Entry` with matching key, deletes it from the hash map, + /// and discards it. + pub fn removeAssertDiscard(self: *Self, key: K) void { + return self.unmanaged.removeAssertDiscard(key); + } + + pub fn items(self: Self) []Entry { + return self.unmanaged.items(); + } + pub fn clone(self: Self) !Self { - var other = Self.init(self.allocator); - try other.initCapacity(self.entries.len); - var it = self.iterator(); - while (it.next()) |entry| { - try other.putNoClobber(entry.key, entry.value); + var other = try self.unmanaged.clone(self.allocator); + return other.promote(self.allocator); + } + }; +} + +/// General purpose hash table. +/// Insertion order is preserved. +/// Deletions perform a "swap removal" on the entries list. +/// Modifying the hash map while iterating is allowed, however one must understand +/// the (well defined) behavior when mixing insertions and deletions with iteration. +/// This type does not store an Allocator field - the Allocator must be passed in +/// with each function call that requires it. See `HashMap` for a type that stores +/// an Allocator field for convenience. +/// Can be initialized directly using the default field values. +/// This type is designed to have low overhead for small numbers of entries. When +/// `store_hash` is `false` and the number of entries in the map is less than 9, +/// the overhead cost of using `HashMapUnmanaged` rather than `std.ArrayList` is +/// only a single pointer-sized integer. +/// When `store_hash` is `false`, this data structure is biased towards cheap `eql` +/// functions. It does not store each item's hash in the table. Setting `store_hash` +/// to `true` incurs slightly more memory cost by storing each key's hash in the table +/// but guarantees only one call to `eql` per insertion/deletion. +pub fn HashMapUnmanaged( + comptime K: type, + comptime V: type, + comptime hash: fn (key: K) u32, + comptime eql: fn (a: K, b: K) bool, + comptime store_hash: bool, +) type { + return struct { + /// It is permitted to access this field directly. + entries: std.ArrayListUnmanaged(Entry) = .{}, + + /// When entries length is less than `linear_scan_max`, this remains `null`. + /// Once entries length grows big enough, this field is allocated. There is + /// an IndexHeader followed by an array of Index(I) structs, where I is defined + /// by how many total indexes there are. + index_header: ?*IndexHeader = null, + + /// Modifying the key is illegal behavior. + /// Modifying the value is allowed. + /// Entry pointers become invalid whenever this HashMap is modified, + /// unless `ensureCapacity` was previously used. + pub const Entry = struct { + /// This field is `void` if `store_hash` is `false`. + hash: Hash, + key: K, + value: V, + }; + + pub const Hash = if (store_hash) u32 else void; + + pub const GetOrPutResult = struct { + entry: *Entry, + found_existing: bool, + }; + + pub const Managed = HashMap(K, V, hash, eql, store_hash); + + const Self = @This(); + + const linear_scan_max = 8; + + pub fn promote(self: Self, allocator: *Allocator) Managed { + return .{ + .unmanaged = self, + .allocator = allocator, + }; + } + + pub fn deinit(self: *Self, allocator: *Allocator) void { + self.entries.deinit(allocator); + if (self.index_header) |header| { + header.free(allocator); + } + self.* = undefined; + } + + pub fn clearRetainingCapacity(self: *Self) void { + self.entries.items.len = 0; + if (self.header) |header| { + header.max_distance_from_start_index = 0; + const indexes = header.indexes(u8); + @memset(indexes.ptr, 0xff, indexes.len); + } + } + + pub fn clearAndFree(self: *Self, allocator: *Allocator) void { + self.entries.shrink(allocator, 0); + if (self.header) |header| { + header.free(allocator); + self.header = null; + } + } + + /// If key exists this function cannot fail. + /// If there is an existing item with `key`, then the result + /// `Entry` pointer points to it, and found_existing is true. + /// Otherwise, puts a new item with undefined value, and + /// the `Entry` pointer points to it. Caller should then initialize + /// the value (but not the key). + pub fn getOrPut(self: *Self, allocator: *Allocator, key: K) !GetOrPutResult { + self.ensureCapacity(allocator, self.entries.items.len + 1) catch |err| { + // "If key exists this function cannot fail." + return GetOrPutResult{ + .entry = self.getEntry(key) orelse return err, + .found_existing = true, + }; + }; + return self.getOrPutAssumeCapacity(key); + } + + /// If there is an existing item with `key`, then the result + /// `Entry` pointer points to it, and found_existing is true. + /// Otherwise, puts a new item with undefined value, and + /// the `Entry` pointer points to it. Caller should then initialize + /// the value (but not the key). + /// If a new entry needs to be stored, this function asserts there + /// is enough capacity to store it. + pub fn getOrPutAssumeCapacity(self: *Self, key: K) GetOrPutResult { + const header = self.index_header orelse { + // Linear scan. + const h = if (store_hash) hash(key) else {}; + for (self.entries.items) |*item| { + if (item.hash == h and eql(key, item.key)) { + return GetOrPutResult{ + .entry = item, + .found_existing = true, + }; + } + } + const new_entry = self.entries.addOneAssumeCapacity(); + new_entry.* = .{ + .hash = if (store_hash) h else {}, + .key = key, + .value = undefined, + }; + return GetOrPutResult{ + .entry = new_entry, + .found_existing = false, + }; + }; + + switch (header.capacityIndexType()) { + .u8 => return self.getOrPutInternal(key, header, u8), + .u16 => return self.getOrPutInternal(key, header, u16), + .u32 => return self.getOrPutInternal(key, header, u32), + .usize => return self.getOrPutInternal(key, header, usize), + } + } + + pub fn getOrPutValue(self: *Self, allocator: *Allocator, key: K, value: V) !*Entry { + const res = try self.getOrPut(allocator, key); + if (!res.found_existing) + res.entry.value = value; + + return res.entry; + } + + /// Increases capacity, guaranteeing that insertions up until the + /// `expected_count` will not cause an allocation, and therefore cannot fail. + pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void { + try self.entries.ensureCapacity(allocator, new_capacity); + if (new_capacity <= linear_scan_max) return; + + // Resize if indexes would be more than 75% full. + const needed_len = new_capacity * 4 / 3; + if (self.index_header) |header| { + if (needed_len > header.indexes_len) { + var new_indexes_len = header.indexes_len; + while (true) { + new_indexes_len += new_indexes_len / 2 + 8; + if (new_indexes_len >= needed_len) break; + } + const new_header = try IndexHeader.alloc(allocator, new_indexes_len); + self.insertAllEntriesIntoNewHeader(new_header); + header.free(allocator); + self.index_header = new_header; + } + } else { + const header = try IndexHeader.alloc(allocator, needed_len); + self.insertAllEntriesIntoNewHeader(header); + self.index_header = header; + } + } + + /// Returns the number of total elements which may be present before it is + /// no longer guaranteed that no allocations will be performed. + pub fn capacity(self: Self) usize { + const entry_cap = self.entries.capacity; + const header = self.index_header orelse return math.min(linear_scan_max, entry_cap); + const indexes_cap = (header.indexes_len + 1) * 3 / 4; + return math.min(entry_cap, indexes_cap); + } + + /// Clobbers any existing data. To detect if a put would clobber + /// existing data, see `getOrPut`. + pub fn put(self: *Self, allocator: *Allocator, key: K, value: V) !void { + const result = try self.getOrPut(allocator, key); + result.entry.value = value; + } + + /// Inserts a key-value pair into the hash map, asserting that no previous + /// entry with the same key is already present + pub fn putNoClobber(self: *Self, allocator: *Allocator, key: K, value: V) !void { + const result = try self.getOrPut(allocator, key); + assert(!result.found_existing); + result.entry.value = value; + } + + /// Asserts there is enough capacity to store the new key-value pair. + /// Clobbers any existing data. To detect if a put would clobber + /// existing data, see `getOrPutAssumeCapacity`. + pub fn putAssumeCapacity(self: *Self, key: K, value: V) void { + const result = self.getOrPutAssumeCapacity(key); + result.entry.value = value; + } + + /// Asserts there is enough capacity to store the new key-value pair. + /// Asserts that it does not clobber any existing data. + /// To detect if a put would clobber existing data, see `getOrPutAssumeCapacity`. + pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void { + const result = self.getOrPutAssumeCapacity(key); + assert(!result.found_existing); + result.entry.value = value; + } + + /// Inserts a new `Entry` into the hash map, returning the previous one, if any. + pub fn fetchPut(self: *Self, allocator: *Allocator, key: K, value: V) !?Entry { + const gop = try self.getOrPut(allocator, key); + var result: ?Entry = null; + if (gop.found_existing) { + result = gop.entry.*; + } + gop.entry.value = value; + return result; + } + + /// Inserts a new `Entry` into the hash map, returning the previous one, if any. + /// If insertion happuns, asserts there is enough capacity without allocating. + pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry { + const gop = self.getOrPutAssumeCapacity(key); + var result: ?Entry = null; + if (gop.found_existing) { + result = gop.entry.*; + } + gop.entry.value = value; + return result; + } + + pub fn getEntry(self: Self, key: K) ?*Entry { + const header = self.index_header orelse { + // Linear scan. + const h = if (store_hash) hash(key) else {}; + for (self.entries.items) |*item| { + if (item.hash == h and eql(key, item.key)) { + return item; + } + } + return null; + }; + + switch (header.capacityIndexType()) { + .u8 => return self.getInternal(key, header, u8), + .u16 => return self.getInternal(key, header, u16), + .u32 => return self.getInternal(key, header, u32), + .usize => return self.getInternal(key, header, usize), + } + } + + pub fn get(self: Self, key: K) ?V { + return if (self.getEntry(key)) |entry| entry.value else null; + } + + pub fn contains(self: Self, key: K) bool { + return self.getEntry(key) != null; + } + + /// If there is an `Entry` with a matching key, it is deleted from + /// the hash map, and then returned from this function. + pub fn remove(self: *Self, key: K) ?Entry { + const header = self.index_header orelse { + // Linear scan. + const h = if (store_hash) hash(key) else {}; + for (self.entries.items) |item, i| { + if (item.hash == h and eql(key, item.key)) { + return self.entries.swapRemove(i); + } + } + return null; + }; + switch (header.capacityIndexType()) { + .u8 => return self.removeInternal(key, header, u8), + .u16 => return self.removeInternal(key, header, u16), + .u32 => return self.removeInternal(key, header, u32), + .usize => return self.removeInternal(key, header, usize), + } + } + + /// Asserts there is an `Entry` with matching key, deletes it from the hash map, + /// and discards it. + pub fn removeAssertDiscard(self: *Self, key: K) void { + assert(self.remove(key) != null); + } + + pub fn items(self: Self) []Entry { + return self.entries.items; + } + + pub fn clone(self: Self, allocator: *Allocator) !Self { + // TODO this can be made more efficient by directly allocating + // the memory slices and memcpying the elements. + var other = Self.init(); + try other.initCapacity(allocator, self.entries.len); + for (self.entries.items) |entry| { + other.putAssumeCapacityNoClobber(entry.key, entry.value); } return other; } - fn autoCapacity(self: *Self) !void { - if (self.entries.len == 0) { - return self.ensureCapacityExact(16); - } - // if we get too full (60%), double the capacity - if (self.size * 5 >= self.entries.len * 3) { - return self.ensureCapacityExact(self.entries.len * 2); - } - } + fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry { + const indexes = header.indexes(I); + const h = hash(key); + const start_index = header.hashToIndex(h); + var roll_over: usize = 0; + while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { + const index_index = (start_index + roll_over) % header.indexes_len; + var index = &indexes[index_index]; + if (index.isEmpty()) + return null; + + const entry = &self.entries.items[index.entry_index]; - fn initCapacity(hm: *Self, capacity: usize) !void { - hm.entries = try hm.allocator.alloc(Entry, capacity); - hm.size = 0; - hm.max_distance_from_start_index = 0; - for (hm.entries) |*entry| { - entry.used = false; + const hash_match = if (store_hash) h == entry.hash else true; + if (!hash_match or !eql(key, entry.key)) + continue; + + const removed_entry = self.entries.swapRemove(index.entry_index); + if (self.entries.items.len > 0 and self.entries.items.len != index.entry_index) { + // Because of the swap remove, now we need to update the index that was + // pointing to the last entry and is now pointing to this removed item slot. + self.updateEntryIndex(header, self.entries.items.len, index.entry_index, I, indexes); + } + + // Now we have to shift over the following indexes. + roll_over += 1; + while (roll_over < header.indexes_len) : (roll_over += 1) { + const next_index_index = (start_index + roll_over) % header.indexes_len; + const next_index = &indexes[next_index_index]; + if (next_index.isEmpty() or next_index.distance_from_start_index == 0) { + index.setEmpty(); + return removed_entry; + } + index.* = next_index.*; + index.distance_from_start_index -= 1; + index = next_index; + } + unreachable; } + return null; } - fn incrementModificationCount(hm: *Self) void { - if (want_modification_safety) { - hm.modification_count +%= 1; + fn updateEntryIndex( + self: *Self, + header: *IndexHeader, + old_entry_index: usize, + new_entry_index: usize, + comptime I: type, + indexes: []Index(I), + ) void { + const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key); + const start_index = header.hashToIndex(h); + var roll_over: usize = 0; + while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { + const index_index = (start_index + roll_over) % header.indexes_len; + const index = &indexes[index_index]; + if (index.entry_index == old_entry_index) { + index.entry_index = @intCast(I, new_entry_index); + return; + } } + unreachable; } - const InternalPutResult = struct { - new_entry: *Entry, - old_kv: ?KV, - }; - - /// Returns a pointer to the new entry. - /// Asserts that there is enough space for the new item. - fn internalPut(self: *Self, orig_key: K) InternalPutResult { - var key = orig_key; - var value: V = undefined; - const start_index = self.keyToIndex(key); + /// Must ensureCapacity before calling this. + fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult { + const indexes = header.indexes(I); + const h = hash(key); + const start_index = header.hashToIndex(h); var roll_over: usize = 0; var distance_from_start_index: usize = 0; - var got_result_entry = false; - var result = InternalPutResult{ - .new_entry = undefined, - .old_kv = null, - }; - while (roll_over < self.entries.len) : ({ + while (roll_over <= header.indexes_len) : ({ roll_over += 1; distance_from_start_index += 1; }) { - const index = self.constrainIndex(start_index + roll_over); - const entry = &self.entries[index]; + const index_index = (start_index + roll_over) % header.indexes_len; + const index = indexes[index_index]; + if (index.isEmpty()) { + indexes[index_index] = .{ + .distance_from_start_index = @intCast(I, distance_from_start_index), + .entry_index = @intCast(I, self.entries.items.len), + }; + header.maybeBumpMax(distance_from_start_index); + const new_entry = self.entries.addOneAssumeCapacity(); + new_entry.* = .{ + .hash = if (store_hash) h else {}, + .key = key, + .value = undefined, + }; + return .{ + .found_existing = false, + .entry = new_entry, + }; + } - if (entry.used and !eql(entry.kv.key, key)) { - if (entry.distance_from_start_index < distance_from_start_index) { - // robin hood to the rescue - const tmp = entry.*; - self.max_distance_from_start_index = math.max(self.max_distance_from_start_index, distance_from_start_index); - if (!got_result_entry) { - got_result_entry = true; - result.new_entry = entry; + // This pointer survives the following append because we call + // entries.ensureCapacity before getOrPutInternal. + const entry = &self.entries.items[index.entry_index]; + const hash_match = if (store_hash) h == entry.hash else true; + if (hash_match and eql(key, entry.key)) { + return .{ + .found_existing = true, + .entry = entry, + }; + } + if (index.distance_from_start_index < distance_from_start_index) { + // In this case, we did not find the item. We will put a new entry. + // However, we will use this index for the new entry, and move + // the previous index down the line, to keep the max_distance_from_start_index + // as small as possible. + indexes[index_index] = .{ + .distance_from_start_index = @intCast(I, distance_from_start_index), + .entry_index = @intCast(I, self.entries.items.len), + }; + header.maybeBumpMax(distance_from_start_index); + const new_entry = self.entries.addOneAssumeCapacity(); + new_entry.* = .{ + .hash = if (store_hash) h else {}, + .key = key, + .value = undefined, + }; + + distance_from_start_index = index.distance_from_start_index; + var prev_entry_index = index.entry_index; + + // Find somewhere to put the index we replaced by shifting + // following indexes backwards. + roll_over += 1; + distance_from_start_index += 1; + while (roll_over < header.indexes_len) : ({ + roll_over += 1; + distance_from_start_index += 1; + }) { + const next_index_index = (start_index + roll_over) % header.indexes_len; + const next_index = indexes[next_index_index]; + if (next_index.isEmpty()) { + header.maybeBumpMax(distance_from_start_index); + indexes[next_index_index] = .{ + .entry_index = prev_entry_index, + .distance_from_start_index = @intCast(I, distance_from_start_index), + }; + return .{ + .found_existing = false, + .entry = new_entry, + }; + } + if (next_index.distance_from_start_index < distance_from_start_index) { + header.maybeBumpMax(distance_from_start_index); + indexes[next_index_index] = .{ + .entry_index = prev_entry_index, + .distance_from_start_index = @intCast(I, distance_from_start_index), + }; + distance_from_start_index = next_index.distance_from_start_index; + prev_entry_index = next_index.entry_index; } - entry.* = Entry{ - .used = true, - .distance_from_start_index = distance_from_start_index, - .kv = KV{ - .key = key, - .value = value, - }, - }; - key = tmp.kv.key; - value = tmp.kv.value; - distance_from_start_index = tmp.distance_from_start_index; } - continue; + unreachable; } - - if (entry.used) { - result.old_kv = entry.kv; - } else { - // adding an entry. otherwise overwriting old value with - // same key - self.size += 1; - } - - self.max_distance_from_start_index = math.max(distance_from_start_index, self.max_distance_from_start_index); - if (!got_result_entry) { - result.new_entry = entry; - } - entry.* = Entry{ - .used = true, - .distance_from_start_index = distance_from_start_index, - .kv = KV{ - .key = key, - .value = value, - }, - }; - return result; } - unreachable; // put into a full map + unreachable; } - fn internalGet(hm: Self, key: K) ?*KV { - const start_index = hm.keyToIndex(key); - { - var roll_over: usize = 0; - while (roll_over <= hm.max_distance_from_start_index) : (roll_over += 1) { - const index = hm.constrainIndex(start_index + roll_over); - const entry = &hm.entries[index]; + fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry { + const indexes = header.indexes(I); + const h = hash(key); + const start_index = header.hashToIndex(h); + var roll_over: usize = 0; + while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { + const index_index = (start_index + roll_over) % header.indexes_len; + const index = indexes[index_index]; + if (index.isEmpty()) + return null; - if (!entry.used) return null; - if (eql(entry.kv.key, key)) return &entry.kv; - } + const entry = &self.entries.items[index.entry_index]; + const hash_match = if (store_hash) h == entry.hash else true; + if (hash_match and eql(key, entry.key)) + return entry; } return null; } - fn keyToIndex(hm: Self, key: K) usize { - return hm.constrainIndex(@as(usize, hash(key))); + fn insertAllEntriesIntoNewHeader(self: *Self, header: *IndexHeader) void { + switch (header.capacityIndexType()) { + .u8 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u8), + .u16 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u16), + .u32 => return self.insertAllEntriesIntoNewHeaderGeneric(header, u32), + .usize => return self.insertAllEntriesIntoNewHeaderGeneric(header, usize), + } } - fn constrainIndex(hm: Self, i: usize) usize { - // this is an optimization for modulo of power of two integers; - // it requires hm.entries.len to always be a power of two - return i & (hm.entries.len - 1); + fn insertAllEntriesIntoNewHeaderGeneric(self: *Self, header: *IndexHeader, comptime I: type) void { + const indexes = header.indexes(I); + entry_loop: for (self.entries.items) |entry, i| { + const h = if (store_hash) entry.hash else hash(entry.key); + const start_index = header.hashToIndex(h); + var entry_index = i; + var roll_over: usize = 0; + var distance_from_start_index: usize = 0; + while (roll_over < header.indexes_len) : ({ + roll_over += 1; + distance_from_start_index += 1; + }) { + const index_index = (start_index + roll_over) % header.indexes_len; + const next_index = indexes[index_index]; + if (next_index.isEmpty()) { + header.maybeBumpMax(distance_from_start_index); + indexes[index_index] = .{ + .distance_from_start_index = @intCast(I, distance_from_start_index), + .entry_index = @intCast(I, entry_index), + }; + continue :entry_loop; + } + if (next_index.distance_from_start_index < distance_from_start_index) { + header.maybeBumpMax(distance_from_start_index); + indexes[index_index] = .{ + .distance_from_start_index = @intCast(I, distance_from_start_index), + .entry_index = @intCast(I, entry_index), + }; + distance_from_start_index = next_index.distance_from_start_index; + entry_index = next_index.entry_index; + } + } + unreachable; + } } }; } +const CapacityIndexType = enum { u8, u16, u32, usize }; + +fn capacityIndexType(indexes_len: usize) CapacityIndexType { + if (indexes_len < math.maxInt(u8)) + return .u8; + if (indexes_len < math.maxInt(u16)) + return .u16; + if (indexes_len < math.maxInt(u32)) + return .u32; + return .usize; +} + +fn capacityIndexSize(indexes_len: usize) usize { + switch (capacityIndexType(indexes_len)) { + .u8 => return @sizeOf(Index(u8)), + .u16 => return @sizeOf(Index(u16)), + .u32 => return @sizeOf(Index(u32)), + .usize => return @sizeOf(Index(usize)), + } +} + +fn Index(comptime I: type) type { + return extern struct { + entry_index: I, + distance_from_start_index: I, + + const Self = @This(); + + fn isEmpty(idx: Self) bool { + return idx.entry_index == math.maxInt(I); + } + + fn setEmpty(idx: *Self) void { + idx.entry_index = math.maxInt(I); + } + }; +} + +/// This struct is trailed by an array of `Index(I)`, where `I` +/// and the array length are determined by `indexes_len`. +const IndexHeader = struct { + max_distance_from_start_index: usize, + indexes_len: usize, + + fn hashToIndex(header: IndexHeader, h: u32) usize { + return @as(usize, h) % header.indexes_len; + } + + fn indexes(header: *IndexHeader, comptime I: type) []Index(I) { + const start = @ptrCast([*]Index(I), @ptrCast([*]u8, header) + @sizeOf(IndexHeader)); + return start[0..header.indexes_len]; + } + + fn capacityIndexType(header: IndexHeader) CapacityIndexType { + return hash_map.capacityIndexType(header.indexes_len); + } + + fn maybeBumpMax(header: *IndexHeader, distance_from_start_index: usize) void { + if (distance_from_start_index > header.max_distance_from_start_index) { + header.max_distance_from_start_index = distance_from_start_index; + } + } + + fn alloc(allocator: *Allocator, len: usize) !*IndexHeader { + const index_size = hash_map.capacityIndexSize(len); + const nbytes = @sizeOf(IndexHeader) + index_size * len; + const bytes = try allocator.allocAdvanced(u8, @alignOf(IndexHeader), nbytes, .exact); + @memset(bytes.ptr + @sizeOf(IndexHeader), 0xff, bytes.len - @sizeOf(IndexHeader)); + const result = @ptrCast(*IndexHeader, bytes.ptr); + result.* = .{ + .max_distance_from_start_index = 0, + .indexes_len = len, + }; + return result; + } + + fn free(header: *IndexHeader, allocator: *Allocator) void { + const index_size = hash_map.capacityIndexSize(header.indexes_len); + const ptr = @ptrCast([*]u8, header); + const slice = ptr[0 .. @sizeOf(IndexHeader) + header.indexes_len * index_size]; + allocator.free(slice); + } +}; + test "basic hash map usage" { var map = AutoHashMap(i32, i32).init(std.testing.allocator); defer map.deinit(); - testing.expect((try map.put(1, 11)) == null); - testing.expect((try map.put(2, 22)) == null); - testing.expect((try map.put(3, 33)) == null); - testing.expect((try map.put(4, 44)) == null); + testing.expect((try map.fetchPut(1, 11)) == null); + testing.expect((try map.fetchPut(2, 22)) == null); + testing.expect((try map.fetchPut(3, 33)) == null); + testing.expect((try map.fetchPut(4, 44)) == null); try map.putNoClobber(5, 55); - testing.expect((try map.put(5, 66)).?.value == 55); - testing.expect((try map.put(5, 55)).?.value == 66); + testing.expect((try map.fetchPut(5, 66)).?.value == 55); + testing.expect((try map.fetchPut(5, 55)).?.value == 66); const gop1 = try map.getOrPut(5); testing.expect(gop1.found_existing == true); - testing.expect(gop1.kv.value == 55); - gop1.kv.value = 77; - testing.expect(map.get(5).?.value == 77); + testing.expect(gop1.entry.value == 55); + gop1.entry.value = 77; + testing.expect(map.getEntry(5).?.value == 77); const gop2 = try map.getOrPut(99); testing.expect(gop2.found_existing == false); - gop2.kv.value = 42; - testing.expect(map.get(99).?.value == 42); + gop2.entry.value = 42; + testing.expect(map.getEntry(99).?.value == 42); const gop3 = try map.getOrPutValue(5, 5); testing.expect(gop3.value == 77); @@ -454,15 +876,15 @@ test "basic hash map usage" { testing.expect(gop4.value == 41); testing.expect(map.contains(2)); - testing.expect(map.get(2).?.value == 22); - testing.expect(map.getValue(2).? == 22); + testing.expect(map.getEntry(2).?.value == 22); + testing.expect(map.get(2).? == 22); const rmv1 = map.remove(2); testing.expect(rmv1.?.key == 2); testing.expect(rmv1.?.value == 22); testing.expect(map.remove(2) == null); + testing.expect(map.getEntry(2) == null); testing.expect(map.get(2) == null); - testing.expect(map.getValue(2) == null); map.removeAssertDiscard(3); } @@ -498,8 +920,8 @@ test "iterator hash map" { it.reset(); var count: usize = 0; - while (it.next()) |kv| : (count += 1) { - buffer[@intCast(usize, kv.key)] = kv.value; + while (it.next()) |entry| : (count += 1) { + buffer[@intCast(usize, entry.key)] = entry.value; } testing.expect(count == 3); testing.expect(it.next() == null); @@ -510,8 +932,8 @@ test "iterator hash map" { it.reset(); count = 0; - while (it.next()) |kv| { - buffer[@intCast(usize, kv.key)] = kv.value; + while (it.next()) |entry| { + buffer[@intCast(usize, entry.key)] = entry.value; count += 1; if (count >= 2) break; } @@ -531,14 +953,14 @@ test "ensure capacity" { defer map.deinit(); try map.ensureCapacity(20); - const initialCapacity = map.entries.len; - testing.expect(initialCapacity >= 20); + const initial_capacity = map.capacity(); + testing.expect(initial_capacity >= 20); var i: i32 = 0; while (i < 20) : (i += 1) { - testing.expect(map.putAssumeCapacity(i, i + 10) == null); + testing.expect(map.fetchPutAssumeCapacity(i, i + 10) == null); } // shouldn't resize from putAssumeCapacity - testing.expect(initialCapacity == map.entries.len); + testing.expect(initial_capacity == map.capacity()); } pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) { @@ -575,6 +997,24 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { }.eql; } +pub fn autoEqlIsCheap(comptime K: type) bool { + return switch (@typeInfo(K)) { + .Bool, + .Int, + .Float, + .Pointer, + .ComptimeFloat, + .ComptimeInt, + .Enum, + .Fn, + .ErrorSet, + .AnyFrame, + .EnumLiteral, + => true, + else => false, + }; +} + pub fn getAutoHashStratFn(comptime K: type, comptime strategy: std.hash.Strategy) (fn (K) u32) { return struct { fn hash(key: K) u32 { -- 2.54.0 From 632acffcbd96a085ea92899e6f37465e40178f44 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 4 Jul 2020 01:31:29 +0000 Subject: [PATCH 181/295] update std lib to new hash map API --- lib/std/buf_map.zig | 15 ++++--- lib/std/buf_set.zig | 8 ++-- lib/std/build.zig | 12 +++--- lib/std/hash_map.zig | 25 ++++++++---- lib/std/http/headers.zig | 72 ++++++++++++++++----------------- lib/std/json.zig | 56 ++++++++++++------------- src-self-hosted/main.zig | 4 +- src-self-hosted/translate_c.zig | 27 ++++++------- 8 files changed, 111 insertions(+), 108 deletions(-) diff --git a/lib/std/buf_map.zig b/lib/std/buf_map.zig index e8bc735b572c45b3bed6efc05ee88054e2816987..651561b374e666c6f03eb431dd804ab6ff471576 100644 --- a/lib/std/buf_map.zig +++ b/lib/std/buf_map.zig @@ -33,10 +33,10 @@ pub const BufMap = struct { pub fn setMove(self: *BufMap, key: []u8, value: []u8) !void { const get_or_put = try self.hash_map.getOrPut(key); if (get_or_put.found_existing) { - self.free(get_or_put.kv.key); - get_or_put.kv.key = key; + self.free(get_or_put.entry.key); + get_or_put.entry.key = key; } - get_or_put.kv.value = value; + get_or_put.entry.value = value; } /// `key` and `value` are copied into the BufMap. @@ -45,19 +45,18 @@ pub const BufMap = struct { errdefer self.free(value_copy); const get_or_put = try self.hash_map.getOrPut(key); if (get_or_put.found_existing) { - self.free(get_or_put.kv.value); + self.free(get_or_put.entry.value); } else { - get_or_put.kv.key = self.copy(key) catch |err| { + get_or_put.entry.key = self.copy(key) catch |err| { _ = self.hash_map.remove(key); return err; }; } - get_or_put.kv.value = value_copy; + get_or_put.entry.value = value_copy; } pub fn get(self: BufMap, key: []const u8) ?[]const u8 { - const entry = self.hash_map.get(key) orelse return null; - return entry.value; + return self.hash_map.get(key); } pub fn delete(self: *BufMap, key: []const u8) void { diff --git a/lib/std/buf_set.zig b/lib/std/buf_set.zig index 89df0478ff91437873b56d3ce2d782091105e048..d8a0264bd71fac60a8f9bf93c1189b29d7cf33c8 100644 --- a/lib/std/buf_set.zig +++ b/lib/std/buf_set.zig @@ -14,14 +14,12 @@ pub const BufSet = struct { return self; } - pub fn deinit(self: *const BufSet) void { - var it = self.hash_map.iterator(); - while (true) { - const entry = it.next() orelse break; + pub fn deinit(self: *BufSet) void { + for (self.hash_map.items()) |entry| { self.free(entry.key); } - self.hash_map.deinit(); + self.* = undefined; } pub fn put(self: *BufSet, key: []const u8) !void { diff --git a/lib/std/build.zig b/lib/std/build.zig index df1dc6d73a61bc0f46f7120698b1129fb3c6b0a1..5619bc5fe67176ce86b29152a09d7a59c63825f2 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -422,12 +422,12 @@ pub const Builder = struct { .type_id = type_id, .description = description, }; - if ((self.available_options_map.put(name, available_option) catch unreachable) != null) { + if ((self.available_options_map.fetchPut(name, available_option) catch unreachable) != null) { panic("Option '{}' declared twice", .{name}); } self.available_options_list.append(available_option) catch unreachable; - const entry = self.user_input_options.get(name) orelse return null; + const entry = self.user_input_options.getEntry(name) orelse return null; entry.value.used = true; switch (type_id) { TypeId.Bool => switch (entry.value.value) { @@ -634,7 +634,7 @@ pub const Builder = struct { pub fn addUserInputOption(self: *Builder, name: []const u8, value: []const u8) !bool { const gop = try self.user_input_options.getOrPut(name); if (!gop.found_existing) { - gop.kv.value = UserInputOption{ + gop.entry.value = UserInputOption{ .name = name, .value = UserValue{ .Scalar = value }, .used = false, @@ -643,7 +643,7 @@ pub const Builder = struct { } // option already exists - switch (gop.kv.value.value) { + switch (gop.entry.value.value) { UserValue.Scalar => |s| { // turn it into a list var list = ArrayList([]const u8).init(self.allocator); @@ -675,7 +675,7 @@ pub const Builder = struct { pub fn addUserInputFlag(self: *Builder, name: []const u8) !bool { const gop = try self.user_input_options.getOrPut(name); if (!gop.found_existing) { - gop.kv.value = UserInputOption{ + gop.entry.value = UserInputOption{ .name = name, .value = UserValue{ .Flag = {} }, .used = false, @@ -684,7 +684,7 @@ pub const Builder = struct { } // option already exists - switch (gop.kv.value.value) { + switch (gop.entry.value.value) { UserValue.Scalar => |s| { warn("Flag '-D{}' conflicts with option '-D{}={}'.\n", .{ name, name, s }); return true; diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index 4b91a83ba2dbab22e1d7a62d68d2d460aba4a8b5..d0b18d941957ab3ec9bbbd1fc7ce08c13e5fed1f 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -293,18 +293,22 @@ pub fn HashMapUnmanaged( pub fn clearRetainingCapacity(self: *Self) void { self.entries.items.len = 0; - if (self.header) |header| { + if (self.index_header) |header| { header.max_distance_from_start_index = 0; - const indexes = header.indexes(u8); - @memset(indexes.ptr, 0xff, indexes.len); + switch (header.capacityIndexType()) { + .u8 => mem.set(Index(u8), header.indexes(u8), Index(u8).empty), + .u16 => mem.set(Index(u16), header.indexes(u16), Index(u16).empty), + .u32 => mem.set(Index(u32), header.indexes(u32), Index(u32).empty), + .usize => mem.set(Index(usize), header.indexes(usize), Index(usize).empty), + } } } pub fn clearAndFree(self: *Self, allocator: *Allocator) void { self.entries.shrink(allocator, 0); - if (self.header) |header| { + if (self.index_header) |header| { header.free(allocator); - self.header = null; + self.index_header = null; } } @@ -378,13 +382,13 @@ pub fn HashMapUnmanaged( try self.entries.ensureCapacity(allocator, new_capacity); if (new_capacity <= linear_scan_max) return; - // Resize if indexes would be more than 75% full. - const needed_len = new_capacity * 4 / 3; + // Resize if indexes would be more than 60% full. + const needed_len = new_capacity * 5 / 3; if (self.index_header) |header| { if (needed_len > header.indexes_len) { var new_indexes_len = header.indexes_len; while (true) { - new_indexes_len += new_indexes_len / 2 + 8; + new_indexes_len *= new_indexes_len / 2 + 8; if (new_indexes_len >= needed_len) break; } const new_header = try IndexHeader.alloc(allocator, new_indexes_len); @@ -789,6 +793,11 @@ fn Index(comptime I: type) type { const Self = @This(); + const empty = Self{ + .entry_index = math.maxInt(I), + .distance_from_start_index = undefined, + }; + fn isEmpty(idx: Self) bool { return idx.entry_index == math.maxInt(I); } diff --git a/lib/std/http/headers.zig b/lib/std/http/headers.zig index ba929a446c1a4e382835ecafc73838a6011accd5..1c48a0781913b9f79f0ffaaea73971f2cd1b97c4 100644 --- a/lib/std/http/headers.zig +++ b/lib/std/http/headers.zig @@ -118,13 +118,12 @@ pub const Headers = struct { }; } - pub fn deinit(self: Self) void { + pub fn deinit(self: *Self) void { { - var it = self.index.iterator(); - while (it.next()) |kv| { - var dex = &kv.value; + for (self.index.items()) |*entry| { + const dex = &entry.value; dex.deinit(); - self.allocator.free(kv.key); + self.allocator.free(entry.key); } self.index.deinit(); } @@ -134,6 +133,7 @@ pub const Headers = struct { } self.data.deinit(); } + self.* = undefined; } pub fn clone(self: Self, allocator: *Allocator) !Self { @@ -155,10 +155,10 @@ pub const Headers = struct { const n = self.data.items.len + 1; try self.data.ensureCapacity(n); var entry: HeaderEntry = undefined; - if (self.index.get(name)) |kv| { + if (self.index.getEntry(name)) |kv| { entry = try HeaderEntry.init(self.allocator, kv.key, value, never_index); errdefer entry.deinit(); - var dex = &kv.value; + const dex = &kv.value; try dex.append(n - 1); } else { const name_dup = try mem.dupe(self.allocator, u8, name); @@ -195,7 +195,7 @@ pub const Headers = struct { /// Returns boolean indicating if something was deleted. pub fn delete(self: *Self, name: []const u8) bool { if (self.index.remove(name)) |kv| { - var dex = &kv.value; + const dex = &kv.value; // iterate backwards var i = dex.items.len; while (i > 0) { @@ -207,7 +207,7 @@ pub const Headers = struct { } dex.deinit(); self.allocator.free(kv.key); - self.rebuild_index(); + self.rebuildIndex(); return true; } else { return false; @@ -216,45 +216,52 @@ pub const Headers = struct { /// Removes the element at the specified index. /// Moves items down to fill the empty space. + /// TODO this implementation can be replaced by adding + /// orderedRemove to the new hash table implementation as an + /// alternative to swapRemove. pub fn orderedRemove(self: *Self, i: usize) void { const removed = self.data.orderedRemove(i); - const kv = self.index.get(removed.name).?; - var dex = &kv.value; + const kv = self.index.getEntry(removed.name).?; + const dex = &kv.value; if (dex.items.len == 1) { // was last item; delete the index - _ = self.index.remove(kv.key); dex.deinit(); removed.deinit(); - self.allocator.free(kv.key); + const key = kv.key; + _ = self.index.remove(key); // invalidates `kv` and `dex` + self.allocator.free(key); } else { dex.shrink(dex.items.len - 1); removed.deinit(); } // if it was the last item; no need to rebuild index if (i != self.data.items.len) { - self.rebuild_index(); + self.rebuildIndex(); } } /// Removes the element at the specified index. /// The empty slot is filled from the end of the list. + /// TODO this implementation can be replaced by simply using the + /// new hash table which does swap removal. pub fn swapRemove(self: *Self, i: usize) void { const removed = self.data.swapRemove(i); - const kv = self.index.get(removed.name).?; - var dex = &kv.value; + const kv = self.index.getEntry(removed.name).?; + const dex = &kv.value; if (dex.items.len == 1) { // was last item; delete the index - _ = self.index.remove(kv.key); dex.deinit(); removed.deinit(); - self.allocator.free(kv.key); + const key = kv.key; + _ = self.index.remove(key); // invalidates `kv` and `dex` + self.allocator.free(key); } else { dex.shrink(dex.items.len - 1); removed.deinit(); } // if it was the last item; no need to rebuild index if (i != self.data.items.len) { - self.rebuild_index(); + self.rebuildIndex(); } } @@ -266,11 +273,7 @@ pub const Headers = struct { /// Returns a list of indices containing headers with the given name. /// The returned list should not be modified by the caller. pub fn getIndices(self: Self, name: []const u8) ?HeaderIndexList { - if (self.index.get(name)) |kv| { - return kv.value; - } else { - return null; - } + return self.index.get(name); } /// Returns a slice containing each header with the given name. @@ -325,25 +328,20 @@ pub const Headers = struct { return buf; } - fn rebuild_index(self: *Self) void { - { // clear out the indexes - var it = self.index.iterator(); - while (it.next()) |kv| { - var dex = &kv.value; - dex.items.len = 0; // keeps capacity available - } + fn rebuildIndex(self: *Self) void { + // clear out the indexes + for (self.index.items()) |*entry| { + entry.value.shrinkRetainingCapacity(0); } - { // fill up indexes again; we know capacity is fine from before - for (self.data.span()) |entry, i| { - var dex = &self.index.get(entry.name).?.value; - dex.appendAssumeCapacity(i); - } + // fill up indexes again; we know capacity is fine from before + for (self.data.items) |entry, i| { + self.index.getEntry(entry.name).?.value.appendAssumeCapacity(i); } } pub fn sort(self: *Self) void { std.sort.sort(HeaderEntry, self.data.items, {}, HeaderEntry.compare); - self.rebuild_index(); + self.rebuildIndex(); } pub fn format( diff --git a/lib/std/json.zig b/lib/std/json.zig index 6377b69a80445338193f5dac9a16ef4f6ab897d4..a8b19756da80c2145afb59a177c17334950ead21 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -2149,27 +2149,27 @@ test "json.parser.dynamic" { var root = tree.root; - var image = root.Object.get("Image").?.value; + var image = root.Object.get("Image").?; - const width = image.Object.get("Width").?.value; + const width = image.Object.get("Width").?; testing.expect(width.Integer == 800); - const height = image.Object.get("Height").?.value; + const height = image.Object.get("Height").?; testing.expect(height.Integer == 600); - const title = image.Object.get("Title").?.value; + const title = image.Object.get("Title").?; testing.expect(mem.eql(u8, title.String, "View from 15th Floor")); - const animated = image.Object.get("Animated").?.value; + const animated = image.Object.get("Animated").?; testing.expect(animated.Bool == false); - const array_of_object = image.Object.get("ArrayOfObject").?.value; + const array_of_object = image.Object.get("ArrayOfObject").?; testing.expect(array_of_object.Array.items.len == 1); - const obj0 = array_of_object.Array.items[0].Object.get("n").?.value; + const obj0 = array_of_object.Array.items[0].Object.get("n").?; testing.expect(mem.eql(u8, obj0.String, "m")); - const double = image.Object.get("double").?.value; + const double = image.Object.get("double").?; testing.expect(double.Float == 1.3412); } @@ -2217,12 +2217,12 @@ test "write json then parse it" { var tree = try parser.parse(fixed_buffer_stream.getWritten()); defer tree.deinit(); - testing.expect(tree.root.Object.get("f").?.value.Bool == false); - testing.expect(tree.root.Object.get("t").?.value.Bool == true); - testing.expect(tree.root.Object.get("int").?.value.Integer == 1234); - testing.expect(tree.root.Object.get("array").?.value.Array.items[0].Null == {}); - testing.expect(tree.root.Object.get("array").?.value.Array.items[1].Float == 12.34); - testing.expect(mem.eql(u8, tree.root.Object.get("str").?.value.String, "hello")); + testing.expect(tree.root.Object.get("f").?.Bool == false); + testing.expect(tree.root.Object.get("t").?.Bool == true); + testing.expect(tree.root.Object.get("int").?.Integer == 1234); + testing.expect(tree.root.Object.get("array").?.Array.items[0].Null == {}); + testing.expect(tree.root.Object.get("array").?.Array.items[1].Float == 12.34); + testing.expect(mem.eql(u8, tree.root.Object.get("str").?.String, "hello")); } fn test_parse(arena_allocator: *std.mem.Allocator, json_str: []const u8) !Value { @@ -2245,7 +2245,7 @@ test "integer after float has proper type" { \\ "ints": [1, 2, 3] \\} ); - std.testing.expect(json.Object.getValue("ints").?.Array.items[0] == .Integer); + std.testing.expect(json.Object.get("ints").?.Array.items[0] == .Integer); } test "escaped characters" { @@ -2271,16 +2271,16 @@ test "escaped characters" { const obj = (try test_parse(&arena_allocator.allocator, input)).Object; - testing.expectEqualSlices(u8, obj.get("backslash").?.value.String, "\\"); - testing.expectEqualSlices(u8, obj.get("forwardslash").?.value.String, "/"); - testing.expectEqualSlices(u8, obj.get("newline").?.value.String, "\n"); - testing.expectEqualSlices(u8, obj.get("carriagereturn").?.value.String, "\r"); - testing.expectEqualSlices(u8, obj.get("tab").?.value.String, "\t"); - testing.expectEqualSlices(u8, obj.get("formfeed").?.value.String, "\x0C"); - testing.expectEqualSlices(u8, obj.get("backspace").?.value.String, "\x08"); - testing.expectEqualSlices(u8, obj.get("doublequote").?.value.String, "\""); - testing.expectEqualSlices(u8, obj.get("unicode").?.value.String, "ą"); - testing.expectEqualSlices(u8, obj.get("surrogatepair").?.value.String, "😂"); + testing.expectEqualSlices(u8, obj.get("backslash").?.String, "\\"); + testing.expectEqualSlices(u8, obj.get("forwardslash").?.String, "/"); + testing.expectEqualSlices(u8, obj.get("newline").?.String, "\n"); + testing.expectEqualSlices(u8, obj.get("carriagereturn").?.String, "\r"); + testing.expectEqualSlices(u8, obj.get("tab").?.String, "\t"); + testing.expectEqualSlices(u8, obj.get("formfeed").?.String, "\x0C"); + testing.expectEqualSlices(u8, obj.get("backspace").?.String, "\x08"); + testing.expectEqualSlices(u8, obj.get("doublequote").?.String, "\""); + testing.expectEqualSlices(u8, obj.get("unicode").?.String, "ą"); + testing.expectEqualSlices(u8, obj.get("surrogatepair").?.String, "😂"); } test "string copy option" { @@ -2306,11 +2306,11 @@ test "string copy option" { const obj_copy = tree_copy.root.Object; for ([_][]const u8{ "noescape", "simple", "unicode", "surrogatepair" }) |field_name| { - testing.expectEqualSlices(u8, obj_nocopy.getValue(field_name).?.String, obj_copy.getValue(field_name).?.String); + testing.expectEqualSlices(u8, obj_nocopy.get(field_name).?.String, obj_copy.get(field_name).?.String); } - const nocopy_addr = &obj_nocopy.getValue("noescape").?.String[0]; - const copy_addr = &obj_copy.getValue("noescape").?.String[0]; + const nocopy_addr = &obj_nocopy.get("noescape").?.String[0]; + const copy_addr = &obj_copy.get("noescape").?.String[0]; var found_nocopy = false; for (input) |_, index| { diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 3743b4f3342bad0111060cd58ef073847960e8a2..33f422692c9209bf2542af227d1b86256817fca1 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -720,7 +720,7 @@ fn fmtPathDir( defer dir.close(); const stat = try dir.stat(); - if (try fmt.seen.put(stat.inode, {})) |_| return; + if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; var dir_it = dir.iterate(); while (try dir_it.next()) |entry| { @@ -768,7 +768,7 @@ fn fmtPathFile( defer fmt.gpa.free(source_code); // Add to set after no longer possible to get error.IsDir. - if (try fmt.seen.put(stat.inode, {})) |_| return; + if (try fmt.seen.fetchPut(stat.inode, {})) |_| return; const tree = try std.zig.parse(fmt.gpa, source_code); defer tree.deinit(); diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 171846d380e7329c80d70bcd97da6b87ad6eeacc..261cef37b5035cb63c5bde1c28da1e5e9d5c5c1c 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -20,7 +20,7 @@ pub const Error = error{OutOfMemory}; const TypeError = Error || error{UnsupportedType}; const TransError = TypeError || error{UnsupportedTranslation}; -const DeclTable = std.HashMap(usize, []const u8, addrHash, addrEql); +const DeclTable = std.HashMap(usize, []const u8, addrHash, addrEql, false); fn addrHash(x: usize) u32 { switch (@typeInfo(usize).Int.bits) { @@ -776,8 +776,8 @@ fn checkForBuiltinTypedef(checked_name: []const u8) ?[]const u8 { } fn transTypeDef(c: *Context, typedef_decl: *const ZigClangTypedefNameDecl, top_level_visit: bool) Error!?*ast.Node { - if (c.decl_table.get(@ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)))) |kv| - return transCreateNodeIdentifier(c, kv.value); // Avoid processing this decl twice + if (c.decl_table.get(@ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)))) |name| + return transCreateNodeIdentifier(c, name); // Avoid processing this decl twice const rp = makeRestorePoint(c); const typedef_name = try c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, typedef_decl))); @@ -818,8 +818,8 @@ fn transCreateNodeTypedef(rp: RestorePoint, typedef_decl: *const ZigClangTypedef } fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?*ast.Node { - if (c.decl_table.get(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)))) |kv| - return try transCreateNodeIdentifier(c, kv.value); // Avoid processing this decl twice + if (c.decl_table.get(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)))) |name| + return try transCreateNodeIdentifier(c, name); // Avoid processing this decl twice const record_loc = ZigClangRecordDecl_getLocation(record_decl); var bare_name = try c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, record_decl))); @@ -969,7 +969,7 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?* fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.Node { if (c.decl_table.get(@ptrToInt(ZigClangEnumDecl_getCanonicalDecl(enum_decl)))) |name| - return try transCreateNodeIdentifier(c, name.value); // Avoid processing this decl twice + return try transCreateNodeIdentifier(c, name); // Avoid processing this decl twice const rp = makeRestorePoint(c); const enum_loc = ZigClangEnumDecl_getLocation(enum_decl); @@ -2130,7 +2130,7 @@ fn transInitListExprRecord( var raw_name = try rp.c.str(ZigClangNamedDecl_getName_bytes_begin(@ptrCast(*const ZigClangNamedDecl, field_decl))); if (ZigClangFieldDecl_isAnonymousStructOrUnion(field_decl)) { const name = rp.c.decl_table.get(@ptrToInt(ZigClangFieldDecl_getCanonicalDecl(field_decl))).?; - raw_name = try mem.dupe(rp.c.arena, u8, name.value); + raw_name = try mem.dupe(rp.c.arena, u8, name); } const field_name_tok = try appendIdentifier(rp.c, raw_name); @@ -2855,7 +2855,7 @@ fn transMemberExpr(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangMemberE const field_decl = @ptrCast(*const struct_ZigClangFieldDecl, member_decl); if (ZigClangFieldDecl_isAnonymousStructOrUnion(field_decl)) { const name = rp.c.decl_table.get(@ptrToInt(ZigClangFieldDecl_getCanonicalDecl(field_decl))).?; - break :blk try mem.dupe(rp.c.arena, u8, name.value); + break :blk try mem.dupe(rp.c.arena, u8, name); } } const decl = @ptrCast(*const ZigClangNamedDecl, member_decl); @@ -6040,8 +6040,8 @@ fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { } else if (node.id == .PrefixOp) { return node; } else if (node.cast(ast.Node.Identifier)) |ident| { - if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |kv| { - if (kv.value.cast(ast.Node.VarDecl)) |var_decl| + if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { + if (value.cast(ast.Node.VarDecl)) |var_decl| return getContainer(c, var_decl.init_node.?); } } else if (node.cast(ast.Node.InfixOp)) |infix| { @@ -6064,8 +6064,8 @@ fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { if (ref.cast(ast.Node.Identifier)) |ident| { - if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |kv| { - if (kv.value.cast(ast.Node.VarDecl)) |var_decl| { + if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { + if (value.cast(ast.Node.VarDecl)) |var_decl| { if (var_decl.type_node) |ty| return getContainer(c, ty); } @@ -6104,8 +6104,7 @@ fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto { } fn addMacros(c: *Context) !void { - var macro_it = c.global_scope.macro_table.iterator(); - while (macro_it.next()) |kv| { + for (c.global_scope.macro_table.items()) |kv| { if (getFnProto(c, kv.value)) |proto_node| { // If a macro aliases a global variable which is a function pointer, we conclude that // the macro is intended to represent a function that assumes the function pointer -- 2.54.0 From 3c8b13d998c5c58c8171d36d7506ea3a181d0db9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 4 Jul 2020 02:07:27 +0000 Subject: [PATCH 182/295] std hash map: do the pow2 improvement again it's a noticeable speedup --- lib/std/hash_map.zig | 46 ++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index d0b18d941957ab3ec9bbbd1fc7ce08c13e5fed1f..0fe8ae34f197b658ed75eaa0ad63b6de5008aa37 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -382,22 +382,24 @@ pub fn HashMapUnmanaged( try self.entries.ensureCapacity(allocator, new_capacity); if (new_capacity <= linear_scan_max) return; - // Resize if indexes would be more than 60% full. + // Ensure that the indexes will be at most 60% full if + // `new_capacity` items are put into it. const needed_len = new_capacity * 5 / 3; if (self.index_header) |header| { if (needed_len > header.indexes_len) { - var new_indexes_len = header.indexes_len; - while (true) { - new_indexes_len *= new_indexes_len / 2 + 8; - if (new_indexes_len >= needed_len) break; - } + // An overflow here would mean the amount of memory required would not + // be representable in the address space. + const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable; const new_header = try IndexHeader.alloc(allocator, new_indexes_len); self.insertAllEntriesIntoNewHeader(new_header); header.free(allocator); self.index_header = new_header; } } else { - const header = try IndexHeader.alloc(allocator, needed_len); + // An overflow here would mean the amount of memory required would not + // be representable in the address space. + const new_indexes_len = math.ceilPowerOfTwo(usize, needed_len) catch unreachable; + const header = try IndexHeader.alloc(allocator, new_indexes_len); self.insertAllEntriesIntoNewHeader(header); self.index_header = header; } @@ -540,10 +542,10 @@ pub fn HashMapUnmanaged( fn removeInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) ?Entry { const indexes = header.indexes(I); const h = hash(key); - const start_index = header.hashToIndex(h); + const start_index = header.constrainIndex(h); var roll_over: usize = 0; while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { - const index_index = (start_index + roll_over) % header.indexes_len; + const index_index = header.constrainIndex(start_index + roll_over); var index = &indexes[index_index]; if (index.isEmpty()) return null; @@ -564,7 +566,7 @@ pub fn HashMapUnmanaged( // Now we have to shift over the following indexes. roll_over += 1; while (roll_over < header.indexes_len) : (roll_over += 1) { - const next_index_index = (start_index + roll_over) % header.indexes_len; + const next_index_index = header.constrainIndex(start_index + roll_over); const next_index = &indexes[next_index_index]; if (next_index.isEmpty() or next_index.distance_from_start_index == 0) { index.setEmpty(); @@ -588,10 +590,10 @@ pub fn HashMapUnmanaged( indexes: []Index(I), ) void { const h = if (store_hash) self.entries.items[new_entry_index].hash else hash(self.entries.items[new_entry_index].key); - const start_index = header.hashToIndex(h); + const start_index = header.constrainIndex(h); var roll_over: usize = 0; while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { - const index_index = (start_index + roll_over) % header.indexes_len; + const index_index = header.constrainIndex(start_index + roll_over); const index = &indexes[index_index]; if (index.entry_index == old_entry_index) { index.entry_index = @intCast(I, new_entry_index); @@ -605,14 +607,14 @@ pub fn HashMapUnmanaged( fn getOrPutInternal(self: *Self, key: K, header: *IndexHeader, comptime I: type) GetOrPutResult { const indexes = header.indexes(I); const h = hash(key); - const start_index = header.hashToIndex(h); + const start_index = header.constrainIndex(h); var roll_over: usize = 0; var distance_from_start_index: usize = 0; while (roll_over <= header.indexes_len) : ({ roll_over += 1; distance_from_start_index += 1; }) { - const index_index = (start_index + roll_over) % header.indexes_len; + const index_index = header.constrainIndex(start_index + roll_over); const index = indexes[index_index]; if (index.isEmpty()) { indexes[index_index] = .{ @@ -670,7 +672,7 @@ pub fn HashMapUnmanaged( roll_over += 1; distance_from_start_index += 1; }) { - const next_index_index = (start_index + roll_over) % header.indexes_len; + const next_index_index = header.constrainIndex(start_index + roll_over); const next_index = indexes[next_index_index]; if (next_index.isEmpty()) { header.maybeBumpMax(distance_from_start_index); @@ -702,10 +704,10 @@ pub fn HashMapUnmanaged( fn getInternal(self: Self, key: K, header: *IndexHeader, comptime I: type) ?*Entry { const indexes = header.indexes(I); const h = hash(key); - const start_index = header.hashToIndex(h); + const start_index = header.constrainIndex(h); var roll_over: usize = 0; while (roll_over <= header.max_distance_from_start_index) : (roll_over += 1) { - const index_index = (start_index + roll_over) % header.indexes_len; + const index_index = header.constrainIndex(start_index + roll_over); const index = indexes[index_index]; if (index.isEmpty()) return null; @@ -731,7 +733,7 @@ pub fn HashMapUnmanaged( const indexes = header.indexes(I); entry_loop: for (self.entries.items) |entry, i| { const h = if (store_hash) entry.hash else hash(entry.key); - const start_index = header.hashToIndex(h); + const start_index = header.constrainIndex(h); var entry_index = i; var roll_over: usize = 0; var distance_from_start_index: usize = 0; @@ -739,7 +741,7 @@ pub fn HashMapUnmanaged( roll_over += 1; distance_from_start_index += 1; }) { - const index_index = (start_index + roll_over) % header.indexes_len; + const index_index = header.constrainIndex(start_index + roll_over); const next_index = indexes[index_index]; if (next_index.isEmpty()) { header.maybeBumpMax(distance_from_start_index); @@ -814,8 +816,10 @@ const IndexHeader = struct { max_distance_from_start_index: usize, indexes_len: usize, - fn hashToIndex(header: IndexHeader, h: u32) usize { - return @as(usize, h) % header.indexes_len; + fn constrainIndex(header: IndexHeader, i: usize) usize { + // This is an optimization for modulo of power of two integers; + // it requires `indexes_len` to always be a power of two. + return i & (header.indexes_len - 1); } fn indexes(header: *IndexHeader, comptime I: type) []Index(I) { -- 2.54.0 From 3a89f214aa672c5844def1704845ad38ea60bdcd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 4 Jul 2020 22:25:49 +0000 Subject: [PATCH 183/295] update more HashMap API usage --- doc/docgen.zig | 2 +- doc/langref.html.in | 14 +-- lib/std/debug.zig | 6 +- lib/std/hash_map.zig | 2 +- src-self-hosted/Module.zig | 197 +++++++++++++++--------------------- src-self-hosted/codegen.zig | 4 +- src-self-hosted/link.zig | 6 +- src-self-hosted/zir.zig | 34 +++---- 8 files changed, 112 insertions(+), 153 deletions(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index 7886c7cc90e2b9a862135f4a732ad4c182286d9d..e2acfae768d9efb192f4e1e093715339240183ed 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -392,7 +392,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { .n = header_stack_size, }, }); - if (try urls.put(urlized, tag_token)) |entry| { + if (try urls.fetchPut(urlized, tag_token)) |entry| { parseError(tokenizer, tag_token, "duplicate header url: #{}", .{urlized}) catch {}; parseError(tokenizer, entry.value, "other tag here", .{}) catch {}; return error.ParseError; diff --git a/doc/langref.html.in b/doc/langref.html.in index dfbb93decfb5529d0e114b07569c0a21bfd2e668..d7b9de3c6e493200a3f697dccb990adb0f58ab3d 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5363,11 +5363,11 @@ const std = @import("std"); const assert = std.debug.assert; test "turn HashMap into a set with void" { - var map = std.HashMap(i32, void, hash_i32, eql_i32).init(std.testing.allocator); + var map = std.AutoHashMap(i32, void).init(std.testing.allocator); defer map.deinit(); - _ = try map.put(1, {}); - _ = try map.put(2, {}); + try map.put(1, {}); + try map.put(2, {}); assert(map.contains(2)); assert(!map.contains(3)); @@ -5375,14 +5375,6 @@ test "turn HashMap into a set with void" { _ = map.remove(2); assert(!map.contains(2)); } - -fn hash_i32(x: i32) u32 { - return @bitCast(u32, x); -} - -fn eql_i32(a: i32, b: i32) bool { - return a == b; -} {#code_end#}

Note that this is different from using a dummy value for the hash map value. By using {#syntax#}void{#endsyntax#} as the type of the value, the hash map entry type has no value field, and diff --git a/lib/std/debug.zig b/lib/std/debug.zig index e9bafec94c967a8a93a38a6117595b97e0bdd8e6..e6d0c17da4b783fc9a56d9b44c04ed2d63e90351 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1132,7 +1132,7 @@ pub const DebugInfo = struct { const seg_end = seg_start + segment_cmd.vmsize; if (rebased_address >= seg_start and rebased_address < seg_end) { - if (self.address_map.getValue(base_address)) |obj_di| { + if (self.address_map.get(base_address)) |obj_di| { return obj_di; } @@ -1204,7 +1204,7 @@ pub const DebugInfo = struct { const seg_end = seg_start + info.SizeOfImage; if (address >= seg_start and address < seg_end) { - if (self.address_map.getValue(seg_start)) |obj_di| { + if (self.address_map.get(seg_start)) |obj_di| { return obj_di; } @@ -1441,7 +1441,7 @@ pub const ModuleDebugInfo = switch (builtin.os.tag) { const o_file_path = mem.spanZ(self.strings[symbol.ofile.?.n_strx..]); // Check if its debug infos are already in the cache - var o_file_di = self.ofiles.getValue(o_file_path) orelse + var o_file_di = self.ofiles.get(o_file_path) orelse (self.loadOFile(o_file_path) catch |err| switch (err) { error.FileNotFound, error.MissingDebugInfo, diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index 0fe8ae34f197b658ed75eaa0ad63b6de5008aa37..aaec9a4d58758a945953fc4008c4ad6af1134dde 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -458,7 +458,7 @@ pub fn HashMapUnmanaged( } /// Inserts a new `Entry` into the hash map, returning the previous one, if any. - /// If insertion happuns, asserts there is enough capacity without allocating. + /// If insertion happens, asserts there is enough capacity without allocating. pub fn fetchPutAssumeCapacity(self: *Self, key: K, value: V) ?Entry { const gop = self.getOrPutAssumeCapacity(key); var result: ?Entry = null; diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 7a61cd5ccdee421991f8abbea459c7ee03af5048..0c80803fc7baca06c59ecc389734526c89963a9d 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -75,7 +75,7 @@ deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, keep_source_files_loaded: bool, -const DeclTable = std.HashMap(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql); +const DeclTable = std.HashMap(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false); const WorkItem = union(enum) { /// Write the machine code for a Decl to the output file. @@ -795,49 +795,38 @@ pub fn deinit(self: *Module) void { const allocator = self.allocator; self.deletion_set.deinit(allocator); self.work_queue.deinit(); - { - var it = self.decl_table.iterator(); - while (it.next()) |kv| { - kv.value.destroy(allocator); - } - self.decl_table.deinit(); + + for (self.decl_table.items()) |entry| { + entry.value.destroy(allocator); } - { - var it = self.failed_decls.iterator(); - while (it.next()) |kv| { - kv.value.destroy(allocator); - } - self.failed_decls.deinit(); + self.decl_table.deinit(); + + for (self.failed_decls.items()) |entry| { + entry.value.destroy(allocator); } - { - var it = self.failed_files.iterator(); - while (it.next()) |kv| { - kv.value.destroy(allocator); - } - self.failed_files.deinit(); + self.failed_decls.deinit(); + + for (self.failed_files.items()) |entry| { + entry.value.destroy(allocator); } - { - var it = self.failed_exports.iterator(); - while (it.next()) |kv| { - kv.value.destroy(allocator); - } - self.failed_exports.deinit(); + self.failed_files.deinit(); + + for (self.failed_exports.items()) |entry| { + entry.value.destroy(allocator); } - { - var it = self.decl_exports.iterator(); - while (it.next()) |kv| { - const export_list = kv.value; - allocator.free(export_list); - } - self.decl_exports.deinit(); + self.failed_exports.deinit(); + + for (self.decl_exports.items()) |entry| { + const export_list = entry.value; + allocator.free(export_list); } - { - var it = self.export_owners.iterator(); - while (it.next()) |kv| { - freeExportList(allocator, kv.value); - } - self.export_owners.deinit(); + self.decl_exports.deinit(); + + for (self.export_owners.items()) |entry| { + freeExportList(allocator, entry.value); } + self.export_owners.deinit(); + self.symbol_exports.deinit(); self.root_scope.destroy(allocator); self.* = undefined; @@ -918,9 +907,9 @@ pub fn makeBinFileWritable(self: *Module) !void { } pub fn totalErrorCount(self: *Module) usize { - const total = self.failed_decls.size + - self.failed_files.size + - self.failed_exports.size; + const total = self.failed_decls.items().len + + self.failed_files.items().len + + self.failed_exports.items().len; return if (total == 0) @boolToInt(self.link_error_flags.no_entry_point_found) else total; } @@ -931,32 +920,23 @@ pub fn getAllErrorsAlloc(self: *Module) !AllErrors { var errors = std.ArrayList(AllErrors.Message).init(self.allocator); defer errors.deinit(); - { - var it = self.failed_files.iterator(); - while (it.next()) |kv| { - const scope = kv.key; - const err_msg = kv.value; - const source = try scope.getSource(self); - try AllErrors.add(&arena, &errors, scope.subFilePath(), source, err_msg.*); - } + for (self.failed_files.items()) |entry| { + const scope = entry.key; + const err_msg = entry.value; + const source = try scope.getSource(self); + try AllErrors.add(&arena, &errors, scope.subFilePath(), source, err_msg.*); } - { - var it = self.failed_decls.iterator(); - while (it.next()) |kv| { - const decl = kv.key; - const err_msg = kv.value; - const source = try decl.scope.getSource(self); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); - } + for (self.failed_decls.items()) |entry| { + const decl = entry.key; + const err_msg = entry.value; + const source = try decl.scope.getSource(self); + try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); } - { - var it = self.failed_exports.iterator(); - while (it.next()) |kv| { - const decl = kv.key.owner_decl; - const err_msg = kv.value; - const source = try decl.scope.getSource(self); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); - } + for (self.failed_exports.items()) |entry| { + const decl = entry.key.owner_decl; + const err_msg = entry.value; + const source = try decl.scope.getSource(self); + try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); } if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) { @@ -1016,7 +996,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { decl.analysis = .dependency_failure; }, else => { - try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); + try self.failed_decls.ensureCapacity(self.failed_decls.items().len + 1); self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( self.allocator, decl.src(), @@ -1086,7 +1066,7 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => return error.AnalysisFail, else => { - try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); + try self.failed_decls.ensureCapacity(self.failed_decls.items().len + 1); self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( self.allocator, decl.src(), @@ -1636,7 +1616,7 @@ fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { switch (root_scope.status) { .never_loaded, .unloaded_success => { - try self.failed_files.ensureCapacity(self.failed_files.size + 1); + try self.failed_files.ensureCapacity(self.failed_files.items().len + 1); const source = try root_scope.getSource(self); @@ -1677,7 +1657,7 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { switch (root_scope.status) { .never_loaded, .unloaded_success => { - try self.failed_files.ensureCapacity(self.failed_files.size + 1); + try self.failed_files.ensureCapacity(self.failed_files.items().len + 1); const source = try root_scope.getSource(self); @@ -1745,8 +1725,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { const name = tree.tokenSliceLoc(name_loc); const name_hash = root_scope.fullyQualifiedNameHash(name); const contents_hash = std.zig.hashSrc(tree.getNodeSource(src_decl)); - if (self.decl_table.get(name_hash)) |kv| { - const decl = kv.value; + if (self.decl_table.get(name_hash)) |decl| { // Update the AST Node index of the decl, even if its contents are unchanged, it may // have been re-ordered. decl.src_index = decl_i; @@ -1774,14 +1753,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { // TODO also look for global variable declarations // TODO also look for comptime blocks and exported globals } - { - // Handle explicitly deleted decls from the source code. Not to be confused - // with when we delete decls because they are no longer referenced. - var it = deleted_decls.iterator(); - while (it.next()) |kv| { - //std.debug.warn("noticed '{}' deleted from source\n", .{kv.key.name}); - try self.deleteDecl(kv.key); - } + // Handle explicitly deleted decls from the source code. Not to be confused + // with when we delete decls because they are no longer referenced. + for (deleted_decls.items()) |entry| { + //std.debug.warn("noticed '{}' deleted from source\n", .{entry.key.name}); + try self.deleteDecl(entry.key); } } @@ -1800,18 +1776,14 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { // we know which ones have been deleted. var deleted_decls = std.AutoHashMap(*Decl, void).init(self.allocator); defer deleted_decls.deinit(); - try deleted_decls.ensureCapacity(self.decl_table.size); - { - var it = self.decl_table.iterator(); - while (it.next()) |kv| { - deleted_decls.putAssumeCapacityNoClobber(kv.value, {}); - } + try deleted_decls.ensureCapacity(self.decl_table.items().len); + for (self.decl_table.items()) |entry| { + deleted_decls.putAssumeCapacityNoClobber(entry.value, {}); } for (src_module.decls) |src_decl, decl_i| { const name_hash = root_scope.fullyQualifiedNameHash(src_decl.name); - if (self.decl_table.get(name_hash)) |kv| { - const decl = kv.value; + if (self.decl_table.get(name_hash)) |decl| { deleted_decls.removeAssertDiscard(decl); //std.debug.warn("'{}' contents: '{}'\n", .{ src_decl.name, src_decl.contents }); if (!srcHashEql(src_decl.contents_hash, decl.contents_hash)) { @@ -1835,14 +1807,11 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { for (exports_to_resolve.items) |export_decl| { _ = try self.resolveZirDecl(&root_scope.base, export_decl); } - { - // Handle explicitly deleted decls from the source code. Not to be confused - // with when we delete decls because they are no longer referenced. - var it = deleted_decls.iterator(); - while (it.next()) |kv| { - //std.debug.warn("noticed '{}' deleted from source\n", .{kv.key.name}); - try self.deleteDecl(kv.key); - } + // Handle explicitly deleted decls from the source code. Not to be confused + // with when we delete decls because they are no longer referenced. + for (deleted_decls.items()) |entry| { + //std.debug.warn("noticed '{}' deleted from source\n", .{entry.key.name}); + try self.deleteDecl(entry.key); } } @@ -1888,7 +1857,7 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { const kv = self.export_owners.remove(decl) orelse return; for (kv.value) |exp| { - if (self.decl_exports.get(exp.exported_decl)) |decl_exports_kv| { + if (self.decl_exports.getEntry(exp.exported_decl)) |decl_exports_kv| { // Remove exports with owner_decl matching the regenerating decl. const list = decl_exports_kv.value; var i: usize = 0; @@ -1983,7 +1952,7 @@ fn createNewDecl( name_hash: Scope.NameHash, contents_hash: std.zig.SrcHash, ) !*Decl { - try self.decl_table.ensureCapacity(self.decl_table.size + 1); + try self.decl_table.ensureCapacity(self.decl_table.items().len + 1); const new_decl = try self.allocateNewDecl(scope, src_index, contents_hash); errdefer self.allocator.destroy(new_decl); new_decl.name = try mem.dupeZ(self.allocator, u8, decl_name); @@ -2043,7 +2012,7 @@ fn resolveZirDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError! fn resolveZirDeclHavingIndex(self: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl { const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name); - const decl = self.decl_table.getValue(name_hash).?; + const decl = self.decl_table.get(name_hash).?; decl.src_index = src_index; try self.ensureDeclAnalyzed(decl); return decl; @@ -2148,8 +2117,8 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), } - try self.decl_exports.ensureCapacity(self.decl_exports.size + 1); - try self.export_owners.ensureCapacity(self.export_owners.size + 1); + try self.decl_exports.ensureCapacity(self.decl_exports.items().len + 1); + try self.export_owners.ensureCapacity(self.export_owners.items().len + 1); const new_export = try self.allocator.create(Export); errdefer self.allocator.destroy(new_export); @@ -2168,23 +2137,23 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const // Add to export_owners table. const eo_gop = self.export_owners.getOrPut(owner_decl) catch unreachable; if (!eo_gop.found_existing) { - eo_gop.kv.value = &[0]*Export{}; + eo_gop.entry.value = &[0]*Export{}; } - eo_gop.kv.value = try self.allocator.realloc(eo_gop.kv.value, eo_gop.kv.value.len + 1); - eo_gop.kv.value[eo_gop.kv.value.len - 1] = new_export; - errdefer eo_gop.kv.value = self.allocator.shrink(eo_gop.kv.value, eo_gop.kv.value.len - 1); + eo_gop.entry.value = try self.allocator.realloc(eo_gop.entry.value, eo_gop.entry.value.len + 1); + eo_gop.entry.value[eo_gop.entry.value.len - 1] = new_export; + errdefer eo_gop.entry.value = self.allocator.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); // Add to exported_decl table. const de_gop = self.decl_exports.getOrPut(exported_decl) catch unreachable; if (!de_gop.found_existing) { - de_gop.kv.value = &[0]*Export{}; + de_gop.entry.value = &[0]*Export{}; } - de_gop.kv.value = try self.allocator.realloc(de_gop.kv.value, de_gop.kv.value.len + 1); - de_gop.kv.value[de_gop.kv.value.len - 1] = new_export; - errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1); + de_gop.entry.value = try self.allocator.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); + de_gop.entry.value[de_gop.entry.value.len - 1] = new_export; + errdefer de_gop.entry.value = self.allocator.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); if (self.symbol_exports.get(symbol_name)) |_| { - try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); + try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1); self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( self.allocator, src, @@ -2197,10 +2166,10 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const } try self.symbol_exports.putNoClobber(symbol_name, new_export); - self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) { + self.bin_file.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { - try self.failed_exports.ensureCapacity(self.failed_exports.size + 1); + try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1); self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( self.allocator, src, @@ -2494,7 +2463,7 @@ fn getNextAnonNameIndex(self: *Module) usize { fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*Decl { const namespace = scope.namespace(); const name_hash = namespace.fullyQualifiedNameHash(ident_name); - return self.decl_table.getValue(name_hash); + return self.decl_table.get(name_hash); } fn analyzeInstExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst { @@ -3489,8 +3458,8 @@ fn failNode( fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *ErrorMsg) InnerError { { errdefer err_msg.destroy(self.allocator); - try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); - try self.failed_files.ensureCapacity(self.failed_files.size + 1); + try self.failed_decls.ensureCapacity(self.failed_decls.items().len + 1); + try self.failed_files.ensureCapacity(self.failed_files.items().len + 1); } switch (scope.tag) { .decl => { diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 73758bda87c7b212dca2c0adb805a6c9b53c3c19..8885ed282558632825f698ed8711d9b8a57a9912 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -705,7 +705,7 @@ const Function = struct { } fn resolveInst(self: *Function, inst: *ir.Inst) !MCValue { - if (self.inst_table.getValue(inst)) |mcv| { + if (self.inst_table.get(inst)) |mcv| { return mcv; } if (inst.cast(ir.Inst.Constant)) |const_inst| { @@ -713,7 +713,7 @@ const Function = struct { try self.inst_table.putNoClobber(inst, mcvalue); return mcvalue; } else { - return self.inst_table.getValue(inst).?; + return self.inst_table.get(inst).?; } } diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index c6acf21b84727166d16e47abc457fbcb14875a01..c615ad35fdaab745905eb7b0e5fab2155aff10ce 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -1071,7 +1071,7 @@ pub const ElfFile = struct { try self.file.?.pwriteAll(code, file_offset); // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. - const decl_exports = module.decl_exports.getValue(decl) orelse &[0]*Module.Export{}; + const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; return self.updateDeclExports(module, decl, decl_exports); } @@ -1093,7 +1093,7 @@ pub const ElfFile = struct { for (exports) |exp| { if (exp.options.section) |section_name| { if (!mem.eql(u8, section_name, ".text")) { - try module.failed_exports.ensureCapacity(module.failed_exports.size + 1); + try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}), @@ -1111,7 +1111,7 @@ pub const ElfFile = struct { }, .Weak => elf.STB_WEAK, .LinkOnce => { - try module.failed_exports.ensureCapacity(module.failed_exports.size + 1); + try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 92dbc66e2b9f928f9dca1dcfee5e3c0a93645ff1..7dceaaea1bb757f0d4ad98e7d1bc55c54e111a76 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -758,7 +758,7 @@ pub const Module = struct { } fn writeInstParamToStream(self: Module, stream: var, inst: *Inst, inst_table: *const InstPtrTable) !void { - if (inst_table.getValue(inst)) |info| { + if (inst_table.get(inst)) |info| { if (info.index) |i| { try stream.print("%{}", .{info.index}); } else { @@ -843,7 +843,7 @@ const Parser = struct { skipSpace(self); const decl = try parseInstruction(self, &body_context, ident); const ident_index = body_context.instructions.items.len; - if (try body_context.name_map.put(ident, decl.inst)) |_| { + if (try body_context.name_map.fetchPut(ident, decl.inst)) |_| { return self.fail("redefinition of identifier '{}'", .{ident}); } try body_context.instructions.append(decl.inst); @@ -929,7 +929,7 @@ const Parser = struct { skipSpace(self); const decl = try parseInstruction(self, null, ident); const ident_index = self.decls.items.len; - if (try self.global_name_map.put(ident, decl.inst)) |_| { + if (try self.global_name_map.fetchPut(ident, decl.inst)) |_| { return self.fail("redefinition of identifier '{}'", .{ident}); } try self.decls.append(self.allocator, decl); @@ -1153,7 +1153,7 @@ const Parser = struct { else => continue, }; const ident = self.source[name_start..self.i]; - const kv = map.get(ident) orelse { + return map.get(ident) orelse { const bad_name = self.source[name_start - 1 .. self.i]; const src = name_start - 1; if (local_ref) { @@ -1172,7 +1172,6 @@ const Parser = struct { return &declval.base; } }; - return kv.value; } fn generateName(self: *Parser) ![]u8 { @@ -1219,13 +1218,12 @@ const EmitZIR = struct { // by the hash table. var src_decls = std.ArrayList(*IrModule.Decl).init(self.allocator); defer src_decls.deinit(); - try src_decls.ensureCapacity(self.old_module.decl_table.size); - try self.decls.ensureCapacity(self.allocator, self.old_module.decl_table.size); - try self.names.ensureCapacity(self.old_module.decl_table.size); + try src_decls.ensureCapacity(self.old_module.decl_table.items().len); + try self.decls.ensureCapacity(self.allocator, self.old_module.decl_table.items().len); + try self.names.ensureCapacity(self.old_module.decl_table.items().len); - var decl_it = self.old_module.decl_table.iterator(); - while (decl_it.next()) |kv| { - const decl = kv.value; + for (self.old_module.decl_table.items()) |entry| { + const decl = entry.value; src_decls.appendAssumeCapacity(decl); self.names.putAssumeCapacityNoClobber(mem.spanZ(decl.name), {}); } @@ -1248,7 +1246,7 @@ const EmitZIR = struct { .codegen_failure, .dependency_failure, .codegen_failure_retryable, - => if (self.old_module.failed_decls.getValue(ir_decl)) |err_msg| { + => if (self.old_module.failed_decls.get(ir_decl)) |err_msg| { const fail_inst = try self.arena.allocator.create(Inst.CompileError); fail_inst.* = .{ .base = .{ @@ -1270,7 +1268,7 @@ const EmitZIR = struct { continue; }, } - if (self.old_module.export_owners.getValue(ir_decl)) |exports| { + if (self.old_module.export_owners.get(ir_decl)) |exports| { for (exports) |module_export| { const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name); const export_inst = try self.arena.allocator.create(Inst.Export); @@ -1314,7 +1312,7 @@ const EmitZIR = struct { try new_body.inst_table.putNoClobber(inst, new_inst); return new_inst; } else { - return new_body.inst_table.getValue(inst).?; + return new_body.inst_table.get(inst).?; } } @@ -1424,7 +1422,7 @@ const EmitZIR = struct { try self.emitBody(body, &inst_table, &instructions); }, .sema_failure => { - const err_msg = self.old_module.failed_decls.getValue(module_fn.owner_decl).?; + const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?; const fail_inst = try self.arena.allocator.create(Inst.CompileError); fail_inst.* = .{ .base = .{ @@ -1841,7 +1839,7 @@ const EmitZIR = struct { self.next_auto_name += 1; const gop = try self.names.getOrPut(proposed_name); if (!gop.found_existing) { - gop.kv.value = {}; + gop.entry.value = {}; return proposed_name; } } @@ -1861,9 +1859,9 @@ const EmitZIR = struct { }, .kw_args = .{}, }; - gop.kv.value = try self.emitUnnamedDecl(&primitive_inst.base); + gop.entry.value = try self.emitUnnamedDecl(&primitive_inst.base); } - return gop.kv.value; + return gop.entry.value; } fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Decl { -- 2.54.0 From 27c1e0b453d50f8c242fd1153290a00b74dff018 Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Wed, 1 Jul 2020 23:04:05 -0600 Subject: [PATCH 184/295] Fix issue 5757: increase branch quota for formatting enums --- lib/std/fmt.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index cf0b21dc3a43826121090f32a2aaa87426d9e788..e09775ba95bc07c5397a74b3e5e942c2e1a2d2af 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -366,6 +366,7 @@ pub fn formatType( } // Use @tagName only if value is one of known fields + @setEvalBranchQuota(3 * enumInfo.fields.len); inline for (enumInfo.fields) |enumField| { if (@enumToInt(value) == enumField.value) { try writer.writeAll("."); @@ -1321,6 +1322,9 @@ test "enum" { try testFmt("enum: Enum.Two\n", "enum: {}\n", .{&value}); try testFmt("enum: Enum.One\n", "enum: {x}\n", .{Enum.One}); try testFmt("enum: Enum.Two\n", "enum: {X}\n", .{Enum.Two}); + + // test very large enum to verify ct branch quota is large enough + try testFmt("enum: Win32Error.INVALID_FUNCTION\n", "enum: {}\n", .{std.os.windows.Win32Error.INVALID_FUNCTION}); } test "non-exhaustive enum" { -- 2.54.0 From 68be2299172f6d87ed5db2ad2a7fcbdef2085725 Mon Sep 17 00:00:00 2001 From: emekoi Date: Sat, 11 Apr 2020 22:30:55 -0500 Subject: [PATCH 185/295] added custom format method for WindowsVersion --- lib/std/target.zig | 30 ++++++++++++++++++++++++++++++ lib/std/zig/cross_target.zig | 4 ++-- src-self-hosted/stage2.zig | 25 ++++--------------------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 9df3e21e52f4ca26e95d52fea3a35dfd5d24c91a..8edc4acea86364aec82b6100098e38dbb842499d 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -101,6 +101,36 @@ pub const Target = struct { return @enumToInt(ver) >= @enumToInt(self.min) and @enumToInt(ver) <= @enumToInt(self.max); } }; + + pub fn format( + self: WindowsVersion, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + out_stream: var, + ) !void { + if (fmt.len > 0 and fmt[0] == 's') { + if ( + @enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.win10_19h1) + ) { + try std.fmt.format(out_stream, ".{}", .{@tagName(self)}); + } else { + try std.fmt.format(out_stream, + "@intToEnum(Target.Os.WindowsVersion, {})", + .{ @enumToInt(self) } + ); + } + } else { + if ( + @enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.win10_19h1) + ) { + try std.fmt.format(out_stream, "WindowsVersion.{}", .{@tagName(self)}); + } else { + try std.fmt.format(out_stream, "WindowsVersion(", .{@typeName(@This())}); + try std.fmt.format(out_stream, "{}", .{@enumToInt(self)}); + try out_stream.writeAll(")"); + } + } + } }; pub const LinuxVersionRange = struct { diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index 910d263c6a6f3d770220d38f9203408fc2e91852..5466b39f0bf499baa751c6a1f175b485905581c7 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -514,14 +514,14 @@ pub const CrossTarget = struct { switch (self.getOsVersionMin()) { .none => {}, .semver => |v| try result.outStream().print(".{}", .{v}), - .windows => |v| try result.outStream().print(".{}", .{@tagName(v)}), + .windows => |v| try result.outStream().print("{s}", .{v}), } } if (self.os_version_max) |max| { switch (max) { .none => {}, .semver => |v| try result.outStream().print("...{}", .{v}), - .windows => |v| try result.outStream().print("...{}", .{@tagName(v)}), + .windows => |v| try result.outStream().print("..{s}", .{v}), } } diff --git a/src-self-hosted/stage2.zig b/src-self-hosted/stage2.zig index bd24ffb399f1663f871fb92e2c53c99de0baf08c..91524d7396991766a283fef8797daf364d17a538 100644 --- a/src-self-hosted/stage2.zig +++ b/src-self-hosted/stage2.zig @@ -653,23 +653,6 @@ export fn stage2_libc_render(stage1_libc: *Stage2LibCInstallation, output_file: return .None; } -fn enumToString(value: var, type_name: []const u8) ![]const u8 { - switch (@typeInfo(@TypeOf(value))) { - .Enum => |e| { - if (e.is_exhaustive) { - return std.fmt.allocPrint(std.heap.c_allocator, ".{}", .{@tagName(value)}); - } else { - return std.fmt.allocPrint( - std.heap.c_allocator, - "@intToEnum({}, {})", - .{ type_name, @enumToInt(value) }, - ); - } - }, - else => unreachable, - } -} - // ABI warning const Stage2Target = extern struct { arch: c_int, @@ -887,13 +870,13 @@ const Stage2Target = extern struct { .windows => try os_builtin_str_buffer.outStream().print( \\ .windows = .{{ - \\ .min = {}, - \\ .max = {}, + \\ .min = {s}, + \\ .max = {s}, \\ }}}}, \\ , .{ - try enumToString(target.os.version_range.windows.min, "Target.Os.WindowsVersion"), - try enumToString(target.os.version_range.windows.max, "Target.Os.WindowsVersion"), + target.os.version_range.windows.min, + target.os.version_range.windows.max, }), } try os_builtin_str_buffer.appendSlice("};\n"); -- 2.54.0 From 86ef3202dd5f4198411b39abdbea293832ebaad9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 5 Jul 2020 22:44:25 +0000 Subject: [PATCH 186/295] add doc comment for std.Target.Os.WindowsVersion --- lib/std/target.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/target.zig b/lib/std/target.zig index 8edc4acea86364aec82b6100098e38dbb842499d..110b7a088f50a8a643a9fad2e7232837e12b6239 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -102,6 +102,8 @@ pub const Target = struct { } }; + /// This function is defined to serialize a Zig source code representation of this + /// type, that, when parsed, will deserialize into the same data. pub fn format( self: WindowsVersion, comptime fmt: []const u8, -- 2.54.0 From a0a93f20919e523cb68fc414221b5babe2586825 Mon Sep 17 00:00:00 2001 From: Alluet Date: Fri, 3 Jul 2020 15:35:18 -0400 Subject: [PATCH 187/295] Rewrite std.fmt.parseInt --- lib/std/fmt.zig | 87 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index e09775ba95bc07c5397a74b3e5e942c2e1a2d2af..2674ba485a75c0cb4527561db747966a89ab0b8b 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -996,49 +996,77 @@ pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, option return fbs.pos; } -pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T { - if (!T.is_signed) return parseUnsigned(T, buf, radix); - if (buf.len == 0) return @as(T, 0); - if (buf[0] == '-') { - return math.negate(try parseUnsigned(T, buf[1..], radix)); - } else if (buf[0] == '+') { - return parseUnsigned(T, buf[1..], radix); - } else { - return parseUnsigned(T, buf, radix); - } -} - -test "parseInt" { - std.testing.expect((parseInt(i32, "-10", 10) catch unreachable) == -10); - std.testing.expect((parseInt(i32, "+10", 10) catch unreachable) == 10); - std.testing.expect(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidCharacter); - std.testing.expect(if (parseInt(i32, "10 ", 10)) |_| false else |err| err == error.InvalidCharacter); - std.testing.expect(if (parseInt(u32, "-10", 10)) |_| false else |err| err == error.InvalidCharacter); - std.testing.expect((parseInt(u8, "255", 10) catch unreachable) == 255); - std.testing.expect(if (parseInt(u8, "256", 10)) |_| false else |err| err == error.Overflow); -} - -pub const ParseUnsignedError = error{ +pub const ParseIntError = error{ /// The result cannot fit in the type specified Overflow, - /// The input had a byte that was not a digit + /// The input was empty or had a byte that was not a digit InvalidCharacter, }; -pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseUnsignedError!T { +pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) ParseIntError!T { + if (buf.len == 0) return error.InvalidCharacter; + if (buf[0] == '+') return parseWithSign(T, buf[1..], radix, .Pos); + if (buf[0] == '-') return parseWithSign(T, buf[1..], radix, .Neg); + return parseWithSign(T, buf, radix, .Pos); +} + +test "parseInt" { + std.testing.expect((try parseInt(i32, "-10", 10)) == -10); + std.testing.expect((try parseInt(i32, "+10", 10)) == 10); + std.testing.expect((try parseInt(u32, "+10", 10)) == 10); + std.testing.expectError(error.Overflow, parseInt(u32, "-10", 10)); + std.testing.expectError(error.InvalidCharacter, parseInt(u32, " 10", 10)); + std.testing.expectError(error.InvalidCharacter, parseInt(u32, "10 ", 10)); + std.testing.expect((try parseInt(u8, "255", 10)) == 255); + std.testing.expectError(error.Overflow, parseInt(u8, "256", 10)); + + // +0 and -0 should work for unsigned + std.testing.expect((try parseInt(u8, "-0", 10)) == 0); + std.testing.expect((try parseInt(u8, "+0", 10)) == 0); + + // ensure minInt is parsed correctly + std.testing.expect((try parseInt(i8, "-128", 10)) == math.minInt(i8)); + std.testing.expect((try parseInt(i43, "-4398046511104", 10)) == math.minInt(i43)); + + // empty string or bare +- is invalid + std.testing.expectError(error.InvalidCharacter, parseInt(u32, "", 10)); + std.testing.expectError(error.InvalidCharacter, parseInt(i32, "", 10)); + std.testing.expectError(error.InvalidCharacter, parseInt(u32, "+", 10)); + std.testing.expectError(error.InvalidCharacter, parseInt(i32, "+", 10)); + std.testing.expectError(error.InvalidCharacter, parseInt(u32, "-", 10)); + std.testing.expectError(error.InvalidCharacter, parseInt(i32, "-", 10)); +} + +fn parseWithSign( + comptime T: type, + buf: []const u8, + radix: u8, + comptime sign: enum { Pos, Neg }, +) ParseIntError!T { + if (buf.len == 0) return error.InvalidCharacter; + + const add = switch (sign) { + .Pos => math.add, + .Neg => math.sub, + }; + var x: T = 0; for (buf) |c| { const digit = try charToDigit(c, radix); if (x != 0) x = try math.mul(T, x, try math.cast(T, radix)); - x = try math.add(T, x, try math.cast(T, digit)); + x = try add(T, x, try math.cast(T, digit)); } return x; } +pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseIntError!T { + return parseWithSign(T, buf, radix, .Pos); +} + test "parseUnsigned" { std.testing.expect((try parseUnsigned(u16, "050124", 10)) == 50124); std.testing.expect((try parseUnsigned(u16, "65535", 10)) == 65535); @@ -1064,6 +1092,13 @@ test "parseUnsigned" { std.testing.expect((try parseUnsigned(u1, "001", 16)) == 1); std.testing.expect((try parseUnsigned(u2, "3", 16)) == 3); std.testing.expectError(error.Overflow, parseUnsigned(u2, "4", 16)); + + // parseUnsigned does not expect a sign + std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "+0", 10)); + std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "-0", 10)); + + // test empty string error + std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "", 10)); } pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; -- 2.54.0 From 8fb392dbb443688cdf62e965935e17a4ae4a4267 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 29 Jun 2020 21:58:34 -0400 Subject: [PATCH 188/295] stage2: implement liveness analysis --- src-self-hosted/Module.zig | 6 ++ src-self-hosted/codegen.zig | 60 +++++++++++++--- src-self-hosted/ir.zig | 18 ++++- src-self-hosted/liveness.zig | 134 +++++++++++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 11 deletions(-) create mode 100644 src-self-hosted/liveness.zig diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index f4d65ab7c0ed207bfa36c338f98d69f2817e0467..4c83076caddbe83acb0f620c2516b1696674228d 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -18,6 +18,7 @@ const Inst = ir.Inst; const Body = ir.Body; const ast = std.zig.ast; const trace = @import("tracy.zig").trace; +const liveness = @import("liveness.zig"); /// General-purpose allocator. allocator: *Allocator, @@ -986,6 +987,11 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { .sema_failure, .dependency_failure => continue, .success => {}, } + // Here we tack on additional allocations to the Decl's arena. The allocations are + // lifetime annotations in the ZIR. + var decl_arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); + defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; + try liveness.analyze(self.allocator, &decl_arena.allocator, payload.func.analysis.success); } assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 8885ed282558632825f698ed8711d9b8a57a9912..eea3de7f36cd0c5d7d782f6924ae5fd4e83575a0 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -104,11 +104,18 @@ pub fn generateSymbol( .bin_file = bin_file, .mod_fn = module_fn, .code = code, - .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(bin_file.allocator), .err_msg = null, .args = mc_args.items, + .branch_stack = .{}, }; - defer function.inst_table.deinit(); + defer { + assert(function.branch_stack.items.len == 1); + function.branch_stack.items[0].inst_table.deinit(); + function.branch_stack.deinit(bin_file.allocator); + } + try function.branch_stack.append(bin_file.allocator, .{ + .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(bin_file.allocator), + }); function.gen() catch |err| switch (err) { error.CodegenFail => return Result{ .fail = function.err_msg.? }, @@ -215,13 +222,29 @@ const Function = struct { target: *const std.Target, mod_fn: *const Module.Fn, code: *std.ArrayList(u8), - inst_table: std.AutoHashMap(*ir.Inst, MCValue), err_msg: ?*ErrorMsg, args: []MCValue, + /// Whenever there is a runtime branch, we push a Branch onto this stack, + /// and pop it off when the runtime branch joins. This provides an "overlay" + /// of the table of mappings from instructions to `MCValue` from within the branch. + /// This way we can modify the `MCValue` for an instruction in different ways + /// within different branches. Special consideration is needed when a branch + /// joins with its parent, to make sure all instructions have the same MCValue + /// across each runtime branch upon joining. + branch_stack: std.ArrayListUnmanaged(Branch), + + const Branch = struct { + inst_table: std.AutoHashMap(*ir.Inst, MCValue), + }; + const MCValue = union(enum) { + /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc. none, + /// Control flow will not allow this value to be observed. unreach, + /// No more references to this value remain. + dead, /// A pointer-sized integer that fits in a register. immediate: u64, /// The constant was emitted into the code, at this offset. @@ -292,9 +315,10 @@ const Function = struct { } fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void { + const inst_table = &self.branch_stack.items[0].inst_table; for (self.mod_fn.analysis.success.instructions) |inst| { const new_inst = try self.genFuncInst(inst, arch); - try self.inst_table.putNoClobber(inst, new_inst); + try inst_table.putNoClobber(inst, new_inst); } } @@ -525,7 +549,9 @@ const Function = struct { fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) error{ CodegenFail, OutOfMemory }!void { switch (arch) { .x86_64 => switch (mcv) { - .none, .unreach => unreachable, + .dead => unreachable, + .none => unreachable, + .unreach => unreachable, .immediate => |x| { if (reg.size() != 64) { return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); @@ -708,12 +734,26 @@ const Function = struct { if (self.inst_table.get(inst)) |mcv| { return mcv; } + // Constants have static lifetimes, so they are always memoized in the outer most table. if (inst.cast(ir.Inst.Constant)) |const_inst| { - const mcvalue = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); - try self.inst_table.putNoClobber(inst, mcvalue); - return mcvalue; - } else { - return self.inst_table.get(inst).?; + const branch = &self.branch_stack.items[0]; + const gop = try branch.inst_table.getOrPut(inst); + if (!gop.found_existing) { + const mcv = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); + try branch.inst_table.putNoClobber(inst, mcv); + gop.kv.value = mcv; + return mcv; + } + return gop.kv.value; + } + + // Treat each stack item as a "layer" on top of the previous one. + var i: usize = self.branch_stack.items.len; + while (true) { + i -= 1; + if (self.branch_stack.items[i].inst_table.getValue(inst)) |mcv| { + return mcv; + } } } diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index cb9c7b5e11c2d9772ddba0242cfa638cfbdd0f5a..6fa06c81383e9fd5227546cc3f5393231aa03f3c 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -10,6 +10,16 @@ const Module = @import("Module.zig"); /// a memory location for the value to survive after a const instruction. pub const Inst = struct { tag: Tag, + /// Each bit represents the index of an `Inst` parameter in the `args` field. + /// If a bit is set, it marks the end of the lifetime of the corresponding + /// instruction parameter. For example, 0b00000101 means that the first and + /// third `Inst` parameters' lifetimes end after this instruction, and will + /// not have any more following references. + /// The most significant bit being set means that the instruction itself is + /// never referenced, in other words its lifetime ends as soon as it finishes. + /// If the byte is `0xff`, it means this is a special case and this data is + /// encoded elsewhere. + deaths: u8 = 0xff, ty: Type, /// Byte offset into the source. src: usize, @@ -165,6 +175,12 @@ pub const Inst = struct { true_body: Body, false_body: Body, }, + /// Set of instructions whose lifetimes end at the start of one of the branches. + /// The `true` branch is first: `deaths[0..true_death_count]`. + /// The `false` branch is next: `(deaths + true_death_count)[..false_death_count]`. + deaths: [*]*Inst = undefined, + true_death_count: u32 = 0, + false_death_count: u32 = 0, }; pub const Constant = struct { @@ -224,4 +240,4 @@ pub const Inst = struct { pub const Body = struct { instructions: []*Inst, -}; +}; \ No newline at end of file diff --git a/src-self-hosted/liveness.zig b/src-self-hosted/liveness.zig new file mode 100644 index 0000000000000000000000000000000000000000..65f81bbf4a8be0768891e4483a3ccf0f7167a5e9 --- /dev/null +++ b/src-self-hosted/liveness.zig @@ -0,0 +1,134 @@ +const std = @import("std"); +const ir = @import("ir.zig"); +const trace = @import("tracy.zig").trace; + +/// Perform Liveness Analysis over the `Body`. Each `Inst` will have its `deaths` field populated. +pub fn analyze( + /// Used for temporary storage during the analysis. + gpa: *std.mem.Allocator, + /// Used to tack on extra allocations in the same lifetime as the existing instructions. + arena: *std.mem.Allocator, + body: ir.Body, +) error{OutOfMemory}!void { + const tracy = trace(@src()); + defer tracy.end(); + + var table = std.AutoHashMap(*ir.Inst, void).init(gpa); + defer table.deinit(); + try table.ensureCapacity(body.instructions.len); + try analyzeWithTable(arena, &table, body); +} + +fn analyzeWithTable(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), body: ir.Body) error{OutOfMemory}!void { + var i: usize = body.instructions.len; + + while (i != 0) { + i -= 1; + const base = body.instructions[i]; + + // Obtain the corresponding instruction type based on the tag type. + inline for (std.meta.declarations(ir.Inst)) |decl| { + switch (decl.data) { + .Type => |T| { + if (@hasDecl(T, "base_tag")) { + if (T.base_tag == base.tag) { + return analyzeInst(arena, table, T, @fieldParentPtr(T, "base", base)); + } + } + }, + else => continue, + } + } + unreachable; + } +} + +fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), comptime T: type, inst: *T) error{OutOfMemory}!void { + inst.base.deaths = 0; + + switch (T) { + ir.Inst.Constant => return, + ir.Inst.Block => { + try analyzeWithTable(arena, table, inst.args.body); + // We let this continue so that it can possibly mark the block as + // unreferenced below. + }, + ir.Inst.CondBr => { + var true_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); + defer true_table.deinit(); + try true_table.ensureCapacity(inst.args.true_body.instructions.len); + try analyzeWithTable(arena, &true_table, inst.args.true_body); + + var false_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); + defer false_table.deinit(); + try false_table.ensureCapacity(inst.args.false_body.instructions.len); + try analyzeWithTable(arena, &false_table, inst.args.false_body); + + // Each death that occurs inside one branch, but not the other, needs + // to be added as a death immediately upon entering the other branch. + // During the iteration of the table, we additionally propagate the + // deaths to the parent table. + var true_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); + defer true_entry_deaths.deinit(); + var false_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); + defer false_entry_deaths.deinit(); + { + var it = false_table.iterator(); + while (it.next()) |entry| { + const false_death = entry.key; + if (!true_table.contains(false_death)) { + try true_entry_deaths.append(false_death); + // Here we are only adding to the parent table if the following iteration + // would miss it. + try table.putNoClobber(false_death, {}); + } + } + } + { + var it = true_table.iterator(); + while (it.next()) |entry| { + const true_death = entry.key; + try table.putNoClobber(true_death, {}); + if (!false_table.contains(true_death)) { + try false_entry_deaths.append(true_death); + } + } + } + inst.true_death_count = std.math.cast(@TypeOf(inst.true_death_count), true_entry_deaths.items.len) catch return error.OutOfMemory; + inst.false_death_count = std.math.cast(@TypeOf(inst.false_death_count), false_entry_deaths.items.len) catch return error.OutOfMemory; + const allocated_slice = try arena.alloc(*ir.Inst, true_entry_deaths.items.len + false_entry_deaths.items.len); + inst.deaths = allocated_slice.ptr; + + // Continue on with the instruction analysis. The following code will find the condition + // instruction, and the deaths flag for the CondBr instruction will indicate whether the + // condition's lifetime ends immediately before entering any branch. + }, + else => {}, + } + + if (!table.contains(&inst.base)) { + // No tombstone for this instruction means it is never referenced, + // and its birth marks its own death. Very metal 🤘 + inst.base.deaths |= 1 << 7; + } + + const Args = ir.Inst.Args(T); + if (Args == void) { + return; + } + + comptime var arg_index: usize = 0; + inline for (std.meta.fields(Args)) |field| { + if (field.field_type == *ir.Inst) { + if (arg_index >= 6) { + @compileError("out of bits to mark deaths of operands"); + } + const prev = try table.put(@field(inst.args, field.name), {}); + if (prev == null) { + // Death. + inst.base.deaths |= 1 << arg_index; + } + arg_index += 1; + } + } +} \ No newline at end of file -- 2.54.0 From a2fd8f72c155d9c3ffeb3f625598fa81ed9629dc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Jul 2020 06:09:47 +0000 Subject: [PATCH 189/295] std: add new array list functions --- lib/std/array_list.zig | 60 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index a68c1fa9d6873f914e1fac4902a3fcc95968bf06..d667bc4d1753ee0ac16c7e77994e5008af2dee78 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -257,6 +257,24 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { return &self.items[self.items.len - 1]; } + /// Resize the array, adding `n` new elements, which have `undefined` values. + /// The return value is an array pointing to the newly allocated elements. + pub fn addManyAsArray(self: *Self, comptime n: usize) !*[n]T { + const prev_len = self.items.len; + try self.resize(self.items.len + n); + return self.items[prev_len..][0..n]; + } + + /// Resize the array, adding `n` new elements, which have `undefined` values. + /// The return value is an array pointing to the newly allocated elements. + /// Asserts that there is already space for the new item without allocating more. + pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T { + assert(self.items.len + n <= self.capacity); + const prev_len = self.items.len; + self.items.len += n; + return self.items[prev_len..][0..n]; + } + /// Remove and return the last element from the list. /// Asserts the list has at least one item. pub fn pop(self: *Self) T { @@ -488,6 +506,24 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ return &self.items[self.items.len - 1]; } + /// Resize the array, adding `n` new elements, which have `undefined` values. + /// The return value is an array pointing to the newly allocated elements. + pub fn addManyAsArray(self: *Self, allocator: *Allocator, comptime n: usize) !*[n]T { + const prev_len = self.items.len; + try self.resize(allocator, self.items.len + n); + return self.items[prev_len..][0..n]; + } + + /// Resize the array, adding `n` new elements, which have `undefined` values. + /// The return value is an array pointing to the newly allocated elements. + /// Asserts that there is already space for the new item without allocating more. + pub fn addManyAsArrayAssumeCapacity(self: *Self, comptime n: usize) *[n]T { + assert(self.items.len + n <= self.capacity); + const prev_len = self.items.len; + self.items.len += n; + return self.items[prev_len..][0..n]; + } + /// Remove and return the last element from the list. /// Asserts the list has at least one item. /// This operation does not invalidate any element pointers. @@ -727,3 +763,27 @@ test "std.ArrayList.writer" { try writer.writeAll("efg"); testing.expectEqualSlices(u8, list.items, "abcdefg"); } + +test "addManyAsArray" { + const a = std.testing.allocator; + { + var list = ArrayList(u8).init(a); + defer list.deinit(); + + (try list.addManyAsArray(4)).* = "aoeu".*; + try list.ensureCapacity(8); + list.addManyAsArrayAssumeCapacity(4).* = "asdf".*; + + testing.expectEqualSlices(u8, list.items, "aoeuasdf"); + } + { + var list = ArrayListUnmanaged(u8){}; + defer list.deinit(a); + + (try list.addManyAsArray(a, 4)).* = "aoeu".*; + try list.ensureCapacity(a, 8); + list.addManyAsArrayAssumeCapacity(4).* = "asdf".*; + + testing.expectEqualSlices(u8, list.items, "aoeuasdf"); + } +} -- 2.54.0 From ad2ed457dd925c65d7d2bcac9208cee5619c523d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Jul 2020 06:10:03 +0000 Subject: [PATCH 190/295] std: expose unmanaged hash maps These are useful when you have many of them in memory, and already have the allocator stored elsewhere. --- lib/std/hash_map.zig | 4 ++++ lib/std/std.zig | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index aaec9a4d58758a945953fc4008c4ad6af1134dde..a9c30aba877bba8e2253d1f8af3e434571967339 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -15,6 +15,10 @@ pub fn AutoHashMap(comptime K: type, comptime V: type) type { return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K)); } +pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type { + return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), autoEqlIsCheap(K)); +} + /// Builtin hashmap for strings as keys. pub fn StringHashMap(comptime V: type) type { return HashMap([]const u8, V, hashString, eqlString, true); diff --git a/lib/std/std.zig b/lib/std/std.zig index 0961991ea650ffb945c87611b9a272025d12aa93..be4aad5f0d9d44fb41fd724c94a9fafb1cea91b5 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -3,14 +3,16 @@ pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned; pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged; pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled; pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged; -pub const AutoHashMap = @import("hash_map.zig").AutoHashMap; +pub const AutoHashMap = hash_map.AutoHashMap; +pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged; pub const BloomFilter = @import("bloom_filter.zig").BloomFilter; pub const BufMap = @import("buf_map.zig").BufMap; pub const BufSet = @import("buf_set.zig").BufSet; pub const ChildProcess = @import("child_process.zig").ChildProcess; pub const ComptimeStringMap = @import("comptime_string_map.zig").ComptimeStringMap; pub const DynLib = @import("dynamic_library.zig").DynLib; -pub const HashMap = @import("hash_map.zig").HashMap; +pub const HashMap = hash_map.HashMap; +pub const HashMapUnmanaged = hash_map.HashMapUnmanaged; pub const Mutex = @import("mutex.zig").Mutex; pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; @@ -22,7 +24,7 @@ pub const ResetEvent = @import("reset_event.zig").ResetEvent; pub const SegmentedList = @import("segmented_list.zig").SegmentedList; pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; pub const SpinLock = @import("spinlock.zig").SpinLock; -pub const StringHashMap = @import("hash_map.zig").StringHashMap; +pub const StringHashMap = hash_map.StringHashMap; pub const TailQueue = @import("linked_list.zig").TailQueue; pub const Target = @import("target.zig").Target; pub const Thread = @import("thread.zig").Thread; -- 2.54.0 From 8be8ebd698aac447db2babf95def4725d9ddd05f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Jul 2020 06:10:44 +0000 Subject: [PATCH 191/295] stage2: skeleton codegen for x64 ADD also rework Module to take advantage of the new hash map implementation. --- src-self-hosted/Module.zig | 389 +++++++++++++++++------------------ src-self-hosted/codegen.zig | 249 ++++++++++++++++------ src-self-hosted/ir.zig | 21 +- src-self-hosted/link.zig | 6 +- src-self-hosted/liveness.zig | 4 +- src-self-hosted/main.zig | 2 +- 6 files changed, 391 insertions(+), 280 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 4c83076caddbe83acb0f620c2516b1696674228d..4240a1c6c880a4637a1750bebb4025e016c9346c 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -20,8 +20,8 @@ const ast = std.zig.ast; const trace = @import("tracy.zig").trace; const liveness = @import("liveness.zig"); -/// General-purpose allocator. -allocator: *Allocator, +/// General-purpose allocator. Used for both temporary and long-term storage. +gpa: *Allocator, /// Pointer to externally managed resource. root_pkg: *Package, /// Module owns this resource. @@ -33,7 +33,7 @@ bin_file_path: []const u8, /// It's rare for a decl to be exported, so we save memory by having a sparse map of /// Decl pointers to details about them being exported. /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table. -decl_exports: std.AutoHashMap(*Decl, []*Export), +decl_exports: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, /// We track which export is associated with the given symbol name for quick /// detection of symbol collisions. symbol_exports: std.StringHashMap(*Export), @@ -41,9 +41,9 @@ symbol_exports: std.StringHashMap(*Export), /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that /// is performing the export of another Decl. /// This table owns the Export memory. -export_owners: std.AutoHashMap(*Decl, []*Export), +export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, /// Maps fully qualified namespaced names to the Decl struct for them. -decl_table: DeclTable, +decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{}, optimize_mode: std.builtin.Mode, link_error_flags: link.ElfFile.ErrorFlags = .{}, @@ -55,13 +55,13 @@ work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), /// The ErrorMsg memory is owned by the decl, using Module's allocator. /// Note that a Decl can succeed but the Fn it represents can fail. In this case, /// a Decl can have a failed_decls entry but have analysis status of success. -failed_decls: std.AutoHashMap(*Decl, *ErrorMsg), +failed_decls: std.AutoHashMapUnmanaged(*Decl, *ErrorMsg) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Scope`, using Module's allocator. -failed_files: std.AutoHashMap(*Scope, *ErrorMsg), +failed_files: std.AutoHashMapUnmanaged(*Scope, *ErrorMsg) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Export`, using Module's allocator. -failed_exports: std.AutoHashMap(*Export, *ErrorMsg), +failed_exports: std.AutoHashMapUnmanaged(*Export, *ErrorMsg) = .{}, /// Incrementing integer used to compare against the corresponding Decl /// field to determine whether a Decl's status applies to an ongoing update, or a @@ -76,8 +76,6 @@ deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, keep_source_files_loaded: bool, -const DeclTable = std.HashMap(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false); - const WorkItem = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Decl, @@ -176,19 +174,23 @@ pub const Decl = struct { /// The shallow set of other decls whose typed_value could possibly change if this Decl's /// typed_value is modified. - dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, + dependants: DepsTable = .{}, /// The shallow set of other decls whose typed_value changing indicates that this Decl's /// typed_value may need to be regenerated. - dependencies: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){}, + dependencies: DepsTable = .{}, - pub fn destroy(self: *Decl, allocator: *Allocator) void { - allocator.free(mem.spanZ(self.name)); + /// The reason this is not `std.AutoHashMapUnmanaged` is a workaround for + /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself` + pub const DepsTable = std.HashMapUnmanaged(*Decl, void, std.hash_map.getAutoHashFn(*Decl), std.hash_map.getAutoEqlFn(*Decl), false); + + pub fn destroy(self: *Decl, gpa: *Allocator) void { + gpa.free(mem.spanZ(self.name)); if (self.typedValueManaged()) |tvm| { - tvm.deinit(allocator); + tvm.deinit(gpa); } - self.dependants.deinit(allocator); - self.dependencies.deinit(allocator); - allocator.destroy(self); + self.dependants.deinit(gpa); + self.dependencies.deinit(gpa); + gpa.destroy(self); } pub fn src(self: Decl) usize { @@ -247,23 +249,11 @@ pub const Decl = struct { } fn removeDependant(self: *Decl, other: *Decl) void { - for (self.dependants.items) |item, i| { - if (item == other) { - _ = self.dependants.swapRemove(i); - return; - } - } - unreachable; + self.dependants.removeAssertDiscard(other); } fn removeDependency(self: *Decl, other: *Decl) void { - for (self.dependencies.items) |item, i| { - if (item == other) { - _ = self.dependencies.swapRemove(i); - return; - } - } - unreachable; + self.dependencies.removeAssertDiscard(other); } }; @@ -390,10 +380,10 @@ pub const Scope = struct { } } - pub fn unload(base: *Scope, allocator: *Allocator) void { + pub fn unload(base: *Scope, gpa: *Allocator) void { switch (base.tag) { - .file => return @fieldParentPtr(File, "base", base).unload(allocator), - .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(allocator), + .file => return @fieldParentPtr(File, "base", base).unload(gpa), + .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(gpa), .block => unreachable, .gen_zir => unreachable, .decl => unreachable, @@ -422,17 +412,17 @@ pub const Scope = struct { } /// Asserts the scope is a File or ZIRModule and deinitializes it, then deallocates it. - pub fn destroy(base: *Scope, allocator: *Allocator) void { + pub fn destroy(base: *Scope, gpa: *Allocator) void { switch (base.tag) { .file => { const scope_file = @fieldParentPtr(File, "base", base); - scope_file.deinit(allocator); - allocator.destroy(scope_file); + scope_file.deinit(gpa); + gpa.destroy(scope_file); }, .zir_module => { const scope_zir_module = @fieldParentPtr(ZIRModule, "base", base); - scope_zir_module.deinit(allocator); - allocator.destroy(scope_zir_module); + scope_zir_module.deinit(gpa); + gpa.destroy(scope_zir_module); }, .block => unreachable, .gen_zir => unreachable, @@ -483,7 +473,7 @@ pub const Scope = struct { /// Direct children of the file. decls: ArrayListUnmanaged(*Decl), - pub fn unload(self: *File, allocator: *Allocator) void { + pub fn unload(self: *File, gpa: *Allocator) void { switch (self.status) { .never_loaded, .unloaded_parse_failure, @@ -497,16 +487,16 @@ pub const Scope = struct { } switch (self.source) { .bytes => |bytes| { - allocator.free(bytes); + gpa.free(bytes); self.source = .{ .unloaded = {} }; }, .unloaded => {}, } } - pub fn deinit(self: *File, allocator: *Allocator) void { - self.decls.deinit(allocator); - self.unload(allocator); + pub fn deinit(self: *File, gpa: *Allocator) void { + self.decls.deinit(gpa); + self.unload(gpa); self.* = undefined; } @@ -528,7 +518,7 @@ pub const Scope = struct { switch (self.source) { .unloaded => { const source = try module.root_pkg.root_src_dir.readFileAllocOptions( - module.allocator, + module.gpa, self.sub_file_path, std.math.maxInt(u32), 1, @@ -576,7 +566,7 @@ pub const Scope = struct { /// not this one. decls: ArrayListUnmanaged(*Decl), - pub fn unload(self: *ZIRModule, allocator: *Allocator) void { + pub fn unload(self: *ZIRModule, gpa: *Allocator) void { switch (self.status) { .never_loaded, .unloaded_parse_failure, @@ -585,30 +575,30 @@ pub const Scope = struct { => {}, .loaded_success => { - self.contents.module.deinit(allocator); - allocator.destroy(self.contents.module); + self.contents.module.deinit(gpa); + gpa.destroy(self.contents.module); self.contents = .{ .not_available = {} }; self.status = .unloaded_success; }, .loaded_sema_failure => { - self.contents.module.deinit(allocator); - allocator.destroy(self.contents.module); + self.contents.module.deinit(gpa); + gpa.destroy(self.contents.module); self.contents = .{ .not_available = {} }; self.status = .unloaded_sema_failure; }, } switch (self.source) { .bytes => |bytes| { - allocator.free(bytes); + gpa.free(bytes); self.source = .{ .unloaded = {} }; }, .unloaded => {}, } } - pub fn deinit(self: *ZIRModule, allocator: *Allocator) void { - self.decls.deinit(allocator); - self.unload(allocator); + pub fn deinit(self: *ZIRModule, gpa: *Allocator) void { + self.decls.deinit(gpa); + self.unload(gpa); self.* = undefined; } @@ -630,7 +620,7 @@ pub const Scope = struct { switch (self.source) { .unloaded => { const source = try module.root_pkg.root_src_dir.readFileAllocOptions( - module.allocator, + module.gpa, self.sub_file_path, std.math.maxInt(u32), 1, @@ -701,8 +691,8 @@ pub const AllErrors = struct { msg: []const u8, }; - pub fn deinit(self: *AllErrors, allocator: *Allocator) void { - self.arena.promote(allocator).deinit(); + pub fn deinit(self: *AllErrors, gpa: *Allocator) void { + self.arena.promote(gpa).deinit(); } fn add( @@ -772,20 +762,14 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { }; return Module{ - .allocator = gpa, + .gpa = gpa, .root_pkg = options.root_pkg, .root_scope = root_scope, .bin_file_dir = bin_file_dir, .bin_file_path = options.bin_file_path, .bin_file = bin_file, .optimize_mode = options.optimize_mode, - .decl_table = DeclTable.init(gpa), - .decl_exports = std.AutoHashMap(*Decl, []*Export).init(gpa), .symbol_exports = std.StringHashMap(*Export).init(gpa), - .export_owners = std.AutoHashMap(*Decl, []*Export).init(gpa), - .failed_decls = std.AutoHashMap(*Decl, *ErrorMsg).init(gpa), - .failed_files = std.AutoHashMap(*Scope, *ErrorMsg).init(gpa), - .failed_exports = std.AutoHashMap(*Export, *ErrorMsg).init(gpa), .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), .keep_source_files_loaded = options.keep_source_files_loaded, }; @@ -793,51 +777,51 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { pub fn deinit(self: *Module) void { self.bin_file.deinit(); - const allocator = self.allocator; - self.deletion_set.deinit(allocator); + const gpa = self.gpa; + self.deletion_set.deinit(gpa); self.work_queue.deinit(); for (self.decl_table.items()) |entry| { - entry.value.destroy(allocator); + entry.value.destroy(gpa); } - self.decl_table.deinit(); + self.decl_table.deinit(gpa); for (self.failed_decls.items()) |entry| { - entry.value.destroy(allocator); + entry.value.destroy(gpa); } - self.failed_decls.deinit(); + self.failed_decls.deinit(gpa); for (self.failed_files.items()) |entry| { - entry.value.destroy(allocator); + entry.value.destroy(gpa); } - self.failed_files.deinit(); + self.failed_files.deinit(gpa); for (self.failed_exports.items()) |entry| { - entry.value.destroy(allocator); + entry.value.destroy(gpa); } - self.failed_exports.deinit(); + self.failed_exports.deinit(gpa); for (self.decl_exports.items()) |entry| { const export_list = entry.value; - allocator.free(export_list); + gpa.free(export_list); } - self.decl_exports.deinit(); + self.decl_exports.deinit(gpa); for (self.export_owners.items()) |entry| { - freeExportList(allocator, entry.value); + freeExportList(gpa, entry.value); } - self.export_owners.deinit(); + self.export_owners.deinit(gpa); self.symbol_exports.deinit(); - self.root_scope.destroy(allocator); + self.root_scope.destroy(gpa); self.* = undefined; } -fn freeExportList(allocator: *Allocator, export_list: []*Export) void { +fn freeExportList(gpa: *Allocator, export_list: []*Export) void { for (export_list) |exp| { - allocator.destroy(exp); + gpa.destroy(exp); } - allocator.free(export_list); + gpa.free(export_list); } pub fn target(self: Module) std.Target { @@ -855,7 +839,7 @@ pub fn update(self: *Module) !void { // Until then we simulate a full cache miss. Source files could have been loaded for any reason; // to force a refresh we unload now. if (self.root_scope.cast(Scope.File)) |zig_file| { - zig_file.unload(self.allocator); + zig_file.unload(self.gpa); self.analyzeRootSrcFile(zig_file) catch |err| switch (err) { error.AnalysisFail => { assert(self.totalErrorCount() != 0); @@ -863,7 +847,7 @@ pub fn update(self: *Module) !void { else => |e| return e, }; } else if (self.root_scope.cast(Scope.ZIRModule)) |zir_module| { - zir_module.unload(self.allocator); + zir_module.unload(self.gpa); self.analyzeRootZIRModule(zir_module) catch |err| switch (err) { error.AnalysisFail => { assert(self.totalErrorCount() != 0); @@ -876,7 +860,7 @@ pub fn update(self: *Module) !void { // Process the deletion set. while (self.deletion_set.popOrNull()) |decl| { - if (decl.dependants.items.len != 0) { + if (decl.dependants.items().len != 0) { decl.deletion_flag = false; continue; } @@ -889,7 +873,7 @@ pub fn update(self: *Module) !void { // to report error messages. Otherwise we unload all source files to save memory. if (self.totalErrorCount() == 0) { if (!self.keep_source_files_loaded) { - self.root_scope.unload(self.allocator); + self.root_scope.unload(self.gpa); } try self.bin_file.flush(); } @@ -915,10 +899,10 @@ pub fn totalErrorCount(self: *Module) usize { } pub fn getAllErrorsAlloc(self: *Module) !AllErrors { - var arena = std.heap.ArenaAllocator.init(self.allocator); + var arena = std.heap.ArenaAllocator.init(self.gpa); errdefer arena.deinit(); - var errors = std.ArrayList(AllErrors.Message).init(self.allocator); + var errors = std.ArrayList(AllErrors.Message).init(self.gpa); defer errors.deinit(); for (self.failed_files.items()) |entry| { @@ -989,9 +973,9 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { } // Here we tack on additional allocations to the Decl's arena. The allocations are // lifetime annotations in the ZIR. - var decl_arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); + var decl_arena = decl.typed_value.most_recent.arena.?.promote(self.gpa); defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; - try liveness.analyze(self.allocator, &decl_arena.allocator, payload.func.analysis.success); + try liveness.analyze(self.gpa, &decl_arena.allocator, payload.func.analysis.success); } assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); @@ -1002,9 +986,9 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { decl.analysis = .dependency_failure; }, else => { - try self.failed_decls.ensureCapacity(self.failed_decls.items().len + 1); + try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - self.allocator, + self.gpa, decl.src(), "unable to codegen: {}", .{@errorName(err)}, @@ -1048,16 +1032,17 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { // prior to re-analysis. self.deleteDeclExports(decl); // Dependencies will be re-discovered, so we remove them here prior to re-analysis. - for (decl.dependencies.items) |dep| { + for (decl.dependencies.items()) |entry| { + const dep = entry.key; dep.removeDependant(decl); - if (dep.dependants.items.len == 0 and !dep.deletion_flag) { + if (dep.dependants.items().len == 0 and !dep.deletion_flag) { // We don't perform a deletion here, because this Decl or another one // may end up referencing it before the update is complete. dep.deletion_flag = true; - try self.deletion_set.append(self.allocator, dep); + try self.deletion_set.append(self.gpa, dep); } } - decl.dependencies.shrink(self.allocator, 0); + decl.dependencies.clearRetainingCapacity(); break :blk true; }, @@ -1072,9 +1057,9 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => return error.AnalysisFail, else => { - try self.failed_decls.ensureCapacity(self.failed_decls.items().len + 1); + try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - self.allocator, + self.gpa, decl.src(), "unable to analyze: {}", .{@errorName(err)}, @@ -1088,7 +1073,8 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { // We may need to chase the dependants and re-analyze them. // However, if the decl is a function, and the type is the same, we do not need to. if (type_changed or decl.typed_value.most_recent.typed_value.val.tag() != .function) { - for (decl.dependants.items) |dep| { + for (decl.dependants.items()) |entry| { + const dep = entry.key; switch (dep.analysis) { .unreferenced => unreachable, .in_progress => unreachable, @@ -1127,8 +1113,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { // to complete the Decl analysis. var fn_type_scope: Scope.GenZIR = .{ .decl = decl, - .arena = std.heap.ArenaAllocator.init(self.allocator), - .instructions = std.ArrayList(*zir.Inst).init(self.allocator), + .arena = std.heap.ArenaAllocator.init(self.gpa), + .instructions = std.ArrayList(*zir.Inst).init(self.gpa), }; defer fn_type_scope.arena.deinit(); defer fn_type_scope.instructions.deinit(); @@ -1178,7 +1164,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { _ = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.Return, .{ .operand = fn_type_inst }, .{}); // We need the memory for the Type to go into the arena for the Decl - var decl_arena = std.heap.ArenaAllocator.init(self.allocator); + var decl_arena = std.heap.ArenaAllocator.init(self.gpa); errdefer decl_arena.deinit(); const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); @@ -1189,7 +1175,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .instructions = .{}, .arena = &decl_arena.allocator, }; - defer block_scope.instructions.deinit(self.allocator); + defer block_scope.instructions.deinit(self.gpa); const fn_type = try self.analyzeBodyValueAsType(&block_scope, .{ .instructions = fn_type_scope.instructions.items, @@ -1202,8 +1188,8 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { // pass completes, and semantic analysis of it completes. var gen_scope: Scope.GenZIR = .{ .decl = decl, - .arena = std.heap.ArenaAllocator.init(self.allocator), - .instructions = std.ArrayList(*zir.Inst).init(self.allocator), + .arena = std.heap.ArenaAllocator.init(self.gpa), + .instructions = std.ArrayList(*zir.Inst).init(self.gpa), }; errdefer gen_scope.arena.deinit(); defer gen_scope.instructions.deinit(); @@ -1235,7 +1221,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); type_changed = !tvm.typed_value.ty.eql(fn_type); - tvm.deinit(self.allocator); + tvm.deinit(self.gpa); } decl_arena_state.* = decl_arena.state; @@ -1626,40 +1612,31 @@ fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { } fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void { - try depender.dependencies.ensureCapacity(self.allocator, depender.dependencies.items.len + 1); - try dependee.dependants.ensureCapacity(self.allocator, dependee.dependants.items.len + 1); + try depender.dependencies.ensureCapacity(self.gpa, depender.dependencies.items().len + 1); + try dependee.dependants.ensureCapacity(self.gpa, dependee.dependants.items().len + 1); - for (depender.dependencies.items) |item| { - if (item == dependee) break; // Already in the set. - } else { - depender.dependencies.appendAssumeCapacity(dependee); - } - - for (dependee.dependants.items) |item| { - if (item == depender) break; // Already in the set. - } else { - dependee.dependants.appendAssumeCapacity(depender); - } + depender.dependencies.putAssumeCapacity(dependee, {}); + dependee.dependants.putAssumeCapacity(depender, {}); } fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module { switch (root_scope.status) { .never_loaded, .unloaded_success => { - try self.failed_files.ensureCapacity(self.failed_files.items().len + 1); + try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); const source = try root_scope.getSource(self); var keep_zir_module = false; - const zir_module = try self.allocator.create(zir.Module); - defer if (!keep_zir_module) self.allocator.destroy(zir_module); + const zir_module = try self.gpa.create(zir.Module); + defer if (!keep_zir_module) self.gpa.destroy(zir_module); - zir_module.* = try zir.parse(self.allocator, source); - defer if (!keep_zir_module) zir_module.deinit(self.allocator); + zir_module.* = try zir.parse(self.gpa, source); + defer if (!keep_zir_module) zir_module.deinit(self.gpa); if (zir_module.error_msg) |src_err_msg| { self.failed_files.putAssumeCapacityNoClobber( &root_scope.base, - try ErrorMsg.create(self.allocator, src_err_msg.byte_offset, "{}", .{src_err_msg.msg}), + try ErrorMsg.create(self.gpa, src_err_msg.byte_offset, "{}", .{src_err_msg.msg}), ); root_scope.status = .unloaded_parse_failure; return error.AnalysisFail; @@ -1686,22 +1663,22 @@ fn getAstTree(self: *Module, root_scope: *Scope.File) !*ast.Tree { switch (root_scope.status) { .never_loaded, .unloaded_success => { - try self.failed_files.ensureCapacity(self.failed_files.items().len + 1); + try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); const source = try root_scope.getSource(self); var keep_tree = false; - const tree = try std.zig.parse(self.allocator, source); + const tree = try std.zig.parse(self.gpa, source); defer if (!keep_tree) tree.deinit(); if (tree.errors.len != 0) { const parse_err = tree.errors[0]; - var msg = std.ArrayList(u8).init(self.allocator); + var msg = std.ArrayList(u8).init(self.gpa); defer msg.deinit(); try parse_err.render(tree.token_ids, msg.outStream()); - const err_msg = try self.allocator.create(ErrorMsg); + const err_msg = try self.gpa.create(ErrorMsg); err_msg.* = .{ .msg = msg.toOwnedSlice(), .byte_offset = tree.token_locs[parse_err.loc()].start, @@ -1732,11 +1709,11 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { const decls = tree.root_node.decls(); try self.work_queue.ensureUnusedCapacity(decls.len); - try root_scope.decls.ensureCapacity(self.allocator, decls.len); + try root_scope.decls.ensureCapacity(self.gpa, decls.len); // Keep track of the decls that we expect to see in this file so that // we know which ones have been deleted. - var deleted_decls = std.AutoHashMap(*Decl, void).init(self.allocator); + var deleted_decls = std.AutoHashMap(*Decl, void).init(self.gpa); defer deleted_decls.deinit(); try deleted_decls.ensureCapacity(root_scope.decls.items.len); for (root_scope.decls.items) |file_decl| { @@ -1760,9 +1737,9 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { decl.src_index = decl_i; if (deleted_decls.remove(decl) == null) { decl.analysis = .sema_failure; - const err_msg = try ErrorMsg.create(self.allocator, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); - errdefer err_msg.destroy(self.allocator); - try self.failed_decls.putNoClobber(decl, err_msg); + const err_msg = try ErrorMsg.create(self.gpa, tree.token_locs[name_tok].start, "redefinition of '{}'", .{decl.name}); + errdefer err_msg.destroy(self.gpa); + try self.failed_decls.putNoClobber(self.gpa, decl, err_msg); } else { if (!srcHashEql(decl.contents_hash, contents_hash)) { try self.markOutdatedDecl(decl); @@ -1796,14 +1773,14 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { const src_module = try self.getSrcModule(root_scope); try self.work_queue.ensureUnusedCapacity(src_module.decls.len); - try root_scope.decls.ensureCapacity(self.allocator, src_module.decls.len); + try root_scope.decls.ensureCapacity(self.gpa, src_module.decls.len); - var exports_to_resolve = std.ArrayList(*zir.Decl).init(self.allocator); + var exports_to_resolve = std.ArrayList(*zir.Decl).init(self.gpa); defer exports_to_resolve.deinit(); // Keep track of the decls that we expect to see in this file so that // we know which ones have been deleted. - var deleted_decls = std.AutoHashMap(*Decl, void).init(self.allocator); + var deleted_decls = std.AutoHashMap(*Decl, void).init(self.gpa); defer deleted_decls.deinit(); try deleted_decls.ensureCapacity(self.decl_table.items().len); for (self.decl_table.items()) |entry| { @@ -1845,7 +1822,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { } fn deleteDecl(self: *Module, decl: *Decl) !void { - try self.deletion_set.ensureCapacity(self.allocator, self.deletion_set.items.len + decl.dependencies.items.len); + try self.deletion_set.ensureCapacity(self.gpa, self.deletion_set.items.len + decl.dependencies.items().len); // Remove from the namespace it resides in. In the case of an anonymous Decl it will // not be present in the set, and this does nothing. @@ -1855,9 +1832,10 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { const name_hash = decl.fullyQualifiedNameHash(); self.decl_table.removeAssertDiscard(name_hash); // Remove itself from its dependencies, because we are about to destroy the decl pointer. - for (decl.dependencies.items) |dep| { + for (decl.dependencies.items()) |entry| { + const dep = entry.key; dep.removeDependant(decl); - if (dep.dependants.items.len == 0 and !dep.deletion_flag) { + if (dep.dependants.items().len == 0 and !dep.deletion_flag) { // We don't recursively perform a deletion here, because during the update, // another reference to it may turn up. dep.deletion_flag = true; @@ -1865,7 +1843,8 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { } } // Anything that depends on this deleted decl certainly needs to be re-analyzed. - for (decl.dependants.items) |dep| { + for (decl.dependants.items()) |entry| { + const dep = entry.key; dep.removeDependency(decl); if (dep.analysis != .outdated) { // TODO Move this failure possibility to the top of the function. @@ -1873,11 +1852,11 @@ fn deleteDecl(self: *Module, decl: *Decl) !void { } } if (self.failed_decls.remove(decl)) |entry| { - entry.value.destroy(self.allocator); + entry.value.destroy(self.gpa); } self.deleteDeclExports(decl); self.bin_file.freeDecl(decl); - decl.destroy(self.allocator); + decl.destroy(self.gpa); } /// Delete all the Export objects that are caused by this Decl. Re-analysis of @@ -1899,7 +1878,7 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { i += 1; } } - decl_exports_kv.value = self.allocator.shrink(list, new_len); + decl_exports_kv.value = self.gpa.shrink(list, new_len); if (new_len == 0) { self.decl_exports.removeAssertDiscard(exp.exported_decl); } @@ -1907,12 +1886,12 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { self.bin_file.deleteExport(exp.link); if (self.failed_exports.remove(exp)) |entry| { - entry.value.destroy(self.allocator); + entry.value.destroy(self.gpa); } _ = self.symbol_exports.remove(exp.options.name); - self.allocator.destroy(exp); + self.gpa.destroy(exp); } - self.allocator.free(kv.value); + self.gpa.free(kv.value); } fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { @@ -1920,7 +1899,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { defer tracy.end(); // Use the Decl's arena for function memory. - var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); + var arena = decl.typed_value.most_recent.arena.?.promote(self.gpa); defer decl.typed_value.most_recent.arena.?.* = arena.state; var inner_block: Scope.Block = .{ .parent = null, @@ -1929,10 +1908,10 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { .instructions = .{}, .arena = &arena.allocator, }; - defer inner_block.instructions.deinit(self.allocator); + defer inner_block.instructions.deinit(self.gpa); const fn_zir = func.analysis.queued; - defer fn_zir.arena.promote(self.allocator).deinit(); + defer fn_zir.arena.promote(self.gpa).deinit(); func.analysis = .{ .in_progress = {} }; //std.debug.warn("set {} to in_progress\n", .{decl.name}); @@ -1947,7 +1926,7 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { //std.debug.warn("mark {} outdated\n", .{decl.name}); try self.work_queue.writeItem(.{ .analyze_decl = decl }); if (self.failed_decls.remove(decl)) |entry| { - entry.value.destroy(self.allocator); + entry.value.destroy(self.gpa); } decl.analysis = .outdated; } @@ -1958,7 +1937,7 @@ fn allocateNewDecl( src_index: usize, contents_hash: std.zig.SrcHash, ) !*Decl { - const new_decl = try self.allocator.create(Decl); + const new_decl = try self.gpa.create(Decl); new_decl.* = .{ .name = "", .scope = scope.namespace(), @@ -1981,10 +1960,10 @@ fn createNewDecl( name_hash: Scope.NameHash, contents_hash: std.zig.SrcHash, ) !*Decl { - try self.decl_table.ensureCapacity(self.decl_table.items().len + 1); + try self.decl_table.ensureCapacity(self.gpa, self.decl_table.items().len + 1); const new_decl = try self.allocateNewDecl(scope, src_index, contents_hash); - errdefer self.allocator.destroy(new_decl); - new_decl.name = try mem.dupeZ(self.allocator, u8, decl_name); + errdefer self.gpa.destroy(new_decl); + new_decl.name = try mem.dupeZ(self.gpa, u8, decl_name); self.decl_table.putAssumeCapacityNoClobber(name_hash, new_decl); return new_decl; } @@ -1992,7 +1971,7 @@ fn createNewDecl( fn analyzeZirDecl(self: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { var decl_scope: Scope.DeclAnalysis = .{ .decl = decl, - .arena = std.heap.ArenaAllocator.init(self.allocator), + .arena = std.heap.ArenaAllocator.init(self.gpa), }; errdefer decl_scope.arena.deinit(); @@ -2008,7 +1987,7 @@ fn analyzeZirDecl(self: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bo prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); type_changed = !tvm.typed_value.ty.eql(typed_value.ty); - tvm.deinit(self.allocator); + tvm.deinit(self.gpa); } arena_state.* = decl_scope.arena.state; @@ -2146,11 +2125,11 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const else => return self.fail(scope, src, "unable to export type '{}'", .{typed_value.ty}), } - try self.decl_exports.ensureCapacity(self.decl_exports.items().len + 1); - try self.export_owners.ensureCapacity(self.export_owners.items().len + 1); + try self.decl_exports.ensureCapacity(self.gpa, self.decl_exports.items().len + 1); + try self.export_owners.ensureCapacity(self.gpa, self.export_owners.items().len + 1); - const new_export = try self.allocator.create(Export); - errdefer self.allocator.destroy(new_export); + const new_export = try self.gpa.create(Export); + errdefer self.gpa.destroy(new_export); const owner_decl = scope.decl().?; @@ -2164,27 +2143,27 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const }; // Add to export_owners table. - const eo_gop = self.export_owners.getOrPut(owner_decl) catch unreachable; + const eo_gop = self.export_owners.getOrPut(self.gpa, owner_decl) catch unreachable; if (!eo_gop.found_existing) { eo_gop.entry.value = &[0]*Export{}; } - eo_gop.entry.value = try self.allocator.realloc(eo_gop.entry.value, eo_gop.entry.value.len + 1); + eo_gop.entry.value = try self.gpa.realloc(eo_gop.entry.value, eo_gop.entry.value.len + 1); eo_gop.entry.value[eo_gop.entry.value.len - 1] = new_export; - errdefer eo_gop.entry.value = self.allocator.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); + errdefer eo_gop.entry.value = self.gpa.shrink(eo_gop.entry.value, eo_gop.entry.value.len - 1); // Add to exported_decl table. - const de_gop = self.decl_exports.getOrPut(exported_decl) catch unreachable; + const de_gop = self.decl_exports.getOrPut(self.gpa, exported_decl) catch unreachable; if (!de_gop.found_existing) { de_gop.entry.value = &[0]*Export{}; } - de_gop.entry.value = try self.allocator.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); + de_gop.entry.value = try self.gpa.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); de_gop.entry.value[de_gop.entry.value.len - 1] = new_export; - errdefer de_gop.entry.value = self.allocator.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); + errdefer de_gop.entry.value = self.gpa.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); if (self.symbol_exports.get(symbol_name)) |_| { - try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1); + try self.failed_exports.ensureCapacity(self.gpa, self.failed_exports.items().len + 1); self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( - self.allocator, + self.gpa, src, "exported symbol collision: {}", .{symbol_name}, @@ -2198,9 +2177,9 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const self.bin_file.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { - try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1); + try self.failed_exports.ensureCapacity(self.gpa, self.failed_exports.items().len + 1); self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( - self.allocator, + self.gpa, src, "unable to export: {}", .{@errorName(err)}, @@ -2224,13 +2203,13 @@ fn addNewInstArgs( } fn newZIRInst( - allocator: *Allocator, + gpa: *Allocator, src: usize, comptime T: type, positionals: std.meta.fieldInfo(T, "positionals").field_type, kw_args: std.meta.fieldInfo(T, "kw_args").field_type, ) !*zir.Inst { - const inst = try allocator.create(T); + const inst = try gpa.create(T); inst.* = .{ .base = .{ .tag = T.base_tag, @@ -2273,7 +2252,7 @@ fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime }, .args = undefined, }; - try block.instructions.append(self.allocator, &inst.base); + try block.instructions.append(self.gpa, &inst.base); return inst; } @@ -2433,7 +2412,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In fn analyzeInstStr(self: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst { // The bytes references memory inside the ZIR module, which can get deallocated // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. - var new_decl_arena = std.heap.ArenaAllocator.init(self.allocator); + var new_decl_arena = std.heap.ArenaAllocator.init(self.gpa); const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); @@ -2457,8 +2436,8 @@ fn createAnonymousDecl( ) !*Decl { const name_index = self.getNextAnonNameIndex(); const scope_decl = scope.decl().?; - const name = try std.fmt.allocPrint(self.allocator, "{}${}", .{ scope_decl.name, name_index }); - defer self.allocator.free(name); + const name = try std.fmt.allocPrint(self.gpa, "{}${}", .{ scope_decl.name, name_index }); + defer self.gpa.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); const src_hash: std.zig.SrcHash = undefined; const new_decl = try self.createNewDecl(scope, name, scope_decl.src_index, name_hash, src_hash); @@ -2554,8 +2533,8 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr }; const label = &child_block.label.?; - defer child_block.instructions.deinit(self.allocator); - defer label.results.deinit(self.allocator); + defer child_block.instructions.deinit(self.gpa); + defer label.results.deinit(self.gpa); try self.analyzeBody(&child_block.base, inst.positionals.body); @@ -2567,7 +2546,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr // No need to add the Block instruction; we can add the instructions to the parent block directly. // Blocks are terminated with a noreturn instruction which we do not want to include. const instrs = child_block.instructions.items; - try parent_block.instructions.appendSlice(self.allocator, instrs[0 .. instrs.len - 1]); + try parent_block.instructions.appendSlice(self.gpa, instrs[0 .. instrs.len - 1]); if (label.results.items.len == 1) { return label.results.items[0]; } else { @@ -2577,7 +2556,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr // Need to set the type and emit the Block instruction. This allows machine code generation // to emit a jump instruction to after the block when it encounters the break. - try parent_block.instructions.append(self.allocator, &block_inst.base); + try parent_block.instructions.append(self.gpa, &block_inst.base); block_inst.base.ty = try self.resolvePeerTypes(scope, label.results.items); block_inst.args.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; return &block_inst.base; @@ -2596,7 +2575,7 @@ fn analyzeInstBreakVoid(self: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) while (opt_block) |block| { if (block.label) |*label| { if (mem.eql(u8, label.name, label_name)) { - try label.results.append(self.allocator, void_inst); + try label.results.append(self.gpa, void_inst); return self.constNoReturn(scope, inst.base.src); } } @@ -2719,8 +2698,8 @@ fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerErro // TODO handle function calls of generic functions - const fn_param_types = try self.allocator.alloc(Type, fn_params_len); - defer self.allocator.free(fn_param_types); + const fn_param_types = try self.gpa.alloc(Type, fn_params_len); + defer self.gpa.free(fn_param_types); func.ty.fnParamTypes(fn_param_types); const casted_args = try scope.arena().alloc(*Inst, fn_params_len); @@ -2739,7 +2718,7 @@ fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerErro fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type); const fn_zir = blk: { - var fn_arena = std.heap.ArenaAllocator.init(self.allocator); + var fn_arena = std.heap.ArenaAllocator.init(self.gpa); errdefer fn_arena.deinit(); const fn_zir = try scope.arena().create(Fn.ZIR); @@ -3120,7 +3099,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner .instructions = .{}, .arena = parent_block.arena, }; - defer true_block.instructions.deinit(self.allocator); + defer true_block.instructions.deinit(self.gpa); try self.analyzeBody(&true_block.base, inst.positionals.true_body); var false_block: Scope.Block = .{ @@ -3130,7 +3109,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner .instructions = .{}, .arena = parent_block.arena, }; - defer false_block.instructions.deinit(self.allocator); + defer false_block.instructions.deinit(self.gpa); try self.analyzeBody(&false_block.base, inst.positionals.false_body); return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){ @@ -3284,7 +3263,7 @@ fn cmpNumeric( return self.constUndef(scope, src, Type.initTag(.bool)); const is_unsigned = if (lhs_is_float) x: { var bigint_space: Value.BigIntSpace = undefined; - var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.allocator); + var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.gpa); defer bigint.deinit(); const zcmp = lhs_val.orderAgainstZero(); if (lhs_val.floatHasFraction()) { @@ -3319,7 +3298,7 @@ fn cmpNumeric( return self.constUndef(scope, src, Type.initTag(.bool)); const is_unsigned = if (rhs_is_float) x: { var bigint_space: Value.BigIntSpace = undefined; - var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.allocator); + var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.gpa); defer bigint.deinit(); const zcmp = rhs_val.orderAgainstZero(); if (rhs_val.floatHasFraction()) { @@ -3457,7 +3436,7 @@ fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *I fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: var) InnerError { @setCold(true); - const err_msg = try ErrorMsg.create(self.allocator, src, format, args); + const err_msg = try ErrorMsg.create(self.gpa, src, format, args); return self.failWithOwnedErrorMsg(scope, src, err_msg); } @@ -3487,9 +3466,9 @@ fn failNode( fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *ErrorMsg) InnerError { { - errdefer err_msg.destroy(self.allocator); - try self.failed_decls.ensureCapacity(self.failed_decls.items().len + 1); - try self.failed_files.ensureCapacity(self.failed_files.items().len + 1); + errdefer err_msg.destroy(self.gpa); + try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); + try self.failed_files.ensureCapacity(self.gpa, self.failed_files.items().len + 1); } switch (scope.tag) { .decl => { @@ -3542,28 +3521,28 @@ pub const ErrorMsg = struct { byte_offset: usize, msg: []const u8, - pub fn create(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !*ErrorMsg { - const self = try allocator.create(ErrorMsg); - errdefer allocator.destroy(self); - self.* = try init(allocator, byte_offset, format, args); + pub fn create(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !*ErrorMsg { + const self = try gpa.create(ErrorMsg); + errdefer gpa.destroy(self); + self.* = try init(gpa, byte_offset, format, args); return self; } /// Assumes the ErrorMsg struct and msg were both allocated with allocator. - pub fn destroy(self: *ErrorMsg, allocator: *Allocator) void { - self.deinit(allocator); - allocator.destroy(self); + pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void { + self.deinit(gpa); + gpa.destroy(self); } - pub fn init(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !ErrorMsg { + pub fn init(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !ErrorMsg { return ErrorMsg{ .byte_offset = byte_offset, - .msg = try std.fmt.allocPrint(allocator, format, args), + .msg = try std.fmt.allocPrint(gpa, format, args), }; } - pub fn deinit(self: *ErrorMsg, allocator: *Allocator) void { - allocator.free(self.msg); + pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void { + gpa.free(self.msg); self.* = undefined; } }; diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index eea3de7f36cd0c5d7d782f6924ae5fd4e83575a0..e364763b0a98f384a198483c477121faa4c51ba6 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -46,7 +46,14 @@ pub fn generateSymbol( var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len); defer mc_args.deinit(); - var next_stack_offset: u64 = 0; + var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator); + defer { + assert(branch_stack.items.len == 1); + branch_stack.items[0].deinit(bin_file.allocator); + branch_stack.deinit(); + } + const branch = try branch_stack.addOne(); + branch.* = .{}; switch (fn_type.fnCallingConvention()) { .Naked => assert(mc_args.items.len == 0), @@ -61,8 +68,8 @@ pub fn generateSymbol( switch (param_type.zigTypeTag()) { .Bool, .Int => { if (next_int_reg >= integer_registers.len) { - try mc_args.append(.{ .stack_offset = next_stack_offset }); - next_stack_offset += param_type.abiSize(bin_file.options.target); + try mc_args.append(.{ .stack_offset = branch.next_stack_offset }); + branch.next_stack_offset += @intCast(u32, param_type.abiSize(bin_file.options.target)); } else { try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg]) }); next_int_reg += 1; @@ -100,23 +107,17 @@ pub fn generateSymbol( } var function = Function{ + .gpa = bin_file.allocator, .target = &bin_file.options.target, .bin_file = bin_file, .mod_fn = module_fn, .code = code, .err_msg = null, .args = mc_args.items, - .branch_stack = .{}, + .branch_stack = &branch_stack, }; - defer { - assert(function.branch_stack.items.len == 1); - function.branch_stack.items[0].inst_table.deinit(); - function.branch_stack.deinit(bin_file.allocator); - } - try function.branch_stack.append(bin_file.allocator, .{ - .inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(bin_file.allocator), - }); + branch.max_end_stack = branch.next_stack_offset; function.gen() catch |err| switch (err) { error.CodegenFail => return Result{ .fail = function.err_msg.? }, else => |e| return e, @@ -218,6 +219,7 @@ pub fn generateSymbol( } const Function = struct { + gpa: *Allocator, bin_file: *link.ElfFile, target: *const std.Target, mod_fn: *const Module.Fn, @@ -232,10 +234,37 @@ const Function = struct { /// within different branches. Special consideration is needed when a branch /// joins with its parent, to make sure all instructions have the same MCValue /// across each runtime branch upon joining. - branch_stack: std.ArrayListUnmanaged(Branch), + branch_stack: *std.ArrayList(Branch), const Branch = struct { - inst_table: std.AutoHashMap(*ir.Inst, MCValue), + inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, + + /// The key is an enum value of an arch-specific register. + registers: std.AutoHashMapUnmanaged(usize, RegisterAllocation) = .{}, + + /// Maps offset to what is stored there. + stack: std.AutoHashMapUnmanaged(usize, StackAllocation) = .{}, + /// Offset from the stack base, representing the end of the stack frame. + max_end_stack: u32 = 0, + /// Represents the current end stack offset. If there is no existing slot + /// to place a new stack allocation, it goes here, and then bumps `max_end_stack`. + next_stack_offset: u32 = 0, + + fn deinit(self: *Branch, gpa: *Allocator) void { + self.inst_table.deinit(gpa); + self.registers.deinit(gpa); + self.stack.deinit(gpa); + self.* = undefined; + } + }; + + const RegisterAllocation = struct { + inst: *ir.Inst, + }; + + const StackAllocation = struct { + inst: *ir.Inst, + size: u32, }; const MCValue = union(enum) { @@ -256,6 +285,13 @@ const Function = struct { memory: u64, /// The value is one of the stack variables. stack_offset: u64, + + fn isMemory(mcv: MCValue) bool { + return switch (mcv) { + .embedded_in_code, .memory, .stack_offset => true, + else => false, + }; + } }; fn gen(self: *Function) !void { @@ -318,7 +354,7 @@ const Function = struct { const inst_table = &self.branch_stack.items[0].inst_table; for (self.mod_fn.analysis.success.instructions) |inst| { const new_inst = try self.genFuncInst(inst, arch); - try inst_table.putNoClobber(inst, new_inst); + try inst_table.putNoClobber(self.gpa, inst, new_inst); } } @@ -344,19 +380,99 @@ const Function = struct { } fn genAdd(self: *Function, inst: *ir.Inst.Add, comptime arch: std.Target.Cpu.Arch) !MCValue { - const lhs = try self.resolveInst(inst.args.lhs); - const rhs = try self.resolveInst(inst.args.rhs); + // No side effects, so if it's unreferenced, do nothing. + if (inst.base.isUnused()) + return MCValue.dead; switch (arch) { - .i386, .x86_64 => { - // const lhs_reg = try self.instAsReg(lhs); - // const rhs_reg = try self.instAsReg(rhs); - // const result = try self.allocateReg(); + .x86_64 => { + // Biggest encoding of ADD is 8 bytes. + try self.code.ensureCapacity(self.code.items.len + 8); - // try self.code.append(??); + // In x86, ADD has 2 operands, destination and source. + // Either one, but not both, can be a memory operand. + // Source operand can be an immediate, 8 bits or 32 bits. + // So, if either one of the operands dies with this instruction, we can use it + // as the result MCValue. + var dst_mcv: MCValue = undefined; + var src_mcv: MCValue = undefined; + if (inst.base.operandDies(0)) { + // LHS dies; use it as the destination. + dst_mcv = try self.resolveInst(inst.args.lhs); + // Both operands cannot be memory. + if (dst_mcv.isMemory()) { + src_mcv = try self.resolveInstImmOrReg(inst.args.rhs); + } else { + src_mcv = try self.resolveInst(inst.args.rhs); + } + } else if (inst.base.operandDies(1)) { + // RHS dies; use it as the destination. + dst_mcv = try self.resolveInst(inst.args.rhs); + // Both operands cannot be memory. + if (dst_mcv.isMemory()) { + src_mcv = try self.resolveInstImmOrReg(inst.args.lhs); + } else { + src_mcv = try self.resolveInst(inst.args.lhs); + } + } else { + const lhs = try self.resolveInst(inst.args.lhs); + const rhs = try self.resolveInst(inst.args.rhs); + if (lhs.isMemory()) { + dst_mcv = try self.copyToNewRegister(inst.base.src, lhs); + src_mcv = rhs; + } else { + dst_mcv = try self.copyToNewRegister(inst.base.src, rhs); + src_mcv = lhs; + } + } + // x86 ADD supports only signed 32-bit immediates at most. If the immediate + // value is larger than this, we put it in a register. + // A potential opportunity for future optimization here would be keeping track + // of the fact that the instruction is available both as an immediate + // and as a register. + switch (src_mcv) { + .immediate => |imm| { + if (imm > std.math.maxInt(u31)) { + src_mcv = try self.copyToNewRegister(inst.base.src, src_mcv); + } + }, + else => {}, + } - // lhs_reg.release(); - // rhs_reg.release(); - return self.fail(inst.base.src, "TODO implement register allocation", .{}); + switch (dst_mcv) { + .none => unreachable, + .dead, .unreach, .immediate => unreachable, + .register => |dst_reg_usize| { + const dst_reg = @intToEnum(Reg(arch), @intCast(@TagType(Reg(arch)), dst_reg_usize)); + switch (src_mcv) { + .none => unreachable, + .dead, .unreach => unreachable, + .register => |src_reg_usize| { + const src_reg = @intToEnum(Reg(arch), @intCast(@TagType(Reg(arch)), src_reg_usize)); + self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); + }, + .immediate => |imm| { + const imm32 = @intCast(u31, imm); // We handle this case above. + // 81 /0 id + if (imm32 <= std.math.maxInt(u7)) { + self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x83, 0xC0 | @as(u8, dst_reg.id() & 0b111), @intCast(u8, imm32)}); + } else { + self.rex(.{ .r = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x81, 0xC0 | @as(u8, dst_reg.id() & 0b111) }); + std.mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), imm32); + } + }, + .embedded_in_code, .memory, .stack_offset => { + return self.fail(inst.base.src, "TODO implement x86 add source memory", .{}); + }, + } + }, + .embedded_in_code, .memory, .stack_offset => { + return self.fail(inst.base.src, "TODO implement x86 add destination memory", .{}); + }, + } + return dst_mcv; }, else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), } @@ -526,23 +642,23 @@ const Function = struct { /// resulting REX is meaningful, but will remain the same if it is not. /// * Deliberately inserting a "meaningless REX" requires explicit usage of /// 0x40, and cannot be done via this function. - fn REX(self: *Function, arg: struct { B: bool = false, W: bool = false, X: bool = false, R: bool = false }) !void { + fn rex(self: *Function, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. var value: u8 = 0x40; - if (arg.B) { + if (arg.b) { value |= 0x1; } - if (arg.X) { + if (arg.x) { value |= 0x2; } - if (arg.R) { + if (arg.r) { value |= 0x4; } - if (arg.W) { + if (arg.w) { value |= 0x8; } if (value != 0x40) { - try self.code.append(value); + self.code.appendAssumeCapacity(value); } } @@ -570,11 +686,11 @@ const Function = struct { // If we're accessing e.g. r8d, we need to use a REX prefix before the actual operation. Since // this is a 32-bit operation, the W flag is set to zero. X is also zero, as we're not using a SIB. // Both R and B are set, as we're extending, in effect, the register bits *and* the operand. - try self.REX(.{ .R = reg.isExtended(), .B = reg.isExtended() }); + try self.code.ensureCapacity(self.code.items.len + 3); + self.rex(.{ .r = reg.isExtended(), .b = reg.isExtended() }); const id = @as(u8, reg.id() & 0b111); - return self.code.appendSlice(&[_]u8{ - 0x31, 0xC0 | id << 3 | id, - }); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x31, 0xC0 | id << 3 | id }); + return; } if (x <= std.math.maxInt(u32)) { // Next best case: if we set the lower four bytes, the upper four will be zeroed. @@ -607,9 +723,9 @@ const Function = struct { // Since we always need a REX here, let's just check if we also need to set REX.B. // // In this case, the encoding of the REX byte is 0b0100100B - - try self.REX(.{ .W = true, .B = reg.isExtended() }); - try self.code.resize(self.code.items.len + 9); + try self.code.ensureCapacity(self.code.items.len + 10); + self.rex(.{ .w = true, .b = reg.isExtended() }); + self.code.items.len += 9; self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111); const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; mem.writeIntLittle(u64, imm_ptr, x); @@ -620,13 +736,13 @@ const Function = struct { } // We need the offset from RIP in a signed i32 twos complement. // The instruction is 7 bytes long and RIP points to the next instruction. - // + try self.code.ensureCapacity(self.code.items.len + 7); // 64-bit LEA is encoded as REX.W 8D /r. If the register is extended, the REX byte is modified, // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three // bits as five. // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id. - try self.REX(.{ .W = true, .B = reg.isExtended() }); - try self.code.resize(self.code.items.len + 6); + self.rex(.{ .w = true, .b = reg.isExtended() }); + self.code.items.len += 6; const rip = self.code.items.len; const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); const offset = @intCast(i32, big_offset); @@ -646,9 +762,10 @@ const Function = struct { // If the *source* is extended, the B field must be 1. // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle // three bits) contain the destination, and the R/M field (the lower three bits) contain the source. - try self.REX(.{ .W = true, .R = reg.isExtended(), .B = src_reg.isExtended() }); + try self.code.ensureCapacity(self.code.items.len + 3); + self.rex(.{ .w = true, .r = reg.isExtended(), .b = src_reg.isExtended() }); const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111); - try self.code.appendSlice(&[_]u8{ 0x8B, R }); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R }); }, .memory => |x| { if (reg.size() != 64) { @@ -662,14 +779,14 @@ const Function = struct { // The SIB must be 0x25, to indicate a disp32 with no scaled index. // 0b00RRR100, where RRR is the lower three bits of the register ID. // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32. - try self.REX(.{ .W = true, .B = reg.isExtended() }); - try self.code.resize(self.code.items.len + 7); - const r = 0x04 | (@as(u8, reg.id() & 0b111) << 3); - self.code.items[self.code.items.len - 7] = 0x8B; - self.code.items[self.code.items.len - 6] = r; - self.code.items[self.code.items.len - 5] = 0x25; - const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; - mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); + try self.code.ensureCapacity(self.code.items.len + 8); + self.rex(.{ .w = true, .b = reg.isExtended() }); + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0x8B, + 0x04 | (@as(u8, reg.id() & 0b111) << 3), // R + 0x25, + }); + mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), @intCast(u32, x)); } else { // If this is RAX, we can use a direct load; otherwise, we need to load the address, then indirectly load // the value. @@ -700,15 +817,15 @@ const Function = struct { // Currently, we're only allowing 64-bit registers, so we need the `REX.W 8B /r` variant. // TODO: determine whether to allow other sized registers, and if so, handle them properly. // This operation requires three bytes: REX 0x8B R/M - // + try self.code.ensureCapacity(self.code.items.len + 3); // For this operation, we want R/M mode *zero* (use register indirectly), and the two register // values must match. Thus, it's 00ABCABC where ABC is the lower three bits of the register ID. // // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both* // register operands need to be marked as extended. - try self.REX(.{ .W = true, .B = reg.isExtended(), .R = reg.isExtended() }); + self.rex(.{ .w = true, .b = reg.isExtended(), .r = reg.isExtended() }); const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); - try self.code.appendSlice(&[_]u8{ 0x8B, RM }); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); } } }, @@ -731,36 +848,40 @@ const Function = struct { } fn resolveInst(self: *Function, inst: *ir.Inst) !MCValue { - if (self.inst_table.get(inst)) |mcv| { - return mcv; - } // Constants have static lifetimes, so they are always memoized in the outer most table. if (inst.cast(ir.Inst.Constant)) |const_inst| { const branch = &self.branch_stack.items[0]; - const gop = try branch.inst_table.getOrPut(inst); + const gop = try branch.inst_table.getOrPut(self.gpa, inst); if (!gop.found_existing) { const mcv = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); - try branch.inst_table.putNoClobber(inst, mcv); - gop.kv.value = mcv; + try branch.inst_table.putNoClobber(self.gpa, inst, mcv); + gop.entry.value = mcv; return mcv; } - return gop.kv.value; + return gop.entry.value; } // Treat each stack item as a "layer" on top of the previous one. var i: usize = self.branch_stack.items.len; while (true) { i -= 1; - if (self.branch_stack.items[i].inst_table.getValue(inst)) |mcv| { + if (self.branch_stack.items[i].inst_table.get(inst)) |mcv| { return mcv; } } } + fn resolveInstImmOrReg(self: *Function, inst: *ir.Inst) !MCValue { + return self.fail(inst.src, "TODO implement resolveInstImmOrReg", .{}); + } + + fn copyToNewRegister(self: *Function, src: usize, mcv: MCValue) !MCValue { + return self.fail(src, "TODO implement copyToNewRegister", .{}); + } + fn genTypedValue(self: *Function, src: usize, typed_value: TypedValue) !MCValue { const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); - const allocator = self.code.allocator; switch (typed_value.ty.zigTypeTag()) { .Pointer => { if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| { @@ -787,7 +908,7 @@ const Function = struct { fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } { @setCold(true); assert(self.err_msg == null); - self.err_msg = try ErrorMsg.create(self.code.allocator, src, format, args); + self.err_msg = try ErrorMsg.create(self.bin_file.allocator, src, format, args); return error.CodegenFail; } }; diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 6fa06c81383e9fd5227546cc3f5393231aa03f3c..17e6a54725f8449938759e71e0fa0aec807e3e95 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -2,6 +2,7 @@ const std = @import("std"); const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const Module = @import("Module.zig"); +const assert = std.debug.assert; /// These are in-memory, analyzed instructions. See `zir.Inst` for the representation /// of instructions that correspond to the ZIR text format. @@ -12,18 +13,28 @@ pub const Inst = struct { tag: Tag, /// Each bit represents the index of an `Inst` parameter in the `args` field. /// If a bit is set, it marks the end of the lifetime of the corresponding - /// instruction parameter. For example, 0b00000101 means that the first and + /// instruction parameter. For example, 0b000_00101 means that the first and /// third `Inst` parameters' lifetimes end after this instruction, and will /// not have any more following references. /// The most significant bit being set means that the instruction itself is /// never referenced, in other words its lifetime ends as soon as it finishes. - /// If the byte is `0xff`, it means this is a special case and this data is - /// encoded elsewhere. - deaths: u8 = 0xff, + /// If bit 7 (0b1xxx_xxxx) is set, it means this instruction itself is unreferenced. + /// If bit 6 (0bx1xx_xxxx) is set, it means this is a special case and the + /// lifetimes of operands are encoded elsewhere. + deaths: u8 = undefined, ty: Type, /// Byte offset into the source. src: usize, + pub fn isUnused(self: Inst) bool { + return (self.deaths & 0b1000_0000) != 0; + } + + pub fn operandDies(self: Inst, index: u3) bool { + assert(index < 6); + return @truncate(u1, self.deaths << index) != 0; + } + pub const Tag = enum { add, arg, @@ -240,4 +251,4 @@ pub const Inst = struct { pub const Body = struct { instructions: []*Inst, -}; \ No newline at end of file +}; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index c615ad35fdaab745905eb7b0e5fab2155aff10ce..a0894fb1a105722bc0f6614b6a663bc4d097b83c 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -1007,7 +1007,7 @@ pub const ElfFile = struct { .appended => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; - _ = try module.failed_decls.put(decl, em); + _ = try module.failed_decls.put(module.gpa, decl, em); return; }, }; @@ -1093,7 +1093,7 @@ pub const ElfFile = struct { for (exports) |exp| { if (exp.options.section) |section_name| { if (!mem.eql(u8, section_name, ".text")) { - try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); + try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}), @@ -1111,7 +1111,7 @@ pub const ElfFile = struct { }, .Weak => elf.STB_WEAK, .LinkOnce => { - try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); + try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1); module.failed_exports.putAssumeCapacityNoClobber( exp, try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), diff --git a/src-self-hosted/liveness.zig b/src-self-hosted/liveness.zig index 65f81bbf4a8be0768891e4483a3ccf0f7167a5e9..2e0aad49c3ac4a33142099591cf0ca57e3a0631f 100644 --- a/src-self-hosted/liveness.zig +++ b/src-self-hosted/liveness.zig @@ -123,7 +123,7 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void if (arg_index >= 6) { @compileError("out of bits to mark deaths of operands"); } - const prev = try table.put(@field(inst.args, field.name), {}); + const prev = try table.fetchPut(@field(inst.args, field.name), {}); if (prev == null) { // Death. inst.base.deaths |= 1 << arg_index; @@ -131,4 +131,4 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void arg_index += 1; } } -} \ No newline at end of file +} diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 33f422692c9209bf2542af227d1b86256817fca1..82edcc4eff7d43a54b0c98c612a1164f61061c6d 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -502,7 +502,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo const update_nanos = timer.read(); var errors = try module.getAllErrorsAlloc(); - defer errors.deinit(module.allocator); + defer errors.deinit(module.gpa); if (errors.list.len != 0) { for (errors.list) |full_err_msg| { -- 2.54.0 From 12737c9a3071a49f8c05348fadd0b602b6952542 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Jul 2020 09:21:57 +0000 Subject: [PATCH 192/295] stage2: codegen skeleton for cmp and sub --- src-self-hosted/Module.zig | 5 + src-self-hosted/codegen.zig | 297 +++++++++++++++++++++++++----------- src-self-hosted/ir.zig | 14 +- src-self-hosted/zir.zig | 30 ++++ 4 files changed, 254 insertions(+), 92 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 4240a1c6c880a4637a1750bebb4025e016c9346c..776e63018ba3cb7ac77170ab99ff7d716023664b 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2402,6 +2402,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In .bitcast => return self.analyzeInstBitCast(scope, old_inst.cast(zir.Inst.BitCast).?), .elemptr => return self.analyzeInstElemPtr(scope, old_inst.cast(zir.Inst.ElemPtr).?), .add => return self.analyzeInstAdd(scope, old_inst.cast(zir.Inst.Add).?), + .sub => return self.analyzeInstSub(scope, old_inst.cast(zir.Inst.Sub).?), .cmp => return self.analyzeInstCmp(scope, old_inst.cast(zir.Inst.Cmp).?), .condbr => return self.analyzeInstCondBr(scope, old_inst.cast(zir.Inst.CondBr).?), .isnull => return self.analyzeInstIsNull(scope, old_inst.cast(zir.Inst.IsNull).?), @@ -2903,6 +2904,10 @@ fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inn return self.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); } +fn analyzeInstSub(self: *Module, scope: *Scope, inst: *zir.Inst.Sub) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analysis of sub", .{}); +} + fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index e364763b0a98f384a198483c477121faa4c51ba6..aac1a53d6a0e643c3cd945cee1b242c48df68ed1 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -285,6 +285,9 @@ const Function = struct { memory: u64, /// The value is one of the stack variables. stack_offset: u64, + /// The value is the compare flag, with this operator + /// applied on top of it. + compare_flag: std.math.CompareOperator, fn isMemory(mcv: MCValue) bool { return switch (mcv) { @@ -292,6 +295,31 @@ const Function = struct { else => false, }; } + + fn isImmediate(mcv: MCValue) bool { + return switch (mcv) { + .immediate => true, + else => false, + }; + } + + fn isMutable(mcv: MCValue) bool { + return switch (mcv) { + .none => unreachable, + .unreach => unreachable, + .dead => unreachable, + + .immediate, + .embedded_in_code, + .memory, + .compare_flag, + => false, + + .register, + .stack_offset, + => true, + }; + } }; fn gen(self: *Function) !void { @@ -362,20 +390,21 @@ const Function = struct { switch (inst.tag) { .add => return self.genAdd(inst.cast(ir.Inst.Add).?, arch), .arg => return self.genArg(inst.cast(ir.Inst.Arg).?), + .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?, arch), + .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), .block => return self.genBlock(inst.cast(ir.Inst.Block).?, arch), .breakpoint => return self.genBreakpoint(inst.src, arch), .call => return self.genCall(inst.cast(ir.Inst.Call).?, arch), - .unreach => return MCValue{ .unreach = {} }, + .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?, arch), + .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?, arch), .constant => unreachable, // excluded from function bodies - .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?, arch), + .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?, arch), + .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?, arch), .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), - .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), .ret => return self.genRet(inst.cast(ir.Inst.Ret).?, arch), .retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?, arch), - .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?, arch), - .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?, arch), - .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?, arch), - .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?, arch), + .sub => return self.genSub(inst.cast(ir.Inst.Sub).?, arch), + .unreach => return MCValue{ .unreach = {} }, } } @@ -385,96 +414,136 @@ const Function = struct { return MCValue.dead; switch (arch) { .x86_64 => { - // Biggest encoding of ADD is 8 bytes. - try self.code.ensureCapacity(self.code.items.len + 8); + return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 0, 0x00); + }, + else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), + } + } - // In x86, ADD has 2 operands, destination and source. - // Either one, but not both, can be a memory operand. - // Source operand can be an immediate, 8 bits or 32 bits. - // So, if either one of the operands dies with this instruction, we can use it - // as the result MCValue. - var dst_mcv: MCValue = undefined; - var src_mcv: MCValue = undefined; - if (inst.base.operandDies(0)) { - // LHS dies; use it as the destination. - dst_mcv = try self.resolveInst(inst.args.lhs); - // Both operands cannot be memory. - if (dst_mcv.isMemory()) { - src_mcv = try self.resolveInstImmOrReg(inst.args.rhs); - } else { - src_mcv = try self.resolveInst(inst.args.rhs); - } - } else if (inst.base.operandDies(1)) { - // RHS dies; use it as the destination. - dst_mcv = try self.resolveInst(inst.args.rhs); - // Both operands cannot be memory. - if (dst_mcv.isMemory()) { - src_mcv = try self.resolveInstImmOrReg(inst.args.lhs); - } else { - src_mcv = try self.resolveInst(inst.args.lhs); - } - } else { - const lhs = try self.resolveInst(inst.args.lhs); - const rhs = try self.resolveInst(inst.args.rhs); - if (lhs.isMemory()) { - dst_mcv = try self.copyToNewRegister(inst.base.src, lhs); - src_mcv = rhs; - } else { - dst_mcv = try self.copyToNewRegister(inst.base.src, rhs); - src_mcv = lhs; - } + fn genSub(self: *Function, inst: *ir.Inst.Sub, comptime arch: std.Target.Cpu.Arch) !MCValue { + // No side effects, so if it's unreferenced, do nothing. + if (inst.base.isUnused()) + return MCValue.dead; + switch (arch) { + .x86_64 => { + return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 5, 0x28); + }, + else => return self.fail(inst.base.src, "TODO implement sub for {}", .{self.target.cpu.arch}), + } + } + + /// ADD, SUB + fn genX8664BinMath(self: *Function, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, opx: u8, mr: u8) !MCValue { + try self.code.ensureCapacity(self.code.items.len + 8); + + const lhs = try self.resolveInst(op_lhs); + const rhs = try self.resolveInst(op_rhs); + + // There are 2 operands, destination and source. + // Either one, but not both, can be a memory operand. + // Source operand can be an immediate, 8 bits or 32 bits. + // So, if either one of the operands dies with this instruction, we can use it + // as the result MCValue. + var dst_mcv: MCValue = undefined; + var src_mcv: MCValue = undefined; + var src_inst: *ir.Inst = undefined; + if (inst.operandDies(0) and lhs.isMutable()) { + // LHS dies; use it as the destination. + // Both operands cannot be memory. + src_inst = op_rhs; + if (lhs.isMemory() and rhs.isMemory()) { + dst_mcv = try self.copyToNewRegister(op_lhs); + src_mcv = rhs; + } else { + dst_mcv = lhs; + src_mcv = rhs; + } + } else if (inst.operandDies(1) and rhs.isMutable()) { + // RHS dies; use it as the destination. + // Both operands cannot be memory. + src_inst = op_lhs; + if (lhs.isMemory() and rhs.isMemory()) { + dst_mcv = try self.copyToNewRegister(op_rhs); + src_mcv = lhs; + } else { + dst_mcv = rhs; + src_mcv = lhs; + } + } else { + if (lhs.isMemory()) { + dst_mcv = try self.copyToNewRegister(op_lhs); + src_mcv = rhs; + src_inst = op_rhs; + } else { + dst_mcv = try self.copyToNewRegister(op_rhs); + src_mcv = lhs; + src_inst = op_lhs; + } + } + // This instruction supports only signed 32-bit immediates at most. If the immediate + // value is larger than this, we put it in a register. + // A potential opportunity for future optimization here would be keeping track + // of the fact that the instruction is available both as an immediate + // and as a register. + switch (src_mcv) { + .immediate => |imm| { + if (imm > std.math.maxInt(u31)) { + src_mcv = try self.copyToNewRegister(src_inst); } - // x86 ADD supports only signed 32-bit immediates at most. If the immediate - // value is larger than this, we put it in a register. - // A potential opportunity for future optimization here would be keeping track - // of the fact that the instruction is available both as an immediate - // and as a register. + }, + else => {}, + } + + try self.genX8664BinMathCode(inst.src, dst_mcv, src_mcv, opx, mr); + + return dst_mcv; + } + + fn genX8664BinMathCode(self: *Function, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void { + switch (dst_mcv) { + .none => unreachable, + .dead, .unreach, .immediate => unreachable, + .compare_flag => unreachable, + .register => |dst_reg_usize| { + const dst_reg = @intToEnum(Reg(.x86_64), @intCast(u8, dst_reg_usize)); switch (src_mcv) { - .immediate => |imm| { - if (imm > std.math.maxInt(u31)) { - src_mcv = try self.copyToNewRegister(inst.base.src, src_mcv); - } - }, - else => {}, - } - - switch (dst_mcv) { .none => unreachable, - .dead, .unreach, .immediate => unreachable, - .register => |dst_reg_usize| { - const dst_reg = @intToEnum(Reg(arch), @intCast(@TagType(Reg(arch)), dst_reg_usize)); - switch (src_mcv) { - .none => unreachable, - .dead, .unreach => unreachable, - .register => |src_reg_usize| { - const src_reg = @intToEnum(Reg(arch), @intCast(@TagType(Reg(arch)), src_reg_usize)); - self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); - self.code.appendSliceAssumeCapacity(&[_]u8{ 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); - }, - .immediate => |imm| { - const imm32 = @intCast(u31, imm); // We handle this case above. - // 81 /0 id - if (imm32 <= std.math.maxInt(u7)) { - self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); - self.code.appendSliceAssumeCapacity(&[_]u8{ 0x83, 0xC0 | @as(u8, dst_reg.id() & 0b111), @intCast(u8, imm32)}); - } else { - self.rex(.{ .r = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); - self.code.appendSliceAssumeCapacity(&[_]u8{ 0x81, 0xC0 | @as(u8, dst_reg.id() & 0b111) }); - std.mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), imm32); - } - }, - .embedded_in_code, .memory, .stack_offset => { - return self.fail(inst.base.src, "TODO implement x86 add source memory", .{}); - }, + .dead, .unreach => unreachable, + .register => |src_reg_usize| { + const src_reg = @intToEnum(Reg(.x86_64), @intCast(u8, src_reg_usize)); + self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ mr + 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); + }, + .immediate => |imm| { + const imm32 = @intCast(u31, imm); // This case must be handled before calling genX8664BinMathCode. + // 81 /opx id + if (imm32 <= std.math.maxInt(u7)) { + self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0x83, + 0xC0 | (opx << 3) | @truncate(u3, dst_reg.id()), + @intCast(u8, imm32), + }); + } else { + self.rex(.{ .r = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0x81, + 0xC0 | (opx << 3) | @truncate(u3, dst_reg.id()), + }); + std.mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), imm32); } }, .embedded_in_code, .memory, .stack_offset => { - return self.fail(inst.base.src, "TODO implement x86 add destination memory", .{}); + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{}); + }, + .compare_flag => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag", .{}); }, } - return dst_mcv; }, - else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), + .embedded_in_code, .memory, .stack_offset => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP destination memory", .{}); + }, } } @@ -550,7 +619,29 @@ const Function = struct { } fn genCmp(self: *Function, inst: *ir.Inst.Cmp, comptime arch: std.Target.Cpu.Arch) !MCValue { + // No side effects, so if it's unreferenced, do nothing. + if (inst.base.isUnused()) + return MCValue.dead; switch (arch) { + .x86_64 => { + try self.code.ensureCapacity(self.code.items.len + 8); + + const lhs = try self.resolveInst(inst.args.lhs); + const rhs = try self.resolveInst(inst.args.rhs); + + // There are 2 operands, destination and source. + // Either one, but not both, can be a memory operand. + // Source operand can be an immediate, 8 bits or 32 bits. + const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory())) + try self.copyToNewRegister(inst.args.lhs) + else + lhs; + // This instruction supports only signed 32-bit immediates at most. + const src_mcv = try self.limitImmediateType(inst.args.rhs, i32); + + try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38); + return MCValue{.compare_flag = inst.args.op}; + }, else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), } } @@ -668,6 +759,9 @@ const Function = struct { .dead => unreachable, .none => unreachable, .unreach => unreachable, + .compare_flag => |op| { + return self.fail(src, "TODO set register with compare flag value", .{}); + }, .immediate => |x| { if (reg.size() != 64) { return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); @@ -871,14 +965,35 @@ const Function = struct { } } - fn resolveInstImmOrReg(self: *Function, inst: *ir.Inst) !MCValue { - return self.fail(inst.src, "TODO implement resolveInstImmOrReg", .{}); + fn copyToNewRegister(self: *Function, inst: *ir.Inst) !MCValue { + return self.fail(inst.src, "TODO implement copyToNewRegister", .{}); } - fn copyToNewRegister(self: *Function, src: usize, mcv: MCValue) !MCValue { - return self.fail(src, "TODO implement copyToNewRegister", .{}); + /// If the MCValue is an immediate, and it does not fit within this type, + /// we put it in a register. + /// A potential opportunity for future optimization here would be keeping track + /// of the fact that the instruction is available both as an immediate + /// and as a register. + fn limitImmediateType(self: *Function, inst: *ir.Inst, comptime T: type) !MCValue { + const mcv = try self.resolveInst(inst); + const ti = @typeInfo(T).Int; + switch (mcv) { + .immediate => |imm| { + // This immediate is unsigned. + const U = @Type(.{ .Int = .{ + .bits = ti.bits - @boolToInt(ti.is_signed), + .is_signed = false, + }}); + if (imm >= std.math.maxInt(U)) { + return self.copyToNewRegister(inst); + } + }, + else => {}, + } + return mcv; } + fn genTypedValue(self: *Function, src: usize, typed_value: TypedValue) !MCValue { const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 17e6a54725f8449938759e71e0fa0aec807e3e95..c8acc14545ce21dfb0d022732192a610ee0cb0e0 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -51,6 +51,7 @@ pub const Inst = struct { ptrtoint, ret, retvoid, + sub, unreach, /// Returns whether the instruction is one of the control flow "noreturn" types. @@ -64,12 +65,13 @@ pub const Inst = struct { .bitcast, .block, .breakpoint, + .call, .cmp, .constant, .isnonnull, .isnull, .ptrtoint, - .call, + .sub, => false, .condbr, @@ -242,6 +244,16 @@ pub const Inst = struct { args: void, }; + pub const Sub = struct { + pub const base_tag = Tag.sub; + base: Inst, + + args: struct { + lhs: *Inst, + rhs: *Inst, + }, + }; + pub const Unreach = struct { pub const base_tag = Tag.unreach; base: Inst, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 95548657a9c7af14a9f4d722f725171e2286646a..628773e09c57b80121f33c9d6797089648d99407 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -73,6 +73,7 @@ pub const Inst = struct { bitcast, elemptr, add, + sub, cmp, condbr, isnull, @@ -110,6 +111,7 @@ pub const Inst = struct { .bitcast => BitCast, .elemptr => ElemPtr, .add => Add, + .sub => Sub, .cmp => Cmp, .condbr => CondBr, .isnull => IsNull, @@ -512,6 +514,17 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const Sub = struct { + pub const base_tag = Tag.sub; + base: Inst, + + positionals: struct { + lhs: *Inst, + rhs: *Inst, + }, + kw_args: struct {}, + }; + pub const Cmp = struct { pub const base_tag = Tag.cmp; base: Inst, @@ -677,6 +690,7 @@ pub const Module = struct { .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst, inst_table), .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst, inst_table), .add => return self.writeInstToStreamGeneric(stream, .add, inst, inst_table), + .sub => return self.writeInstToStreamGeneric(stream, .sub, inst, inst_table), .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst, inst_table), .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst, inst_table), .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst, inst_table), @@ -1542,6 +1556,22 @@ const EmitZIR = struct { }; break :blk &new_inst.base; }, + .sub => blk: { + const old_inst = inst.cast(ir.Inst.Sub).?; + const new_inst = try self.arena.allocator.create(Inst.Sub); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.Sub.base_tag, + }, + .positionals = .{ + .lhs = try self.resolveInst(new_body, old_inst.args.lhs), + .rhs = try self.resolveInst(new_body, old_inst.args.rhs), + }, + .kw_args = .{}, + }; + break :blk &new_inst.base; + }, .arg => blk: { const old_inst = inst.cast(ir.Inst.Arg).?; const new_inst = try self.arena.allocator.create(Inst.Arg); -- 2.54.0 From 485231deaef9b12e013d423facdef1624f16014b Mon Sep 17 00:00:00 2001 From: Vexu Date: Mon, 6 Jul 2020 16:51:53 +0300 Subject: [PATCH 193/295] fix HashMap.clone() --- lib/std/hash_map.zig | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index aaec9a4d58758a945953fc4008c4ad6af1134dde..05550a289a46a263a19fd819af48169164f2caad 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -529,12 +529,13 @@ pub fn HashMapUnmanaged( } pub fn clone(self: Self, allocator: *Allocator) !Self { - // TODO this can be made more efficient by directly allocating - // the memory slices and memcpying the elements. - var other = Self.init(); - try other.initCapacity(allocator, self.entries.len); - for (self.entries.items) |entry| { - other.putAssumeCapacityNoClobber(entry.key, entry.value); + var other: Self = .{}; + try other.entries.appendSlice(allocator, self.entries.items); + + if (self.index_header) |header| { + const new_header = try IndexHeader.alloc(allocator, header.indexes_len); + other.insertAllEntriesIntoNewHeader(new_header); + other.index_header = new_header; } return other; } @@ -976,6 +977,25 @@ test "ensure capacity" { testing.expect(initial_capacity == map.capacity()); } +test "clone" { + var original = AutoHashMap(i32, i32).init(std.testing.allocator); + defer original.deinit(); + + // put more than `linear_scan_max` so we can test that the index header is properly cloned + var i: u8 = 0; + while (i < 10) : (i += 1) { + try original.putNoClobber(i, i * 10); + } + + var copy = try original.clone(); + defer copy.deinit(); + + i = 0; + while (i < 10) : (i += 1) { + testing.expect(copy.get(i).? == i * 10); + } +} + pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) { return struct { fn hash(key: K) u32 { -- 2.54.0 From 0db0258fb2fed196791d3579fb3b893736d28d0a Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 6 Jul 2020 17:54:06 -0400 Subject: [PATCH 194/295] Remove old comment --- src-self-hosted/test.zig | 3 --- 1 file changed, 3 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 38392b8aa741df56efe16e3d7998f5cece071d19..ae5cecce209d5ea461b8a3926c3a958274356c68 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -415,9 +415,6 @@ pub const TestContext = struct { var module = try Module.init(allocator, .{ .target = target, - // This is an Executable, as opposed to e.g. a *library*. This does - // not mean no ZIR is generated. - // // TODO: support tests for object file building, and library builds // and linking. This will require a rework to support multi-file // tests. -- 2.54.0 From 4d01385e147ae36ad96a1c28d2b6fdec69ce69c9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Jul 2020 03:48:20 +0000 Subject: [PATCH 195/295] fix liveness analysis and not correctly propagating link errors We still flush the ELF file even when there are compile errors. --- src-self-hosted/Module.zig | 12 +++++++----- src-self-hosted/link.zig | 23 ++++++++++++----------- src-self-hosted/liveness.zig | 29 +++++++++++++++++------------ src-self-hosted/main.zig | 5 ++++- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 776e63018ba3cb7ac77170ab99ff7d716023664b..6cd64e9235a708aa576fa5d077897220f732af6a 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -867,15 +867,16 @@ pub fn update(self: *Module) !void { try self.deleteDecl(decl); } + // This is needed before reading the error flags. + try self.bin_file.flush(); + self.link_error_flags = self.bin_file.error_flags; + std.log.debug(.module, "link_error_flags: {}\n", .{self.link_error_flags}); // If there are any errors, we anticipate the source files being loaded // to report error messages. Otherwise we unload all source files to save memory. - if (self.totalErrorCount() == 0) { - if (!self.keep_source_files_loaded) { - self.root_scope.unload(self.gpa); - } - try self.bin_file.flush(); + if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { + self.root_scope.unload(self.gpa); } } @@ -975,6 +976,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { // lifetime annotations in the ZIR. var decl_arena = decl.typed_value.most_recent.arena.?.promote(self.gpa); defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; + std.log.debug(.module, "analyze liveness of {}\n", .{decl.name}); try liveness.analyze(self.gpa, &decl_arena.allocator, payload.func.analysis.success); } diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index a0894fb1a105722bc0f6614b6a663bc4d097b83c..c297c3a1ce1becaa37a1ef750369474b0d6f6ba9 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -369,7 +369,7 @@ pub const ElfFile = struct { const file_size = self.options.program_code_size_hint; const p_align = 0x1000; const off = self.findFreeSpace(file_size, p_align); - //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); try self.program_headers.append(self.allocator, .{ .p_type = elf.PT_LOAD, .p_offset = off, @@ -390,7 +390,7 @@ pub const ElfFile = struct { // page align. const p_align = 0x1000; const off = self.findFreeSpace(file_size, p_align); - //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. // we'll need to re-use that function anyway, in case the GOT grows and overlaps something // else in virtual memory. @@ -412,7 +412,7 @@ pub const ElfFile = struct { assert(self.shstrtab.items.len == 0); try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0 const off = self.findFreeSpace(self.shstrtab.items.len, 1); - //std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); + std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); try self.sections.append(self.allocator, .{ .sh_name = try self.makeString(".shstrtab"), .sh_type = elf.SHT_STRTAB, @@ -470,7 +470,7 @@ pub const ElfFile = struct { const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); const file_size = self.options.symbol_count_hint * each_size; const off = self.findFreeSpace(file_size, min_align); - //std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); try self.sections.append(self.allocator, .{ .sh_name = try self.makeString(".symtab"), @@ -586,7 +586,7 @@ pub const ElfFile = struct { shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); } shstrtab_sect.sh_size = needed_size; - //std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); + std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); if (!self.shdr_table_dirty) { @@ -632,7 +632,7 @@ pub const ElfFile = struct { for (buf) |*shdr, i| { shdr.* = self.sections.items[i]; - //std.log.debug(.link, "writing section {}\n", .{shdr.*}); + std.log.debug(.link, "writing section {}\n", .{shdr.*}); if (foreign_endian) { bswapAllFields(elf.Elf64_Shdr, shdr); } @@ -643,6 +643,7 @@ pub const ElfFile = struct { self.shdr_table_dirty = false; } if (self.entry_addr == null and self.options.output_mode == .Exe) { + std.log.debug(.link, "no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; } else { self.error_flags.no_entry_point_found = false; @@ -956,10 +957,10 @@ pub const ElfFile = struct { try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); if (self.local_symbol_free_list.popOrNull()) |i| { - //std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name}); + std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name}); decl.link.local_sym_index = i; } else { - //std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); + std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -1027,11 +1028,11 @@ pub const ElfFile = struct { !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); if (need_realloc) { const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); - //std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); + std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); if (vaddr != local_sym.st_value) { local_sym.st_value = vaddr; - //std.log.debug(.link, " (writing new offset table entry)\n", .{}); + std.log.debug(.link, " (writing new offset table entry)\n", .{}); self.offset_table.items[decl.link.offset_table_index] = vaddr; try self.writeOffsetTableEntry(decl.link.offset_table_index); } @@ -1049,7 +1050,7 @@ pub const ElfFile = struct { const decl_name = mem.spanZ(decl.name); const name_str_index = try self.makeString(decl_name); const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); - //std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); + std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); errdefer self.freeTextBlock(&decl.link); local_sym.* = .{ diff --git a/src-self-hosted/liveness.zig b/src-self-hosted/liveness.zig index 2e0aad49c3ac4a33142099591cf0ca57e3a0631f..28eb2145c708efbf45723dd5ce908f294e1bb6e5 100644 --- a/src-self-hosted/liveness.zig +++ b/src-self-hosted/liveness.zig @@ -25,22 +25,25 @@ fn analyzeWithTable(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, while (i != 0) { i -= 1; const base = body.instructions[i]; + try analyzeInstGeneric(arena, table, base); + } +} - // Obtain the corresponding instruction type based on the tag type. - inline for (std.meta.declarations(ir.Inst)) |decl| { - switch (decl.data) { - .Type => |T| { - if (@hasDecl(T, "base_tag")) { - if (T.base_tag == base.tag) { - return analyzeInst(arena, table, T, @fieldParentPtr(T, "base", base)); - } +fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), base: *ir.Inst) error{OutOfMemory}!void { + // Obtain the corresponding instruction type based on the tag type. + inline for (std.meta.declarations(ir.Inst)) |decl| { + switch (decl.data) { + .Type => |T| { + if (@hasDecl(T, "base_tag")) { + if (T.base_tag == base.tag) { + return analyzeInst(arena, table, T, @fieldParentPtr(T, "base", base)); } - }, - else => continue, - } + } + }, + else => {}, } - unreachable; } + unreachable; } fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), comptime T: type, inst: *T) error{OutOfMemory}!void { @@ -131,4 +134,6 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void arg_index += 1; } } + + std.log.debug(.liveness, "analyze {}: 0b{b}\n", .{inst.base.tag, inst.base.deaths}); } diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 82edcc4eff7d43a54b0c98c612a1164f61061c6d..7a47fc5f7814c2e5b6992890a320ac9a07a40668 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -50,7 +50,10 @@ pub fn log( const scope_prefix = "(" ++ switch (scope) { // Uncomment to hide logs //.compiler, - .link => return, + .module, + .liveness, + .link, + => return, else => @tagName(scope), } ++ "): "; -- 2.54.0 From b55d0193e41311532e7b3603b9386863626afcd2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 7 Jul 2020 08:01:54 +0000 Subject: [PATCH 196/295] stage2: progress towards Block and CondBr codegen --- src-self-hosted/Module.zig | 19 ++++++------------- src-self-hosted/codegen.zig | 21 +++++++++++++++++++-- src-self-hosted/ir.zig | 14 ++++++++++++++ src-self-hosted/zir.zig | 16 ++++++++++++++++ 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 6cd64e9235a708aa576fa5d077897220f732af6a..d40afebb64aedda62d9c825363224cd485fb89c5 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2545,18 +2545,6 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr assert(child_block.instructions.items.len != 0); assert(child_block.instructions.items[child_block.instructions.items.len - 1].tag.isNoReturn()); - if (label.results.items.len <= 1) { - // No need to add the Block instruction; we can add the instructions to the parent block directly. - // Blocks are terminated with a noreturn instruction which we do not want to include. - const instrs = child_block.instructions.items; - try parent_block.instructions.appendSlice(self.gpa, instrs[0 .. instrs.len - 1]); - if (label.results.items.len == 1) { - return label.results.items[0]; - } else { - return self.constNoReturn(scope, inst.base.src); - } - } - // Need to set the type and emit the Block instruction. This allows machine code generation // to emit a jump instruction to after the block when it encounters the break. try parent_block.instructions.append(self.gpa, &block_inst.base); @@ -2579,7 +2567,10 @@ fn analyzeInstBreakVoid(self: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) if (block.label) |*label| { if (mem.eql(u8, label.name, label_name)) { try label.results.append(self.gpa, void_inst); - return self.constNoReturn(scope, inst.base.src); + const b = try self.requireRuntimeBlock(scope, inst.base.src); + return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.BreakVoid, .{ + .block = label.block_inst, + }); } } opt_block = block.parent; @@ -3366,6 +3357,8 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { if (instructions.len == 0) return Type.initTag(.noreturn); + if (instructions.len == 1) + return instructions[0].ty; return self.fail(scope, instructions[0].src, "TODO peer type resolution", .{}); } diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index aac1a53d6a0e643c3cd945cee1b242c48df68ed1..d1b32f4ed4a875a130fdf293d9b37cdf3d43dfc9 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -218,6 +218,11 @@ pub fn generateSymbol( } } +const InnerError = error { + OutOfMemory, + CodegenFail, +}; + const Function = struct { gpa: *Allocator, bin_file: *link.ElfFile, @@ -379,8 +384,12 @@ const Function = struct { } fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void { + return self.genBody(self.mod_fn.analysis.success, arch); + } + + fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void { const inst_table = &self.branch_stack.items[0].inst_table; - for (self.mod_fn.analysis.success.instructions) |inst| { + for (body.instructions) |inst| { const new_inst = try self.genFuncInst(inst, arch); try inst_table.putNoClobber(self.gpa, inst, new_inst); } @@ -394,6 +403,7 @@ const Function = struct { .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), .block => return self.genBlock(inst.cast(ir.Inst.Block).?, arch), .breakpoint => return self.genBreakpoint(inst.src, arch), + .breakvoid => return self.genBreakVoid(inst.cast(ir.Inst.BreakVoid).?, arch), .call => return self.genCall(inst.cast(ir.Inst.Call).?, arch), .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?, arch), .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?, arch), @@ -686,9 +696,16 @@ const Function = struct { } fn genBlock(self: *Function, inst: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { + // A block is nothing but a setup to be able to jump to the end. + try self.genBody(inst.args.body, arch); + return self.fail(inst.base.src, "TODO process jump relocs after block end", .{}); + } + + fn genBreakVoid(self: *Function, inst: *ir.Inst.BreakVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { switch (arch) { - else => return self.fail(inst.base.src, "TODO implement codegen Block for {}", .{self.target.cpu.arch}), + else => return self.fail(inst.base.src, "TODO implement breakvoid for {}", .{self.target.cpu.arch}), } + return .none; } fn genAsm(self: *Function, inst: *ir.Inst.Assembly, comptime arch: Target.Cpu.Arch) !MCValue { diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index c8acc14545ce21dfb0d022732192a610ee0cb0e0..81b617c8a2c359dad001b171221001f62bc551ae 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -35,6 +35,10 @@ pub const Inst = struct { return @truncate(u1, self.deaths << index) != 0; } + pub fn specialOperandDeaths(self: Inst) bool { + return (self.deaths & 0b1000_0000) != 0; + } + pub const Tag = enum { add, arg, @@ -42,6 +46,7 @@ pub const Inst = struct { bitcast, block, breakpoint, + breakvoid, call, cmp, condbr, @@ -74,6 +79,7 @@ pub const Inst = struct { .sub, => false, + .breakvoid, .condbr, .ret, .retvoid, @@ -159,6 +165,14 @@ pub const Inst = struct { args: void, }; + pub const BreakVoid = struct { + pub const base_tag = Tag.breakvoid; + base: Inst, + args: struct { + block: *Block, + }, + }; + pub const Call = struct { pub const base_tag = Tag.call; base: Inst, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 628773e09c57b80121f33c9d6797089648d99407..8cc6cdaf05f19c4050a591f693eab6022e4014ce 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -1608,6 +1608,22 @@ const EmitZIR = struct { break :blk &new_inst.base; }, .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), + .breakvoid => blk: { + const old_inst = inst.cast(ir.Inst.BreakVoid).?; + const new_block = inst_table.get(&old_inst.args.block.base).?; + const new_inst = try self.arena.allocator.create(Inst.BreakVoid); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.BreakVoid.base_tag, + }, + .positionals = .{ + .label = new_block.cast(Inst.Block).?.positionals.label, + }, + .kw_args = .{}, + }; + break :blk &new_inst.base; + }, .call => blk: { const old_inst = inst.cast(ir.Inst.Call).?; const new_inst = try self.arena.allocator.create(Inst.Call); -- 2.54.0 From b4c571301be7dd2174b2d067d643a2a093797e7e Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 14:55:44 -0400 Subject: [PATCH 197/295] Stage2: Refactor in preparation for C backend --- src-self-hosted/Module.zig | 60 +- src-self-hosted/codegen.zig | 4 +- src-self-hosted/link.zig | 2373 ++++++++++++++++++----------------- src-self-hosted/test.zig | 25 +- test/stage2/cbe.zig | 18 + test/stage2/test.zig | 1 + 6 files changed, 1309 insertions(+), 1172 deletions(-) create mode 100644 test/stage2/cbe.zig diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index f4d65ab7c0ed207bfa36c338f98d69f2817e0467..ac7bd0017099d8fdffbef054f4445c10cdc44aae 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -26,7 +26,7 @@ root_pkg: *Package, /// Module owns this resource. /// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`. root_scope: *Scope, -bin_file: link.ElfFile, +bin_file: *link.File, bin_file_dir: std.fs.Dir, bin_file_path: []const u8, /// It's rare for a decl to be exported, so we save memory by having a sparse map of @@ -45,7 +45,7 @@ export_owners: std.AutoHashMap(*Decl, []*Export), decl_table: DeclTable, optimize_mode: std.builtin.Mode, -link_error_flags: link.ElfFile.ErrorFlags = .{}, +link_error_flags: link.File.ErrorFlags = .{}, work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic), @@ -91,7 +91,7 @@ pub const Export = struct { /// Byte offset into the file that contains the export directive. src: usize, /// Represents the position of the export, if any, in the output file. - link: link.ElfFile.Export, + link: link.File.Elf.Export, /// The Decl that performs the export. Note that this is *not* the Decl being exported. owner_decl: *Decl, /// The Decl being exported. Note this is *not* the Decl performing the export. @@ -169,7 +169,7 @@ pub const Decl = struct { /// Represents the position of the code in the output file. /// This is populated regardless of semantic analysis and code generation. - link: link.ElfFile.TextBlock = link.ElfFile.TextBlock.empty, + link: link.File.Elf.TextBlock = link.File.Elf.TextBlock.empty, contents_hash: std.zig.SrcHash, @@ -722,6 +722,12 @@ pub const AllErrors = struct { } }; +pub const CStandard = enum { + C99, + GNU99, + C11, +}; + pub const InitOptions = struct { target: std.Target, root_pkg: *Package, @@ -732,17 +738,19 @@ pub const InitOptions = struct { object_format: ?std.builtin.ObjectFormat = null, optimize_mode: std.builtin.Mode = .Debug, keep_source_files_loaded: bool = false, + c_standard: ?CStandard = null, }; pub fn init(gpa: *Allocator, options: InitOptions) !Module { const bin_file_dir = options.bin_file_dir orelse std.fs.cwd(); - var bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{ + const bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{ .target = options.target, .output_mode = options.output_mode, .link_mode = options.link_mode orelse .Static, .object_format = options.object_format orelse options.target.getObjectFormat(), + .c_standard = options.c_standard, }); - errdefer bin_file.deinit(); + errdefer bin_file.*.deinit(); const root_scope = blk: { if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zig")) { @@ -793,6 +801,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { pub fn deinit(self: *Module) void { self.bin_file.deinit(); const allocator = self.allocator; + allocator.destroy(self.bin_file); self.deletion_set.deinit(allocator); self.work_queue.deinit(); @@ -840,7 +849,7 @@ fn freeExportList(allocator: *Allocator, export_list: []*Export) void { } pub fn target(self: Module) std.Target { - return self.bin_file.options.target; + return self.bin_file.options().target; } /// Detect changes to source files, perform semantic analysis, and update the output files. @@ -882,7 +891,7 @@ pub fn update(self: *Module) !void { try self.deleteDecl(decl); } - self.link_error_flags = self.bin_file.error_flags; + self.link_error_flags = self.bin_file.errorFlags(); // If there are any errors, we anticipate the source files being loaded // to report error messages. Otherwise we unload all source files to save memory. @@ -1898,8 +1907,9 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { self.decl_exports.removeAssertDiscard(exp.exported_decl); } } - - self.bin_file.deleteExport(exp.link); + if (self.bin_file.cast(link.File.Elf)) |elf| { + elf.deleteExport(exp.link); + } if (self.failed_exports.remove(exp)) |entry| { entry.value.destroy(self.allocator); } @@ -1961,7 +1971,7 @@ fn allocateNewDecl( .analysis = .unreferenced, .deletion_flag = false, .contents_hash = contents_hash, - .link = link.ElfFile.TextBlock.empty, + .link = link.File.Elf.TextBlock.empty, .generation = 0, }; return new_decl; @@ -2189,19 +2199,21 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const } try self.symbol_exports.putNoClobber(symbol_name, new_export); - self.bin_file.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1); - self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( - self.allocator, - src, - "unable to export: {}", - .{@errorName(err)}, - )); - new_export.status = .failed_retryable; - }, - }; + if (self.bin_file.cast(link.File.Elf)) |elf| { + elf.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1); + self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create( + self.allocator, + src, + "unable to export: {}", + .{@errorName(err)}, + )); + new_export.status = .failed_retryable; + }, + }; + } } fn addNewInstArgs( diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 8885ed282558632825f698ed8711d9b8a57a9912..46715b762b0c876f6fecbf0261af79f4eeb39624 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -21,7 +21,7 @@ pub const Result = union(enum) { }; pub fn generateSymbol( - bin_file: *link.ElfFile, + bin_file: *link.File.Elf, src: usize, typed_value: TypedValue, code: *std.ArrayList(u8), @@ -211,7 +211,7 @@ pub fn generateSymbol( } const Function = struct { - bin_file: *link.ElfFile, + bin_file: *link.File.Elf, target: *const std.Target, mod_fn: *const Module.Fn, code: *std.ArrayList(u8), diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index c615ad35fdaab745905eb7b0e5fab2155aff10ce..9e273a2b9e284b506c207f989c3a4ca586ffe789 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -21,6 +21,7 @@ pub const Options = struct { /// Used for calculating how much space to reserve for executable program code in case /// the binary file deos not already have such a section. program_code_size_hint: u64 = 256 * 1024, + c_standard: ?Module.CStandard = null, }; /// Attempts incremental linking, if the file already exists. @@ -32,13 +33,19 @@ pub fn openBinFilePath( dir: fs.Dir, sub_path: []const u8, options: Options, -) !ElfFile { +) !*File { const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) }); errdefer file.close(); - var bin_file = try openBinFile(allocator, file, options); - bin_file.owns_file_handle = true; - return bin_file; + if (options.c_standard) |cstd| { + return error.Unimplemented; + } else { + var bin_file = try allocator.create(File.Elf); + errdefer allocator.destroy(bin_file); + bin_file.* = try openBinFile(allocator, file, options); + bin_file.owns_file_handle = true; + return &bin_file.base; + } } /// Atomically overwrites the old file, if present. @@ -80,7 +87,7 @@ pub fn writeFilePath( /// Returns an error if `file` is not already open with +read +write +seek abilities. /// A malicious file is detected as incremental link failure and does not cause Illegal Behavior. /// This operation is not atomic. -pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile { +pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf { return openBinFileInner(allocator, file, options) catch |err| switch (err) { error.IncrFailed => { return createElfFile(allocator, file, options); @@ -89,514 +96,484 @@ pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !ElfF }; } -pub const ElfFile = struct { - allocator: *Allocator, - file: ?fs.File, - owns_file_handle: bool, - options: Options, - ptr_width: enum { p32, p64 }, +pub const File = struct { + tag: Tag, + pub fn cast(base: *File, comptime T: type) ?*T { + if (base.tag != T.base_tag) + return null; - /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. - /// Same order as in the file. - sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = std.ArrayListUnmanaged(elf.Elf64_Shdr){}, - shdr_table_offset: ?u64 = null, + return @fieldParentPtr(T, "base", base); + } - /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. - /// Same order as in the file. - program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = std.ArrayListUnmanaged(elf.Elf64_Phdr){}, - phdr_table_offset: ?u64 = null, - /// The index into the program headers of a PT_LOAD program header with Read and Execute flags - phdr_load_re_index: ?u16 = null, - /// The index into the program headers of the global offset table. - /// It needs PT_LOAD and Read flags. - phdr_got_index: ?u16 = null, - entry_addr: ?u64 = null, + pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { + try switch (base.tag) { + .Elf => @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path), + else => unreachable, + }; + } - shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, - shstrtab_index: ?u16 = null, + pub fn makeExecutable(base: *File) !void { + try switch (base.tag) { + .Elf => @fieldParentPtr(Elf, "base", base).makeExecutable(), + else => unreachable, + }; + } - text_section_index: ?u16 = null, - symtab_section_index: ?u16 = null, - got_section_index: ?u16 = null, + pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void { + try switch (base.tag) { + .Elf => @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), + else => unreachable, + }; + } - /// The same order as in the file. ELF requires global symbols to all be after the - /// local symbols, they cannot be mixed. So we must buffer all the global symbols and - /// write them at the end. These are only the local symbols. The length of this array - /// is the value used for sh_info in the .symtab section. - local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, - global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, + pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void { + try switch (base.tag) { + .Elf => @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), + else => unreachable, + }; + } - local_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, - global_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, - offset_table_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, + pub fn deinit(base: *File) void { + switch (base.tag) { + .Elf => @fieldParentPtr(Elf, "base", base).deinit(), + else => unreachable, + } + } - /// Same order as in the file. The value is the absolute vaddr value. - /// If the vaddr of the executable program header changes, the entire - /// offset table needs to be rewritten. - offset_table: std.ArrayListUnmanaged(u64) = std.ArrayListUnmanaged(u64){}, + pub fn flush(base: *File) !void { + try switch (base.tag) { + .Elf => @fieldParentPtr(Elf, "base", base).flush(), + else => unreachable, + }; + } - phdr_table_dirty: bool = false, - shdr_table_dirty: bool = false, - shstrtab_dirty: bool = false, - offset_table_count_dirty: bool = false, + pub fn freeDecl(base: *File, decl: *Module.Decl) void { + switch (base.tag) { + .Elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), + else => unreachable, + } + } - error_flags: ErrorFlags = ErrorFlags{}, + pub fn errorFlags(base: *File) ErrorFlags { + return switch (base.tag) { + .Elf => @fieldParentPtr(Elf, "base", base).error_flags, + else => unreachable, + }; + } - /// A list of text blocks that have surplus capacity. This list can have false - /// positives, as functions grow and shrink over time, only sometimes being added - /// or removed from the freelist. - /// - /// A text block has surplus capacity when its overcapacity value is greater than - /// minimum_text_block_size * alloc_num / alloc_den. That is, when it has so - /// much extra capacity, that we could fit a small new symbol in it, itself with - /// ideal_capacity or more. - /// - /// Ideal capacity is defined by size * alloc_num / alloc_den. - /// - /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that - /// overcapacity can be negative. A simple way to have negative overcapacity is to - /// allocate a fresh text block, which will have ideal capacity, and then grow it - /// by 1 byte. It will then have -1 overcapacity. - text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){}, - last_text_block: ?*TextBlock = null, + pub fn options(base: *File) Options { + return switch (base.tag) { + .Elf => @fieldParentPtr(Elf, "base", base).options, + else => unreachable, + }; + } - /// `alloc_num / alloc_den` is the factor of padding when allocating. - const alloc_num = 4; - const alloc_den = 3; - - /// In order for a slice of bytes to be considered eligible to keep metadata pointing at - /// it as a possible place to put new symbols, it must have enough room for this many bytes - /// (plus extra for reserved capacity). - const minimum_text_block_size = 64; - const min_text_capacity = minimum_text_block_size * alloc_num / alloc_den; + pub const Tag = enum { + Elf, + C, + }; pub const ErrorFlags = struct { no_entry_point_found: bool = false, }; + pub const Elf = struct { + pub const base_tag: Tag = .Elf; + base: File = File{ .tag = base_tag }, - pub const TextBlock = struct { - /// Each decl always gets a local symbol with the fully qualified name. - /// The vaddr and size are found here directly. - /// The file offset is found by computing the vaddr offset from the section vaddr - /// the symbol references, and adding that to the file offset of the section. - /// If this field is 0, it means the codegen size = 0 and there is no symbol or - /// offset table entry. - local_sym_index: u32, - /// This field is undefined for symbols with size = 0. - offset_table_index: u32, - /// Points to the previous and next neighbors, based on the `text_offset`. - /// This can be used to find, for example, the capacity of this `TextBlock`. - prev: ?*TextBlock, - next: ?*TextBlock, - - pub const empty = TextBlock{ - .local_sym_index = 0, - .offset_table_index = undefined, - .prev = null, - .next = null, - }; - - /// Returns how much room there is to grow in virtual address space. - /// File offset relocation happens transparently, so it is not included in - /// this calculation. - fn capacity(self: TextBlock, elf_file: ElfFile) u64 { - const self_sym = elf_file.local_symbols.items[self.local_sym_index]; - if (self.next) |next| { + allocator: *Allocator, + file: ?fs.File, + owns_file_handle: bool, + options: Options, + ptr_width: enum { p32, p64 }, + + /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. + /// Same order as in the file. + sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = std.ArrayListUnmanaged(elf.Elf64_Shdr){}, + shdr_table_offset: ?u64 = null, + + /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. + /// Same order as in the file. + program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = std.ArrayListUnmanaged(elf.Elf64_Phdr){}, + phdr_table_offset: ?u64 = null, + /// The index into the program headers of a PT_LOAD program header with Read and Execute flags + phdr_load_re_index: ?u16 = null, + /// The index into the program headers of the global offset table. + /// It needs PT_LOAD and Read flags. + phdr_got_index: ?u16 = null, + entry_addr: ?u64 = null, + + shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){}, + shstrtab_index: ?u16 = null, + + text_section_index: ?u16 = null, + symtab_section_index: ?u16 = null, + got_section_index: ?u16 = null, + + /// The same order as in the file. ELF requires global symbols to all be after the + /// local symbols, they cannot be mixed. So we must buffer all the global symbols and + /// write them at the end. These are only the local symbols. The length of this array + /// is the value used for sh_info in the .symtab section. + local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, + global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){}, + + local_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, + global_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, + offset_table_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){}, + + /// Same order as in the file. The value is the absolute vaddr value. + /// If the vaddr of the executable program header changes, the entire + /// offset table needs to be rewritten. + offset_table: std.ArrayListUnmanaged(u64) = std.ArrayListUnmanaged(u64){}, + + phdr_table_dirty: bool = false, + shdr_table_dirty: bool = false, + shstrtab_dirty: bool = false, + offset_table_count_dirty: bool = false, + + error_flags: ErrorFlags = ErrorFlags{}, + + /// A list of text blocks that have surplus capacity. This list can have false + /// positives, as functions grow and shrink over time, only sometimes being added + /// or removed from the freelist. + /// + /// A text block has surplus capacity when its overcapacity value is greater than + /// minimum_text_block_size * alloc_num / alloc_den. That is, when it has so + /// much extra capacity, that we could fit a small new symbol in it, itself with + /// ideal_capacity or more. + /// + /// Ideal capacity is defined by size * alloc_num / alloc_den. + /// + /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that + /// overcapacity can be negative. A simple way to have negative overcapacity is to + /// allocate a fresh text block, which will have ideal capacity, and then grow it + /// by 1 byte. It will then have -1 overcapacity. + text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){}, + last_text_block: ?*TextBlock = null, + + /// `alloc_num / alloc_den` is the factor of padding when allocating. + const alloc_num = 4; + const alloc_den = 3; + + /// In order for a slice of bytes to be considered eligible to keep metadata pointing at + /// it as a possible place to put new symbols, it must have enough room for this many bytes + /// (plus extra for reserved capacity). + const minimum_text_block_size = 64; + const min_text_capacity = minimum_text_block_size * alloc_num / alloc_den; + + pub const TextBlock = struct { + /// Each decl always gets a local symbol with the fully qualified name. + /// The vaddr and size are found here directly. + /// The file offset is found by computing the vaddr offset from the section vaddr + /// the symbol references, and adding that to the file offset of the section. + /// If this field is 0, it means the codegen size = 0 and there is no symbol or + /// offset table entry. + local_sym_index: u32, + /// This field is undefined for symbols with size = 0. + offset_table_index: u32, + /// Points to the previous and next neighbors, based on the `text_offset`. + /// This can be used to find, for example, the capacity of this `TextBlock`. + prev: ?*TextBlock, + next: ?*TextBlock, + + pub const empty = TextBlock{ + .local_sym_index = 0, + .offset_table_index = undefined, + .prev = null, + .next = null, + }; + + /// Returns how much room there is to grow in virtual address space. + /// File offset relocation happens transparently, so it is not included in + /// this calculation. + fn capacity(self: TextBlock, elf_file: File.Elf) u64 { + const self_sym = elf_file.local_symbols.items[self.local_sym_index]; + if (self.next) |next| { + const next_sym = elf_file.local_symbols.items[next.local_sym_index]; + return next_sym.st_value - self_sym.st_value; + } else { + // We are the last block. The capacity is limited only by virtual address space. + return std.math.maxInt(u32) - self_sym.st_value; + } + } + + fn freeListEligible(self: TextBlock, elf_file: File.Elf) bool { + // No need to keep a free list node for the last block. + const next = self.next orelse return false; + const self_sym = elf_file.local_symbols.items[self.local_sym_index]; const next_sym = elf_file.local_symbols.items[next.local_sym_index]; - return next_sym.st_value - self_sym.st_value; - } else { - // We are the last block. The capacity is limited only by virtual address space. - return std.math.maxInt(u32) - self_sym.st_value; + const cap = next_sym.st_value - self_sym.st_value; + const ideal_cap = self_sym.st_size * alloc_num / alloc_den; + if (cap <= ideal_cap) return false; + const surplus = cap - ideal_cap; + return surplus >= min_text_capacity; } - } - - fn freeListEligible(self: TextBlock, elf_file: ElfFile) bool { - // No need to keep a free list node for the last block. - const next = self.next orelse return false; - const self_sym = elf_file.local_symbols.items[self.local_sym_index]; - const next_sym = elf_file.local_symbols.items[next.local_sym_index]; - const cap = next_sym.st_value - self_sym.st_value; - const ideal_cap = self_sym.st_size * alloc_num / alloc_den; - if (cap <= ideal_cap) return false; - const surplus = cap - ideal_cap; - return surplus >= min_text_capacity; - } - }; + }; - pub const Export = struct { - sym_index: ?u32 = null, - }; + pub const Export = struct { + sym_index: ?u32 = null, + }; - pub fn deinit(self: *ElfFile) void { - self.sections.deinit(self.allocator); - self.program_headers.deinit(self.allocator); - self.shstrtab.deinit(self.allocator); - self.local_symbols.deinit(self.allocator); - self.global_symbols.deinit(self.allocator); - self.global_symbol_free_list.deinit(self.allocator); - self.local_symbol_free_list.deinit(self.allocator); - self.offset_table_free_list.deinit(self.allocator); - self.text_block_free_list.deinit(self.allocator); - self.offset_table.deinit(self.allocator); - if (self.owns_file_handle) { - if (self.file) |f| f.close(); + pub fn deinit(self: *File.Elf) void { + self.sections.deinit(self.allocator); + self.program_headers.deinit(self.allocator); + self.shstrtab.deinit(self.allocator); + self.local_symbols.deinit(self.allocator); + self.global_symbols.deinit(self.allocator); + self.global_symbol_free_list.deinit(self.allocator); + self.local_symbol_free_list.deinit(self.allocator); + self.offset_table_free_list.deinit(self.allocator); + self.text_block_free_list.deinit(self.allocator); + self.offset_table.deinit(self.allocator); + if (self.owns_file_handle) { + if (self.file) |f| f.close(); + } } - } - pub fn makeExecutable(self: *ElfFile) !void { - assert(self.owns_file_handle); - if (self.file) |f| { - f.close(); - self.file = null; + pub fn makeExecutable(self: *File.Elf) !void { + assert(self.owns_file_handle); + if (self.file) |f| { + f.close(); + self.file = null; + } } - } - pub fn makeWritable(self: *ElfFile, dir: fs.Dir, sub_path: []const u8) !void { - assert(self.owns_file_handle); - if (self.file != null) return; - self.file = try dir.createFile(sub_path, .{ - .truncate = false, - .read = true, - .mode = determineMode(self.options), - }); - } + pub fn makeWritable(self: *File.Elf, dir: fs.Dir, sub_path: []const u8) !void { + assert(self.owns_file_handle); + if (self.file != null) return; + self.file = try dir.createFile(sub_path, .{ + .truncate = false, + .read = true, + .mode = determineMode(self.options), + }); + } - /// Returns end pos of collision, if any. - fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 { - const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; - const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); - if (start < ehdr_size) - return ehdr_size; + /// Returns end pos of collision, if any. + fn detectAllocCollision(self: *File.Elf, start: u64, size: u64) ?u64 { + const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; + const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); + if (start < ehdr_size) + return ehdr_size; - const end = start + satMul(size, alloc_num) / alloc_den; + const end = start + satMul(size, alloc_num) / alloc_den; - if (self.shdr_table_offset) |off| { - const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); - const tight_size = self.sections.items.len * shdr_size; - const increased_size = satMul(tight_size, alloc_num) / alloc_den; - const test_end = off + increased_size; - if (end > off and start < test_end) { - return test_end; + if (self.shdr_table_offset) |off| { + const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr); + const tight_size = self.sections.items.len * shdr_size; + const increased_size = satMul(tight_size, alloc_num) / alloc_den; + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } } - } - if (self.phdr_table_offset) |off| { - const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr); - const tight_size = self.sections.items.len * phdr_size; - const increased_size = satMul(tight_size, alloc_num) / alloc_den; - const test_end = off + increased_size; - if (end > off and start < test_end) { - return test_end; + if (self.phdr_table_offset) |off| { + const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr); + const tight_size = self.sections.items.len * phdr_size; + const increased_size = satMul(tight_size, alloc_num) / alloc_den; + const test_end = off + increased_size; + if (end > off and start < test_end) { + return test_end; + } } - } - for (self.sections.items) |section| { - const increased_size = satMul(section.sh_size, alloc_num) / alloc_den; - const test_end = section.sh_offset + increased_size; - if (end > section.sh_offset and start < test_end) { - return test_end; + for (self.sections.items) |section| { + const increased_size = satMul(section.sh_size, alloc_num) / alloc_den; + const test_end = section.sh_offset + increased_size; + if (end > section.sh_offset and start < test_end) { + return test_end; + } } - } - for (self.program_headers.items) |program_header| { - const increased_size = satMul(program_header.p_filesz, alloc_num) / alloc_den; - const test_end = program_header.p_offset + increased_size; - if (end > program_header.p_offset and start < test_end) { - return test_end; + for (self.program_headers.items) |program_header| { + const increased_size = satMul(program_header.p_filesz, alloc_num) / alloc_den; + const test_end = program_header.p_offset + increased_size; + if (end > program_header.p_offset and start < test_end) { + return test_end; + } } + return null; } - return null; - } - - fn allocatedSize(self: *ElfFile, start: u64) u64 { - var min_pos: u64 = std.math.maxInt(u64); - if (self.shdr_table_offset) |off| { - if (off > start and off < min_pos) min_pos = off; - } - if (self.phdr_table_offset) |off| { - if (off > start and off < min_pos) min_pos = off; - } - for (self.sections.items) |section| { - if (section.sh_offset <= start) continue; - if (section.sh_offset < min_pos) min_pos = section.sh_offset; - } - for (self.program_headers.items) |program_header| { - if (program_header.p_offset <= start) continue; - if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; - } - return min_pos - start; - } - - fn findFreeSpace(self: *ElfFile, object_size: u64, min_alignment: u16) u64 { - var start: u64 = 0; - while (self.detectAllocCollision(start, object_size)) |item_end| { - start = mem.alignForwardGeneric(u64, item_end, min_alignment); - } - return start; - } - fn makeString(self: *ElfFile, bytes: []const u8) !u32 { - try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1); - const result = self.shstrtab.items.len; - self.shstrtab.appendSliceAssumeCapacity(bytes); - self.shstrtab.appendAssumeCapacity(0); - return @intCast(u32, result); - } - - fn getString(self: *ElfFile, str_off: u32) []const u8 { - assert(str_off < self.shstrtab.items.len); - return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off)); - } - - fn updateString(self: *ElfFile, old_str_off: u32, new_name: []const u8) !u32 { - const existing_name = self.getString(old_str_off); - if (mem.eql(u8, existing_name, new_name)) { - return old_str_off; + fn allocatedSize(self: *File.Elf, start: u64) u64 { + var min_pos: u64 = std.math.maxInt(u64); + if (self.shdr_table_offset) |off| { + if (off > start and off < min_pos) min_pos = off; + } + if (self.phdr_table_offset) |off| { + if (off > start and off < min_pos) min_pos = off; + } + for (self.sections.items) |section| { + if (section.sh_offset <= start) continue; + if (section.sh_offset < min_pos) min_pos = section.sh_offset; + } + for (self.program_headers.items) |program_header| { + if (program_header.p_offset <= start) continue; + if (program_header.p_offset < min_pos) min_pos = program_header.p_offset; + } + return min_pos - start; } - return self.makeString(new_name); - } - pub fn populateMissingMetadata(self: *ElfFile) !void { - const small_ptr = switch (self.ptr_width) { - .p32 => true, - .p64 => false, - }; - const ptr_size: u8 = switch (self.ptr_width) { - .p32 => 4, - .p64 => 8, - }; - if (self.phdr_load_re_index == null) { - self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len); - const file_size = self.options.program_code_size_hint; - const p_align = 0x1000; - const off = self.findFreeSpace(file_size, p_align); - //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - try self.program_headers.append(self.allocator, .{ - .p_type = elf.PT_LOAD, - .p_offset = off, - .p_filesz = file_size, - .p_vaddr = default_entry_addr, - .p_paddr = default_entry_addr, - .p_memsz = file_size, - .p_align = p_align, - .p_flags = elf.PF_X | elf.PF_R, - }); - self.entry_addr = null; - self.phdr_table_dirty = true; - } - if (self.phdr_got_index == null) { - self.phdr_got_index = @intCast(u16, self.program_headers.items.len); - const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint; - // We really only need ptr alignment but since we are using PROGBITS, linux requires - // page align. - const p_align = 0x1000; - const off = self.findFreeSpace(file_size, p_align); - //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. - // we'll need to re-use that function anyway, in case the GOT grows and overlaps something - // else in virtual memory. - const default_got_addr = 0x4000000; - try self.program_headers.append(self.allocator, .{ - .p_type = elf.PT_LOAD, - .p_offset = off, - .p_filesz = file_size, - .p_vaddr = default_got_addr, - .p_paddr = default_got_addr, - .p_memsz = file_size, - .p_align = p_align, - .p_flags = elf.PF_R, - }); - self.phdr_table_dirty = true; - } - if (self.shstrtab_index == null) { - self.shstrtab_index = @intCast(u16, self.sections.items.len); - assert(self.shstrtab.items.len == 0); - try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0 - const off = self.findFreeSpace(self.shstrtab.items.len, 1); - //std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); - try self.sections.append(self.allocator, .{ - .sh_name = try self.makeString(".shstrtab"), - .sh_type = elf.SHT_STRTAB, - .sh_flags = 0, - .sh_addr = 0, - .sh_offset = off, - .sh_size = self.shstrtab.items.len, - .sh_link = 0, - .sh_info = 0, - .sh_addralign = 1, - .sh_entsize = 0, - }); - self.shstrtab_dirty = true; - self.shdr_table_dirty = true; + fn findFreeSpace(self: *File.Elf, object_size: u64, min_alignment: u16) u64 { + var start: u64 = 0; + while (self.detectAllocCollision(start, object_size)) |item_end| { + start = mem.alignForwardGeneric(u64, item_end, min_alignment); + } + return start; } - if (self.text_section_index == null) { - self.text_section_index = @intCast(u16, self.sections.items.len); - const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; - try self.sections.append(self.allocator, .{ - .sh_name = try self.makeString(".text"), - .sh_type = elf.SHT_PROGBITS, - .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, - .sh_addr = phdr.p_vaddr, - .sh_offset = phdr.p_offset, - .sh_size = phdr.p_filesz, - .sh_link = 0, - .sh_info = 0, - .sh_addralign = phdr.p_align, - .sh_entsize = 0, - }); - self.shdr_table_dirty = true; + fn makeString(self: *File.Elf, bytes: []const u8) !u32 { + try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1); + const result = self.shstrtab.items.len; + self.shstrtab.appendSliceAssumeCapacity(bytes); + self.shstrtab.appendAssumeCapacity(0); + return @intCast(u32, result); } - if (self.got_section_index == null) { - self.got_section_index = @intCast(u16, self.sections.items.len); - const phdr = &self.program_headers.items[self.phdr_got_index.?]; - try self.sections.append(self.allocator, .{ - .sh_name = try self.makeString(".got"), - .sh_type = elf.SHT_PROGBITS, - .sh_flags = elf.SHF_ALLOC, - .sh_addr = phdr.p_vaddr, - .sh_offset = phdr.p_offset, - .sh_size = phdr.p_filesz, - .sh_link = 0, - .sh_info = 0, - .sh_addralign = phdr.p_align, - .sh_entsize = 0, - }); - self.shdr_table_dirty = true; + fn getString(self: *File.Elf, str_off: u32) []const u8 { + assert(str_off < self.shstrtab.items.len); + return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off)); } - if (self.symtab_section_index == null) { - self.symtab_section_index = @intCast(u16, self.sections.items.len); - const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); - const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); - const file_size = self.options.symbol_count_hint * each_size; - const off = self.findFreeSpace(file_size, min_align); - //std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - try self.sections.append(self.allocator, .{ - .sh_name = try self.makeString(".symtab"), - .sh_type = elf.SHT_SYMTAB, - .sh_flags = 0, - .sh_addr = 0, - .sh_offset = off, - .sh_size = file_size, - // The section header index of the associated string table. - .sh_link = self.shstrtab_index.?, - .sh_info = @intCast(u32, self.local_symbols.items.len), - .sh_addralign = min_align, - .sh_entsize = each_size, - }); - self.shdr_table_dirty = true; - try self.writeSymbol(0); - } - const shsize: u64 = switch (self.ptr_width) { - .p32 => @sizeOf(elf.Elf32_Shdr), - .p64 => @sizeOf(elf.Elf64_Shdr), - }; - const shalign: u16 = switch (self.ptr_width) { - .p32 => @alignOf(elf.Elf32_Shdr), - .p64 => @alignOf(elf.Elf64_Shdr), - }; - if (self.shdr_table_offset == null) { - self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign); - self.shdr_table_dirty = true; - } - const phsize: u64 = switch (self.ptr_width) { - .p32 => @sizeOf(elf.Elf32_Phdr), - .p64 => @sizeOf(elf.Elf64_Phdr), - }; - const phalign: u16 = switch (self.ptr_width) { - .p32 => @alignOf(elf.Elf32_Phdr), - .p64 => @alignOf(elf.Elf64_Phdr), - }; - if (self.phdr_table_offset == null) { - self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign); - self.phdr_table_dirty = true; - } - { - // Iterate over symbols, populating free_list and last_text_block. - if (self.local_symbols.items.len != 1) { - @panic("TODO implement setting up free_list and last_text_block from existing ELF file"); + fn updateString(self: *File.Elf, old_str_off: u32, new_name: []const u8) !u32 { + const existing_name = self.getString(old_str_off); + if (mem.eql(u8, existing_name, new_name)) { + return old_str_off; } - // We are starting with an empty file. The default values are correct, null and empty list. + return self.makeString(new_name); } - } - - /// Commit pending changes and write headers. - pub fn flush(self: *ElfFile) !void { - const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); - - // Unfortunately these have to be buffered and done at the end because ELF does not allow - // mixing local and global symbols within a symbol table. - try self.writeAllGlobalSymbols(); - if (self.phdr_table_dirty) { - const phsize: u64 = switch (self.ptr_width) { - .p32 => @sizeOf(elf.Elf32_Phdr), - .p64 => @sizeOf(elf.Elf64_Phdr), + pub fn populateMissingMetadata(self: *File.Elf) !void { + const small_ptr = switch (self.ptr_width) { + .p32 => true, + .p64 => false, }; - const phalign: u16 = switch (self.ptr_width) { - .p32 => @alignOf(elf.Elf32_Phdr), - .p64 => @alignOf(elf.Elf64_Phdr), + const ptr_size: u8 = switch (self.ptr_width) { + .p32 => 4, + .p64 => 8, }; - const allocated_size = self.allocatedSize(self.phdr_table_offset.?); - const needed_size = self.program_headers.items.len * phsize; - - if (needed_size > allocated_size) { - self.phdr_table_offset = null; // free the space - self.phdr_table_offset = self.findFreeSpace(needed_size, phalign); + if (self.phdr_load_re_index == null) { + self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len); + const file_size = self.options.program_code_size_hint; + const p_align = 0x1000; + const off = self.findFreeSpace(file_size, p_align); + //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + try self.program_headers.append(self.allocator, .{ + .p_type = elf.PT_LOAD, + .p_offset = off, + .p_filesz = file_size, + .p_vaddr = default_entry_addr, + .p_paddr = default_entry_addr, + .p_memsz = file_size, + .p_align = p_align, + .p_flags = elf.PF_X | elf.PF_R, + }); + self.entry_addr = null; + self.phdr_table_dirty = true; } - - switch (self.ptr_width) { - .p32 => { - const buf = try self.allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len); - defer self.allocator.free(buf); - - for (buf) |*phdr, i| { - phdr.* = progHeaderTo32(self.program_headers.items[i]); - if (foreign_endian) { - bswapAllFields(elf.Elf32_Phdr, phdr); - } - } - try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); - }, - .p64 => { - const buf = try self.allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len); - defer self.allocator.free(buf); - - for (buf) |*phdr, i| { - phdr.* = self.program_headers.items[i]; - if (foreign_endian) { - bswapAllFields(elf.Elf64_Phdr, phdr); - } - } - try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); - }, + if (self.phdr_got_index == null) { + self.phdr_got_index = @intCast(u16, self.program_headers.items.len); + const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint; + // We really only need ptr alignment but since we are using PROGBITS, linux requires + // page align. + const p_align = 0x1000; + const off = self.findFreeSpace(file_size, p_align); + //std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. + // we'll need to re-use that function anyway, in case the GOT grows and overlaps something + // else in virtual memory. + const default_got_addr = 0x4000000; + try self.program_headers.append(self.allocator, .{ + .p_type = elf.PT_LOAD, + .p_offset = off, + .p_filesz = file_size, + .p_vaddr = default_got_addr, + .p_paddr = default_got_addr, + .p_memsz = file_size, + .p_align = p_align, + .p_flags = elf.PF_R, + }); + self.phdr_table_dirty = true; } - self.phdr_table_dirty = false; - } + if (self.shstrtab_index == null) { + self.shstrtab_index = @intCast(u16, self.sections.items.len); + assert(self.shstrtab.items.len == 0); + try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0 + const off = self.findFreeSpace(self.shstrtab.items.len, 1); + //std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len }); + try self.sections.append(self.allocator, .{ + .sh_name = try self.makeString(".shstrtab"), + .sh_type = elf.SHT_STRTAB, + .sh_flags = 0, + .sh_addr = 0, + .sh_offset = off, + .sh_size = self.shstrtab.items.len, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = 1, + .sh_entsize = 0, + }); + self.shstrtab_dirty = true; + self.shdr_table_dirty = true; + } + if (self.text_section_index == null) { + self.text_section_index = @intCast(u16, self.sections.items.len); + const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; - { - const shstrtab_sect = &self.sections.items[self.shstrtab_index.?]; - if (self.shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) { - const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset); - const needed_size = self.shstrtab.items.len; + try self.sections.append(self.allocator, .{ + .sh_name = try self.makeString(".text"), + .sh_type = elf.SHT_PROGBITS, + .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR, + .sh_addr = phdr.p_vaddr, + .sh_offset = phdr.p_offset, + .sh_size = phdr.p_filesz, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = phdr.p_align, + .sh_entsize = 0, + }); + self.shdr_table_dirty = true; + } + if (self.got_section_index == null) { + self.got_section_index = @intCast(u16, self.sections.items.len); + const phdr = &self.program_headers.items[self.phdr_got_index.?]; - if (needed_size > allocated_size) { - shstrtab_sect.sh_size = 0; // free the space - shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); - } - shstrtab_sect.sh_size = needed_size; - //std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); + try self.sections.append(self.allocator, .{ + .sh_name = try self.makeString(".got"), + .sh_type = elf.SHT_PROGBITS, + .sh_flags = elf.SHF_ALLOC, + .sh_addr = phdr.p_vaddr, + .sh_offset = phdr.p_offset, + .sh_size = phdr.p_filesz, + .sh_link = 0, + .sh_info = 0, + .sh_addralign = phdr.p_align, + .sh_entsize = 0, + }); + self.shdr_table_dirty = true; + } + if (self.symtab_section_index == null) { + self.symtab_section_index = @intCast(u16, self.sections.items.len); + const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); + const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); + const file_size = self.options.symbol_count_hint * each_size; + const off = self.findFreeSpace(file_size, min_align); + //std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); - try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); - if (!self.shdr_table_dirty) { - // Then it won't get written with the others and we need to do it. - try self.writeSectHeader(self.shstrtab_index.?); - } - self.shstrtab_dirty = false; + try self.sections.append(self.allocator, .{ + .sh_name = try self.makeString(".symtab"), + .sh_type = elf.SHT_SYMTAB, + .sh_flags = 0, + .sh_addr = 0, + .sh_offset = off, + .sh_size = file_size, + // The section header index of the associated string table. + .sh_link = self.shstrtab_index.?, + .sh_info = @intCast(u32, self.local_symbols.items.len), + .sh_addralign = min_align, + .sh_entsize = each_size, + }); + self.shdr_table_dirty = true; + try self.writeSymbol(0); } - } - if (self.shdr_table_dirty) { const shsize: u64 = switch (self.ptr_width) { .p32 => @sizeOf(elf.Elf32_Shdr), .p64 => @sizeOf(elf.Elf64_Shdr), @@ -605,756 +582,866 @@ pub const ElfFile = struct { .p32 => @alignOf(elf.Elf32_Shdr), .p64 => @alignOf(elf.Elf64_Shdr), }; - const allocated_size = self.allocatedSize(self.shdr_table_offset.?); - const needed_size = self.sections.items.len * shsize; + if (self.shdr_table_offset == null) { + self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign); + self.shdr_table_dirty = true; + } + const phsize: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Phdr), + .p64 => @sizeOf(elf.Elf64_Phdr), + }; + const phalign: u16 = switch (self.ptr_width) { + .p32 => @alignOf(elf.Elf32_Phdr), + .p64 => @alignOf(elf.Elf64_Phdr), + }; + if (self.phdr_table_offset == null) { + self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign); + self.phdr_table_dirty = true; + } + { + // Iterate over symbols, populating free_list and last_text_block. + if (self.local_symbols.items.len != 1) { + @panic("TODO implement setting up free_list and last_text_block from existing ELF file"); + } + // We are starting with an empty file. The default values are correct, null and empty list. + } + } + + /// Commit pending changes and write headers. + pub fn flush(self: *File.Elf) !void { + const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); + + // Unfortunately these have to be buffered and done at the end because ELF does not allow + // mixing local and global symbols within a symbol table. + try self.writeAllGlobalSymbols(); + + if (self.phdr_table_dirty) { + const phsize: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Phdr), + .p64 => @sizeOf(elf.Elf64_Phdr), + }; + const phalign: u16 = switch (self.ptr_width) { + .p32 => @alignOf(elf.Elf32_Phdr), + .p64 => @alignOf(elf.Elf64_Phdr), + }; + const allocated_size = self.allocatedSize(self.phdr_table_offset.?); + const needed_size = self.program_headers.items.len * phsize; + + if (needed_size > allocated_size) { + self.phdr_table_offset = null; // free the space + self.phdr_table_offset = self.findFreeSpace(needed_size, phalign); + } + + switch (self.ptr_width) { + .p32 => { + const buf = try self.allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len); + defer self.allocator.free(buf); - if (needed_size > allocated_size) { - self.shdr_table_offset = null; // free the space - self.shdr_table_offset = self.findFreeSpace(needed_size, shalign); + for (buf) |*phdr, i| { + phdr.* = progHeaderTo32(self.program_headers.items[i]); + if (foreign_endian) { + bswapAllFields(elf.Elf32_Phdr, phdr); + } + } + try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); + }, + .p64 => { + const buf = try self.allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len); + defer self.allocator.free(buf); + + for (buf) |*phdr, i| { + phdr.* = self.program_headers.items[i]; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Phdr, phdr); + } + } + try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); + }, + } + self.phdr_table_dirty = false; + } + + { + const shstrtab_sect = &self.sections.items[self.shstrtab_index.?]; + if (self.shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) { + const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset); + const needed_size = self.shstrtab.items.len; + + if (needed_size > allocated_size) { + shstrtab_sect.sh_size = 0; // free the space + shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); + } + shstrtab_sect.sh_size = needed_size; + //std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size }); + + try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset); + if (!self.shdr_table_dirty) { + // Then it won't get written with the others and we need to do it. + try self.writeSectHeader(self.shstrtab_index.?); + } + self.shstrtab_dirty = false; + } } + if (self.shdr_table_dirty) { + const shsize: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Shdr), + .p64 => @sizeOf(elf.Elf64_Shdr), + }; + const shalign: u16 = switch (self.ptr_width) { + .p32 => @alignOf(elf.Elf32_Shdr), + .p64 => @alignOf(elf.Elf64_Shdr), + }; + const allocated_size = self.allocatedSize(self.shdr_table_offset.?); + const needed_size = self.sections.items.len * shsize; + + if (needed_size > allocated_size) { + self.shdr_table_offset = null; // free the space + self.shdr_table_offset = self.findFreeSpace(needed_size, shalign); + } + + switch (self.ptr_width) { + .p32 => { + const buf = try self.allocator.alloc(elf.Elf32_Shdr, self.sections.items.len); + defer self.allocator.free(buf); + + for (buf) |*shdr, i| { + shdr.* = sectHeaderTo32(self.sections.items[i]); + if (foreign_endian) { + bswapAllFields(elf.Elf32_Shdr, shdr); + } + } + try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); + }, + .p64 => { + const buf = try self.allocator.alloc(elf.Elf64_Shdr, self.sections.items.len); + defer self.allocator.free(buf); + + for (buf) |*shdr, i| { + shdr.* = self.sections.items[i]; + //std.log.debug(.link, "writing section {}\n", .{shdr.*}); + if (foreign_endian) { + bswapAllFields(elf.Elf64_Shdr, shdr); + } + } + try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); + }, + } + self.shdr_table_dirty = false; + } + if (self.entry_addr == null and self.options.output_mode == .Exe) { + self.error_flags.no_entry_point_found = true; + } else { + self.error_flags.no_entry_point_found = false; + try self.writeElfHeader(); + } + + // The point of flush() is to commit changes, so nothing should be dirty after this. + assert(!self.phdr_table_dirty); + assert(!self.shdr_table_dirty); + assert(!self.shstrtab_dirty); + assert(!self.offset_table_count_dirty); + const syms_sect = &self.sections.items[self.symtab_section_index.?]; + assert(syms_sect.sh_info == self.local_symbols.items.len); + } + + fn writeElfHeader(self: *File.Elf) !void { + var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; + + var index: usize = 0; + hdr_buf[0..4].* = "\x7fELF".*; + index += 4; + + hdr_buf[index] = switch (self.ptr_width) { + .p32 => elf.ELFCLASS32, + .p64 => elf.ELFCLASS64, + }; + index += 1; + + const endian = self.options.target.cpu.arch.endian(); + hdr_buf[index] = switch (endian) { + .Little => elf.ELFDATA2LSB, + .Big => elf.ELFDATA2MSB, + }; + index += 1; + + hdr_buf[index] = 1; // ELF version + index += 1; + + // OS ABI, often set to 0 regardless of target platform + // ABI Version, possibly used by glibc but not by static executables + // padding + mem.set(u8, hdr_buf[index..][0..9], 0); + index += 9; + + assert(index == 16); + + const elf_type = switch (self.options.output_mode) { + .Exe => elf.ET.EXEC, + .Obj => elf.ET.REL, + .Lib => switch (self.options.link_mode) { + .Static => elf.ET.REL, + .Dynamic => elf.ET.DYN, + }, + }; + mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); + index += 2; + + const machine = self.options.target.cpu.arch.toElfMachine(); + mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); + index += 2; + + // ELF Version, again + mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian); + index += 4; + + const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?; switch (self.ptr_width) { .p32 => { - const buf = try self.allocator.alloc(elf.Elf32_Shdr, self.sections.items.len); - defer self.allocator.free(buf); + mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian); + index += 4; - for (buf) |*shdr, i| { - shdr.* = sectHeaderTo32(self.sections.items[i]); - if (foreign_endian) { - bswapAllFields(elf.Elf32_Shdr, shdr); - } - } - try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); + // e_phoff + mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.phdr_table_offset.?), endian); + index += 4; + + // e_shoff + mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.shdr_table_offset.?), endian); + index += 4; }, .p64 => { - const buf = try self.allocator.alloc(elf.Elf64_Shdr, self.sections.items.len); - defer self.allocator.free(buf); + // e_entry + mem.writeInt(u64, hdr_buf[index..][0..8], e_entry, endian); + index += 8; - for (buf) |*shdr, i| { - shdr.* = self.sections.items[i]; - //std.log.debug(.link, "writing section {}\n", .{shdr.*}); - if (foreign_endian) { - bswapAllFields(elf.Elf64_Shdr, shdr); - } - } - try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?); + // e_phoff + mem.writeInt(u64, hdr_buf[index..][0..8], self.phdr_table_offset.?, endian); + index += 8; + + // e_shoff + mem.writeInt(u64, hdr_buf[index..][0..8], self.shdr_table_offset.?, endian); + index += 8; }, } - self.shdr_table_dirty = false; - } - if (self.entry_addr == null and self.options.output_mode == .Exe) { - self.error_flags.no_entry_point_found = true; - } else { - self.error_flags.no_entry_point_found = false; - try self.writeElfHeader(); - } - - // The point of flush() is to commit changes, so nothing should be dirty after this. - assert(!self.phdr_table_dirty); - assert(!self.shdr_table_dirty); - assert(!self.shstrtab_dirty); - assert(!self.offset_table_count_dirty); - const syms_sect = &self.sections.items[self.symtab_section_index.?]; - assert(syms_sect.sh_info == self.local_symbols.items.len); - } - - fn writeElfHeader(self: *ElfFile) !void { - var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; - - var index: usize = 0; - hdr_buf[0..4].* = "\x7fELF".*; - index += 4; - - hdr_buf[index] = switch (self.ptr_width) { - .p32 => elf.ELFCLASS32, - .p64 => elf.ELFCLASS64, - }; - index += 1; - - const endian = self.options.target.cpu.arch.endian(); - hdr_buf[index] = switch (endian) { - .Little => elf.ELFDATA2LSB, - .Big => elf.ELFDATA2MSB, - }; - index += 1; - - hdr_buf[index] = 1; // ELF version - index += 1; - - // OS ABI, often set to 0 regardless of target platform - // ABI Version, possibly used by glibc but not by static executables - // padding - mem.set(u8, hdr_buf[index..][0..9], 0); - index += 9; - - assert(index == 16); - - const elf_type = switch (self.options.output_mode) { - .Exe => elf.ET.EXEC, - .Obj => elf.ET.REL, - .Lib => switch (self.options.link_mode) { - .Static => elf.ET.REL, - .Dynamic => elf.ET.DYN, - }, - }; - mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); - index += 2; - - const machine = self.options.target.cpu.arch.toElfMachine(); - mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian); - index += 2; - - // ELF Version, again - mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian); - index += 4; - - const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?; - - switch (self.ptr_width) { - .p32 => { - mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian); - index += 4; - - // e_phoff - mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.phdr_table_offset.?), endian); - index += 4; - - // e_shoff - mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.shdr_table_offset.?), endian); - index += 4; - }, - .p64 => { - // e_entry - mem.writeInt(u64, hdr_buf[index..][0..8], e_entry, endian); - index += 8; - - // e_phoff - mem.writeInt(u64, hdr_buf[index..][0..8], self.phdr_table_offset.?, endian); - index += 8; - - // e_shoff - mem.writeInt(u64, hdr_buf[index..][0..8], self.shdr_table_offset.?, endian); - index += 8; - }, - } - const e_flags = 0; - mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian); - index += 4; - - const e_ehsize: u16 = switch (self.ptr_width) { - .p32 => @sizeOf(elf.Elf32_Ehdr), - .p64 => @sizeOf(elf.Elf64_Ehdr), - }; - mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian); - index += 2; - - const e_phentsize: u16 = switch (self.ptr_width) { - .p32 => @sizeOf(elf.Elf32_Phdr), - .p64 => @sizeOf(elf.Elf64_Phdr), - }; - mem.writeInt(u16, hdr_buf[index..][0..2], e_phentsize, endian); - index += 2; - - const e_phnum = @intCast(u16, self.program_headers.items.len); - mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian); - index += 2; - - const e_shentsize: u16 = switch (self.ptr_width) { - .p32 => @sizeOf(elf.Elf32_Shdr), - .p64 => @sizeOf(elf.Elf64_Shdr), - }; - mem.writeInt(u16, hdr_buf[index..][0..2], e_shentsize, endian); - index += 2; - - const e_shnum = @intCast(u16, self.sections.items.len); - mem.writeInt(u16, hdr_buf[index..][0..2], e_shnum, endian); - index += 2; - - mem.writeInt(u16, hdr_buf[index..][0..2], self.shstrtab_index.?, endian); - index += 2; - - assert(index == e_ehsize); - - try self.file.?.pwriteAll(hdr_buf[0..index], 0); - } - - fn freeTextBlock(self: *ElfFile, text_block: *TextBlock) void { - var already_have_free_list_node = false; - { - var i: usize = 0; - while (i < self.text_block_free_list.items.len) { - if (self.text_block_free_list.items[i] == text_block) { - _ = self.text_block_free_list.swapRemove(i); - continue; - } - if (self.text_block_free_list.items[i] == text_block.prev) { - already_have_free_list_node = true; - } - i += 1; - } - } - - if (self.last_text_block == text_block) { - // TODO shrink the .text section size here - self.last_text_block = text_block.prev; - } - - if (text_block.prev) |prev| { - prev.next = text_block.next; - - if (!already_have_free_list_node and prev.freeListEligible(self.*)) { - // The free list is heuristics, it doesn't have to be perfect, so we can - // ignore the OOM here. - self.text_block_free_list.append(self.allocator, prev) catch {}; - } - } else { - text_block.prev = null; - } - - if (text_block.next) |next| { - next.prev = text_block.prev; - } else { - text_block.next = null; + const e_flags = 0; + mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian); + index += 4; + + const e_ehsize: u16 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Ehdr), + .p64 => @sizeOf(elf.Elf64_Ehdr), + }; + mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian); + index += 2; + + const e_phentsize: u16 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Phdr), + .p64 => @sizeOf(elf.Elf64_Phdr), + }; + mem.writeInt(u16, hdr_buf[index..][0..2], e_phentsize, endian); + index += 2; + + const e_phnum = @intCast(u16, self.program_headers.items.len); + mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian); + index += 2; + + const e_shentsize: u16 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Shdr), + .p64 => @sizeOf(elf.Elf64_Shdr), + }; + mem.writeInt(u16, hdr_buf[index..][0..2], e_shentsize, endian); + index += 2; + + const e_shnum = @intCast(u16, self.sections.items.len); + mem.writeInt(u16, hdr_buf[index..][0..2], e_shnum, endian); + index += 2; + + mem.writeInt(u16, hdr_buf[index..][0..2], self.shstrtab_index.?, endian); + index += 2; + + assert(index == e_ehsize); + + try self.file.?.pwriteAll(hdr_buf[0..index], 0); } - } - - fn shrinkTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64) void { - // TODO check the new capacity, and if it crosses the size threshold into a big enough - // capacity, insert a free list node for it. - } - - fn growTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const sym = self.local_symbols.items[text_block.local_sym_index]; - const align_ok = mem.alignBackwardGeneric(u64, sym.st_value, alignment) == sym.st_value; - const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*); - if (!need_realloc) return sym.st_value; - return self.allocateTextBlock(text_block, new_block_size, alignment); - } - - fn allocateTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { - const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; - const shdr = &self.sections.items[self.text_section_index.?]; - const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den; - - // We use these to indicate our intention to update metadata, placing the new block, - // and possibly removing a free list node. - // It would be simpler to do it inside the for loop below, but that would cause a - // problem if an error was returned later in the function. So this action - // is actually carried out at the end of the function, when errors are no longer possible. - var block_placement: ?*TextBlock = null; - var free_list_removal: ?usize = null; - // First we look for an appropriately sized free list node. - // The list is unordered. We'll just take the first thing that works. - const vaddr = blk: { - var i: usize = 0; - while (i < self.text_block_free_list.items.len) { - const big_block = self.text_block_free_list.items[i]; - // We now have a pointer to a live text block that has too much capacity. - // Is it enough that we could fit this new text block? - const sym = self.local_symbols.items[big_block.local_sym_index]; - const capacity = big_block.capacity(self.*); - const ideal_capacity = capacity * alloc_num / alloc_den; - const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity; - const capacity_end_vaddr = sym.st_value + capacity; - const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_ideal_capacity; - const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment); - if (new_start_vaddr < ideal_capacity_end_vaddr) { - // Additional bookkeeping here to notice if this free list node - // should be deleted because the block that it points to has grown to take up - // more of the extra capacity. - if (!big_block.freeListEligible(self.*)) { + fn freeTextBlock(self: *File.Elf, text_block: *TextBlock) void { + var already_have_free_list_node = false; + { + var i: usize = 0; + while (i < self.text_block_free_list.items.len) { + if (self.text_block_free_list.items[i] == text_block) { _ = self.text_block_free_list.swapRemove(i); - } else { - i += 1; + continue; } - continue; + if (self.text_block_free_list.items[i] == text_block.prev) { + already_have_free_list_node = true; + } + i += 1; } - // At this point we know that we will place the new block here. But the - // remaining question is whether there is still yet enough capacity left - // over for there to still be a free list node. - const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; - const keep_free_list_node = remaining_capacity >= min_text_capacity; - - // Set up the metadata to be updated, after errors are no longer possible. - block_placement = big_block; - if (!keep_free_list_node) { - free_list_removal = i; + } + + if (self.last_text_block == text_block) { + // TODO shrink the .text section size here + self.last_text_block = text_block.prev; + } + + if (text_block.prev) |prev| { + prev.next = text_block.next; + + if (!already_have_free_list_node and prev.freeListEligible(self.*)) { + // The free list is heuristics, it doesn't have to be perfect, so we can + // ignore the OOM here. + self.text_block_free_list.append(self.allocator, prev) catch {}; } - break :blk new_start_vaddr; - } else if (self.last_text_block) |last| { - const sym = self.local_symbols.items[last.local_sym_index]; - const ideal_capacity = sym.st_size * alloc_num / alloc_den; - const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity; - const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment); - // Set up the metadata to be updated, after errors are no longer possible. - block_placement = last; - break :blk new_start_vaddr; } else { - break :blk phdr.p_vaddr; + text_block.prev = null; + } + + if (text_block.next) |next| { + next.prev = text_block.prev; + } else { + text_block.next = null; } - }; - - const expand_text_section = block_placement == null or block_placement.?.next == null; - if (expand_text_section) { - const text_capacity = self.allocatedSize(shdr.sh_offset); - const needed_size = (vaddr + new_block_size) - phdr.p_vaddr; - if (needed_size > text_capacity) { - // Must move the entire text section. - const new_offset = self.findFreeSpace(needed_size, 0x1000); - const text_size = if (self.last_text_block) |last| blk: { + } + + fn shrinkTextBlock(self: *File.Elf, text_block: *TextBlock, new_block_size: u64) void { + // TODO check the new capacity, and if it crosses the size threshold into a big enough + // capacity, insert a free list node for it. + } + + fn growTextBlock(self: *File.Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { + const sym = self.local_symbols.items[text_block.local_sym_index]; + const align_ok = mem.alignBackwardGeneric(u64, sym.st_value, alignment) == sym.st_value; + const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*); + if (!need_realloc) return sym.st_value; + return self.allocateTextBlock(text_block, new_block_size, alignment); + } + + fn allocateTextBlock(self: *File.Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { + const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; + const shdr = &self.sections.items[self.text_section_index.?]; + const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den; + + // We use these to indicate our intention to update metadata, placing the new block, + // and possibly removing a free list node. + // It would be simpler to do it inside the for loop below, but that would cause a + // problem if an error was returned later in the function. So this action + // is actually carried out at the end of the function, when errors are no longer possible. + var block_placement: ?*TextBlock = null; + var free_list_removal: ?usize = null; + + // First we look for an appropriately sized free list node. + // The list is unordered. We'll just take the first thing that works. + const vaddr = blk: { + var i: usize = 0; + while (i < self.text_block_free_list.items.len) { + const big_block = self.text_block_free_list.items[i]; + // We now have a pointer to a live text block that has too much capacity. + // Is it enough that we could fit this new text block? + const sym = self.local_symbols.items[big_block.local_sym_index]; + const capacity = big_block.capacity(self.*); + const ideal_capacity = capacity * alloc_num / alloc_den; + const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity; + const capacity_end_vaddr = sym.st_value + capacity; + const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_ideal_capacity; + const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment); + if (new_start_vaddr < ideal_capacity_end_vaddr) { + // Additional bookkeeping here to notice if this free list node + // should be deleted because the block that it points to has grown to take up + // more of the extra capacity. + if (!big_block.freeListEligible(self.*)) { + _ = self.text_block_free_list.swapRemove(i); + } else { + i += 1; + } + continue; + } + // At this point we know that we will place the new block here. But the + // remaining question is whether there is still yet enough capacity left + // over for there to still be a free list node. + const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; + const keep_free_list_node = remaining_capacity >= min_text_capacity; + + // Set up the metadata to be updated, after errors are no longer possible. + block_placement = big_block; + if (!keep_free_list_node) { + free_list_removal = i; + } + break :blk new_start_vaddr; + } else if (self.last_text_block) |last| { const sym = self.local_symbols.items[last.local_sym_index]; - break :blk (sym.st_value + sym.st_size) - phdr.p_vaddr; - } else 0; - const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, text_size); - if (amt != text_size) return error.InputOutput; - shdr.sh_offset = new_offset; - phdr.p_offset = new_offset; - } - self.last_text_block = text_block; - - shdr.sh_size = needed_size; - phdr.p_memsz = needed_size; - phdr.p_filesz = needed_size; - - self.phdr_table_dirty = true; // TODO look into making only the one program header dirty - self.shdr_table_dirty = true; // TODO look into making only the one section dirty - } - - // This function can also reallocate a text block. - // In this case we need to "unplug" it from its previous location before - // plugging it in to its new location. - if (text_block.prev) |prev| { - prev.next = text_block.next; - } - if (text_block.next) |next| { - next.prev = text_block.prev; - } - - if (block_placement) |big_block| { - text_block.prev = big_block; - text_block.next = big_block.next; - big_block.next = text_block; - } else { - text_block.prev = null; - text_block.next = null; - } - if (free_list_removal) |i| { - _ = self.text_block_free_list.swapRemove(i); - } - return vaddr; - } - - pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void { - if (decl.link.local_sym_index != 0) return; - - // Here we also ensure capacity for the free lists so that they can be appended to without fail. - try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1); - try self.local_symbol_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); - try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); - try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); - - if (self.local_symbol_free_list.popOrNull()) |i| { - //std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name}); - decl.link.local_sym_index = i; - } else { - //std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); - decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); - _ = self.local_symbols.addOneAssumeCapacity(); - } - - if (self.offset_table_free_list.popOrNull()) |i| { - decl.link.offset_table_index = i; - } else { - decl.link.offset_table_index = @intCast(u32, self.offset_table.items.len); - _ = self.offset_table.addOneAssumeCapacity(); - self.offset_table_count_dirty = true; - } - - const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; + const ideal_capacity = sym.st_size * alloc_num / alloc_den; + const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity; + const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment); + // Set up the metadata to be updated, after errors are no longer possible. + block_placement = last; + break :blk new_start_vaddr; + } else { + break :blk phdr.p_vaddr; + } + }; + + const expand_text_section = block_placement == null or block_placement.?.next == null; + if (expand_text_section) { + const text_capacity = self.allocatedSize(shdr.sh_offset); + const needed_size = (vaddr + new_block_size) - phdr.p_vaddr; + if (needed_size > text_capacity) { + // Must move the entire text section. + const new_offset = self.findFreeSpace(needed_size, 0x1000); + const text_size = if (self.last_text_block) |last| blk: { + const sym = self.local_symbols.items[last.local_sym_index]; + break :blk (sym.st_value + sym.st_size) - phdr.p_vaddr; + } else 0; + const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, text_size); + if (amt != text_size) return error.InputOutput; + shdr.sh_offset = new_offset; + phdr.p_offset = new_offset; + } + self.last_text_block = text_block; - self.local_symbols.items[decl.link.local_sym_index] = .{ - .st_name = 0, - .st_info = 0, - .st_other = 0, - .st_shndx = 0, - .st_value = phdr.p_vaddr, - .st_size = 0, - }; - self.offset_table.items[decl.link.offset_table_index] = 0; - } + shdr.sh_size = needed_size; + phdr.p_memsz = needed_size; + phdr.p_filesz = needed_size; - pub fn freeDecl(self: *ElfFile, decl: *Module.Decl) void { - self.freeTextBlock(&decl.link); - if (decl.link.local_sym_index != 0) { - self.local_symbol_free_list.appendAssumeCapacity(decl.link.local_sym_index); - self.offset_table_free_list.appendAssumeCapacity(decl.link.offset_table_index); + self.phdr_table_dirty = true; // TODO look into making only the one program header dirty + self.shdr_table_dirty = true; // TODO look into making only the one section dirty + } - self.local_symbols.items[decl.link.local_sym_index].st_info = 0; + // This function can also reallocate a text block. + // In this case we need to "unplug" it from its previous location before + // plugging it in to its new location. + if (text_block.prev) |prev| { + prev.next = text_block.next; + } + if (text_block.next) |next| { + next.prev = text_block.prev; + } - decl.link.local_sym_index = 0; + if (block_placement) |big_block| { + text_block.prev = big_block; + text_block.next = big_block.next; + big_block.next = text_block; + } else { + text_block.prev = null; + text_block.next = null; + } + if (free_list_removal) |i| { + _ = self.text_block_free_list.swapRemove(i); + } + return vaddr; } - } - pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void { - var code_buffer = std.ArrayList(u8).init(self.allocator); - defer code_buffer.deinit(); - - const typed_value = decl.typed_value.most_recent.typed_value; - const code = switch (try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer)) { - .externally_managed => |x| x, - .appended => code_buffer.items, - .fail => |em| { - decl.analysis = .codegen_failure; - _ = try module.failed_decls.put(decl, em); - return; - }, - }; - - const required_alignment = typed_value.ty.abiAlignment(self.options.target); - - const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { - .Fn => elf.STT_FUNC, - else => elf.STT_OBJECT, - }; - - assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes() - const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; - if (local_sym.st_size != 0) { - const capacity = decl.link.capacity(self.*); - const need_realloc = code.len > capacity or - !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); - if (need_realloc) { - const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); - //std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); - if (vaddr != local_sym.st_value) { - local_sym.st_value = vaddr; + pub fn allocateDeclIndexes(self: *File.Elf, decl: *Module.Decl) !void { + if (decl.link.local_sym_index != 0) return; + + // Here we also ensure capacity for the free lists so that they can be appended to without fail. + try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1); + try self.local_symbol_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); + try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1); + try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); + + if (self.local_symbol_free_list.popOrNull()) |i| { + //std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name}); + decl.link.local_sym_index = i; + } else { + //std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); + decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); + _ = self.local_symbols.addOneAssumeCapacity(); + } - //std.log.debug(.link, " (writing new offset table entry)\n", .{}); - self.offset_table.items[decl.link.offset_table_index] = vaddr; - try self.writeOffsetTableEntry(decl.link.offset_table_index); - } - } else if (code.len < local_sym.st_size) { - self.shrinkTextBlock(&decl.link, code.len); + if (self.offset_table_free_list.popOrNull()) |i| { + decl.link.offset_table_index = i; + } else { + decl.link.offset_table_index = @intCast(u32, self.offset_table.items.len); + _ = self.offset_table.addOneAssumeCapacity(); + self.offset_table_count_dirty = true; } - local_sym.st_size = code.len; - local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name)); - local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits; - local_sym.st_other = 0; - local_sym.st_shndx = self.text_section_index.?; - // TODO this write could be avoided if no fields of the symbol were changed. - try self.writeSymbol(decl.link.local_sym_index); - } else { - const decl_name = mem.spanZ(decl.name); - const name_str_index = try self.makeString(decl_name); - const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); - //std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); - errdefer self.freeTextBlock(&decl.link); - local_sym.* = .{ - .st_name = name_str_index, - .st_info = (elf.STB_LOCAL << 4) | stt_bits, + const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; + + self.local_symbols.items[decl.link.local_sym_index] = .{ + .st_name = 0, + .st_info = 0, .st_other = 0, - .st_shndx = self.text_section_index.?, - .st_value = vaddr, - .st_size = code.len, + .st_shndx = 0, + .st_value = phdr.p_vaddr, + .st_size = 0, }; - self.offset_table.items[decl.link.offset_table_index] = vaddr; - - try self.writeSymbol(decl.link.local_sym_index); - try self.writeOffsetTableEntry(decl.link.offset_table_index); + self.offset_table.items[decl.link.offset_table_index] = 0; } - const section_offset = local_sym.st_value - self.program_headers.items[self.phdr_load_re_index.?].p_vaddr; - const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset; - try self.file.?.pwriteAll(code, file_offset); + pub fn freeDecl(self: *File.Elf, decl: *Module.Decl) void { + self.freeTextBlock(&decl.link); + if (decl.link.local_sym_index != 0) { + self.local_symbol_free_list.appendAssumeCapacity(decl.link.local_sym_index); + self.offset_table_free_list.appendAssumeCapacity(decl.link.offset_table_index); - // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. - const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; - return self.updateDeclExports(module, decl, decl_exports); - } + self.local_symbols.items[decl.link.local_sym_index].st_info = 0; - /// Must be called only after a successful call to `updateDecl`. - pub fn updateDeclExports( - self: *ElfFile, - module: *Module, - decl: *const Module.Decl, - exports: []const *Module.Export, - ) !void { - // In addition to ensuring capacity for global_symbols, we also ensure capacity for freeing all of - // them, so that deleting exports is guaranteed to succeed. - try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len); - try self.global_symbol_free_list.ensureCapacity(self.allocator, self.global_symbols.items.len); - const typed_value = decl.typed_value.most_recent.typed_value; - if (decl.link.local_sym_index == 0) return; - const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; - - for (exports) |exp| { - if (exp.options.section) |section_name| { - if (!mem.eql(u8, section_name, ".text")) { - try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); - module.failed_exports.putAssumeCapacityNoClobber( - exp, - try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}), - ); - continue; - } + decl.link.local_sym_index = 0; } - const stb_bits: u8 = switch (exp.options.linkage) { - .Internal => elf.STB_LOCAL, - .Strong => blk: { - if (mem.eql(u8, exp.options.name, "_start")) { - self.entry_addr = decl_sym.st_value; + } + + pub fn updateDecl(self: *File.Elf, module: *Module, decl: *Module.Decl) !void { + var code_buffer = std.ArrayList(u8).init(self.allocator); + defer code_buffer.deinit(); + + const typed_value = decl.typed_value.most_recent.typed_value; + const code = switch (try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer)) { + .externally_managed => |x| x, + .appended => code_buffer.items, + .fail => |em| { + decl.analysis = .codegen_failure; + _ = try module.failed_decls.put(decl, em); + return; + }, + }; + + const required_alignment = typed_value.ty.abiAlignment(self.options.target); + + const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) { + .Fn => elf.STT_FUNC, + else => elf.STT_OBJECT, + }; + + assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes() + const local_sym = &self.local_symbols.items[decl.link.local_sym_index]; + if (local_sym.st_size != 0) { + const capacity = decl.link.capacity(self.*); + const need_realloc = code.len > capacity or + !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment); + if (need_realloc) { + const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment); + //std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr }); + if (vaddr != local_sym.st_value) { + local_sym.st_value = vaddr; + + //std.log.debug(.link, " (writing new offset table entry)\n", .{}); + self.offset_table.items[decl.link.offset_table_index] = vaddr; + try self.writeOffsetTableEntry(decl.link.offset_table_index); } - break :blk elf.STB_GLOBAL; - }, - .Weak => elf.STB_WEAK, - .LinkOnce => { - try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); - module.failed_exports.putAssumeCapacityNoClobber( - exp, - try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), - ); - continue; - }, - }; - const stt_bits: u8 = @truncate(u4, decl_sym.st_info); - if (exp.link.sym_index) |i| { - const sym = &self.global_symbols.items[i]; - sym.* = .{ - .st_name = try self.updateString(sym.st_name, exp.options.name), - .st_info = (stb_bits << 4) | stt_bits, - .st_other = 0, - .st_shndx = self.text_section_index.?, - .st_value = decl_sym.st_value, - .st_size = decl_sym.st_size, - }; + } else if (code.len < local_sym.st_size) { + self.shrinkTextBlock(&decl.link, code.len); + } + local_sym.st_size = code.len; + local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name)); + local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits; + local_sym.st_other = 0; + local_sym.st_shndx = self.text_section_index.?; + // TODO this write could be avoided if no fields of the symbol were changed. + try self.writeSymbol(decl.link.local_sym_index); } else { - const name = try self.makeString(exp.options.name); - const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { - _ = self.global_symbols.addOneAssumeCapacity(); - break :blk self.global_symbols.items.len - 1; - }; - self.global_symbols.items[i] = .{ - .st_name = name, - .st_info = (stb_bits << 4) | stt_bits, + const decl_name = mem.spanZ(decl.name); + const name_str_index = try self.makeString(decl_name); + const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment); + //std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr }); + errdefer self.freeTextBlock(&decl.link); + + local_sym.* = .{ + .st_name = name_str_index, + .st_info = (elf.STB_LOCAL << 4) | stt_bits, .st_other = 0, .st_shndx = self.text_section_index.?, - .st_value = decl_sym.st_value, - .st_size = decl_sym.st_size, + .st_value = vaddr, + .st_size = code.len, }; + self.offset_table.items[decl.link.offset_table_index] = vaddr; - exp.link.sym_index = @intCast(u32, i); + try self.writeSymbol(decl.link.local_sym_index); + try self.writeOffsetTableEntry(decl.link.offset_table_index); } + + const section_offset = local_sym.st_value - self.program_headers.items[self.phdr_load_re_index.?].p_vaddr; + const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset; + try self.file.?.pwriteAll(code, file_offset); + + // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated. + const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{}; + return self.updateDeclExports(module, decl, decl_exports); } - } - pub fn deleteExport(self: *ElfFile, exp: Export) void { - const sym_index = exp.sym_index orelse return; - self.global_symbol_free_list.appendAssumeCapacity(sym_index); - self.global_symbols.items[sym_index].st_info = 0; - } + /// Must be called only after a successful call to `updateDecl`. + pub fn updateDeclExports( + self: *File.Elf, + module: *Module, + decl: *const Module.Decl, + exports: []const *Module.Export, + ) !void { + // In addition to ensuring capacity for global_symbols, we also ensure capacity for freeing all of + // them, so that deleting exports is guaranteed to succeed. + try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len); + try self.global_symbol_free_list.ensureCapacity(self.allocator, self.global_symbols.items.len); + const typed_value = decl.typed_value.most_recent.typed_value; + if (decl.link.local_sym_index == 0) return; + const decl_sym = self.local_symbols.items[decl.link.local_sym_index]; - fn writeProgHeader(self: *ElfFile, index: usize) !void { - const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); - const offset = self.program_headers.items[index].p_offset; - switch (self.options.target.cpu.arch.ptrBitWidth()) { - 32 => { - var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; - if (foreign_endian) { - bswapAllFields(elf.Elf32_Phdr, &phdr[0]); + for (exports) |exp| { + if (exp.options.section) |section_name| { + if (!mem.eql(u8, section_name, ".text")) { + try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); + module.failed_exports.putAssumeCapacityNoClobber( + exp, + try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}), + ); + continue; + } } - return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); - }, - 64 => { - var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; - if (foreign_endian) { - bswapAllFields(elf.Elf64_Phdr, &phdr[0]); + const stb_bits: u8 = switch (exp.options.linkage) { + .Internal => elf.STB_LOCAL, + .Strong => blk: { + if (mem.eql(u8, exp.options.name, "_start")) { + self.entry_addr = decl_sym.st_value; + } + break :blk elf.STB_GLOBAL; + }, + .Weak => elf.STB_WEAK, + .LinkOnce => { + try module.failed_exports.ensureCapacity(module.failed_exports.items().len + 1); + module.failed_exports.putAssumeCapacityNoClobber( + exp, + try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), + ); + continue; + }, + }; + const stt_bits: u8 = @truncate(u4, decl_sym.st_info); + if (exp.link.sym_index) |i| { + const sym = &self.global_symbols.items[i]; + sym.* = .{ + .st_name = try self.updateString(sym.st_name, exp.options.name), + .st_info = (stb_bits << 4) | stt_bits, + .st_other = 0, + .st_shndx = self.text_section_index.?, + .st_value = decl_sym.st_value, + .st_size = decl_sym.st_size, + }; + } else { + const name = try self.makeString(exp.options.name); + const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { + _ = self.global_symbols.addOneAssumeCapacity(); + break :blk self.global_symbols.items.len - 1; + }; + self.global_symbols.items[i] = .{ + .st_name = name, + .st_info = (stb_bits << 4) | stt_bits, + .st_other = 0, + .st_shndx = self.text_section_index.?, + .st_value = decl_sym.st_value, + .st_size = decl_sym.st_size, + }; + + exp.link.sym_index = @intCast(u32, i); } - return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); - }, - else => return error.UnsupportedArchitecture, + } } - } - fn writeSectHeader(self: *ElfFile, index: usize) !void { - const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); - const offset = self.sections.items[index].sh_offset; - switch (self.options.target.cpu.arch.ptrBitWidth()) { - 32 => { - var shdr: [1]elf.Elf32_Shdr = undefined; - shdr[0] = sectHeaderTo32(self.sections.items[index]); - if (foreign_endian) { - bswapAllFields(elf.Elf32_Shdr, &shdr[0]); - } - return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); - }, - 64 => { - var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; - if (foreign_endian) { - bswapAllFields(elf.Elf64_Shdr, &shdr[0]); - } - return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); - }, - else => return error.UnsupportedArchitecture, + pub fn deleteExport(self: *File.Elf, exp: Export) void { + const sym_index = exp.sym_index orelse return; + self.global_symbol_free_list.appendAssumeCapacity(sym_index); + self.global_symbols.items[sym_index].st_info = 0; } - } - fn writeOffsetTableEntry(self: *ElfFile, index: usize) !void { - const shdr = &self.sections.items[self.got_section_index.?]; - const phdr = &self.program_headers.items[self.phdr_got_index.?]; - const entry_size: u16 = switch (self.ptr_width) { - .p32 => 4, - .p64 => 8, - }; - if (self.offset_table_count_dirty) { - // TODO Also detect virtual address collisions. - const allocated_size = self.allocatedSize(shdr.sh_offset); - const needed_size = self.local_symbols.items.len * entry_size; - if (needed_size > allocated_size) { - // Must move the entire got section. - const new_offset = self.findFreeSpace(needed_size, entry_size); - const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, shdr.sh_size); - if (amt != shdr.sh_size) return error.InputOutput; - shdr.sh_offset = new_offset; - phdr.p_offset = new_offset; + fn writeProgHeader(self: *File.Elf, index: usize) !void { + const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); + const offset = self.program_headers.items[index].p_offset; + switch (self.options.target.cpu.arch.ptrBitWidth()) { + 32 => { + var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; + if (foreign_endian) { + bswapAllFields(elf.Elf32_Phdr, &phdr[0]); + } + return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); + }, + 64 => { + var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Phdr, &phdr[0]); + } + return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); + }, + else => return error.UnsupportedArchitecture, } - shdr.sh_size = needed_size; - phdr.p_memsz = needed_size; - phdr.p_filesz = needed_size; + } - self.shdr_table_dirty = true; // TODO look into making only the one section dirty - self.phdr_table_dirty = true; // TODO look into making only the one program header dirty + fn writeSectHeader(self: *File.Elf, index: usize) !void { + const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); + const offset = self.sections.items[index].sh_offset; + switch (self.options.target.cpu.arch.ptrBitWidth()) { + 32 => { + var shdr: [1]elf.Elf32_Shdr = undefined; + shdr[0] = sectHeaderTo32(self.sections.items[index]); + if (foreign_endian) { + bswapAllFields(elf.Elf32_Shdr, &shdr[0]); + } + return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); + }, + 64 => { + var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Shdr, &shdr[0]); + } + return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); + }, + else => return error.UnsupportedArchitecture, + } + } - self.offset_table_count_dirty = false; + fn writeOffsetTableEntry(self: *File.Elf, index: usize) !void { + const shdr = &self.sections.items[self.got_section_index.?]; + const phdr = &self.program_headers.items[self.phdr_got_index.?]; + const entry_size: u16 = switch (self.ptr_width) { + .p32 => 4, + .p64 => 8, + }; + if (self.offset_table_count_dirty) { + // TODO Also detect virtual address collisions. + const allocated_size = self.allocatedSize(shdr.sh_offset); + const needed_size = self.local_symbols.items.len * entry_size; + if (needed_size > allocated_size) { + // Must move the entire got section. + const new_offset = self.findFreeSpace(needed_size, entry_size); + const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, shdr.sh_size); + if (amt != shdr.sh_size) return error.InputOutput; + shdr.sh_offset = new_offset; + phdr.p_offset = new_offset; + } + shdr.sh_size = needed_size; + phdr.p_memsz = needed_size; + phdr.p_filesz = needed_size; + + self.shdr_table_dirty = true; // TODO look into making only the one section dirty + self.phdr_table_dirty = true; // TODO look into making only the one program header dirty + + self.offset_table_count_dirty = false; + } + const endian = self.options.target.cpu.arch.endian(); + const off = shdr.sh_offset + @as(u64, entry_size) * index; + switch (self.ptr_width) { + .p32 => { + var buf: [4]u8 = undefined; + mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); + try self.file.?.pwriteAll(&buf, off); + }, + .p64 => { + var buf: [8]u8 = undefined; + mem.writeInt(u64, &buf, self.offset_table.items[index], endian); + try self.file.?.pwriteAll(&buf, off); + }, + } } - const endian = self.options.target.cpu.arch.endian(); - const off = shdr.sh_offset + @as(u64, entry_size) * index; - switch (self.ptr_width) { - .p32 => { - var buf: [4]u8 = undefined; - mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); - try self.file.?.pwriteAll(&buf, off); - }, - .p64 => { - var buf: [8]u8 = undefined; - mem.writeInt(u64, &buf, self.offset_table.items[index], endian); - try self.file.?.pwriteAll(&buf, off); - }, + + fn writeSymbol(self: *File.Elf, index: usize) !void { + const syms_sect = &self.sections.items[self.symtab_section_index.?]; + // Make sure we are not pointlessly writing symbol data that will have to get relocated + // due to running out of space. + if (self.local_symbols.items.len != syms_sect.sh_info) { + const sym_size: u64 = switch (self.ptr_width) { + .p32 => @sizeOf(elf.Elf32_Sym), + .p64 => @sizeOf(elf.Elf64_Sym), + }; + const sym_align: u16 = switch (self.ptr_width) { + .p32 => @alignOf(elf.Elf32_Sym), + .p64 => @alignOf(elf.Elf64_Sym), + }; + const needed_size = (self.local_symbols.items.len + self.global_symbols.items.len) * sym_size; + if (needed_size > self.allocatedSize(syms_sect.sh_offset)) { + // Move all the symbols to a new file location. + const new_offset = self.findFreeSpace(needed_size, sym_align); + const existing_size = @as(u64, syms_sect.sh_info) * sym_size; + const amt = try self.file.?.copyRangeAll(syms_sect.sh_offset, self.file.?, new_offset, existing_size); + if (amt != existing_size) return error.InputOutput; + syms_sect.sh_offset = new_offset; + } + syms_sect.sh_info = @intCast(u32, self.local_symbols.items.len); + syms_sect.sh_size = needed_size; // anticipating adding the global symbols later + self.shdr_table_dirty = true; // TODO look into only writing one section + } + const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); + switch (self.ptr_width) { + .p32 => { + var sym = [1]elf.Elf32_Sym{ + .{ + .st_name = self.local_symbols.items[index].st_name, + .st_value = @intCast(u32, self.local_symbols.items[index].st_value), + .st_size = @intCast(u32, self.local_symbols.items[index].st_size), + .st_info = self.local_symbols.items[index].st_info, + .st_other = self.local_symbols.items[index].st_other, + .st_shndx = self.local_symbols.items[index].st_shndx, + }, + }; + if (foreign_endian) { + bswapAllFields(elf.Elf32_Sym, &sym[0]); + } + const off = syms_sect.sh_offset + @sizeOf(elf.Elf32_Sym) * index; + try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); + }, + .p64 => { + var sym = [1]elf.Elf64_Sym{self.local_symbols.items[index]}; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Sym, &sym[0]); + } + const off = syms_sect.sh_offset + @sizeOf(elf.Elf64_Sym) * index; + try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); + }, + } } - } - fn writeSymbol(self: *ElfFile, index: usize) !void { - const syms_sect = &self.sections.items[self.symtab_section_index.?]; - // Make sure we are not pointlessly writing symbol data that will have to get relocated - // due to running out of space. - if (self.local_symbols.items.len != syms_sect.sh_info) { + fn writeAllGlobalSymbols(self: *File.Elf) !void { + const syms_sect = &self.sections.items[self.symtab_section_index.?]; const sym_size: u64 = switch (self.ptr_width) { .p32 => @sizeOf(elf.Elf32_Sym), .p64 => @sizeOf(elf.Elf64_Sym), }; - const sym_align: u16 = switch (self.ptr_width) { - .p32 => @alignOf(elf.Elf32_Sym), - .p64 => @alignOf(elf.Elf64_Sym), - }; - const needed_size = (self.local_symbols.items.len + self.global_symbols.items.len) * sym_size; - if (needed_size > self.allocatedSize(syms_sect.sh_offset)) { - // Move all the symbols to a new file location. - const new_offset = self.findFreeSpace(needed_size, sym_align); - const existing_size = @as(u64, syms_sect.sh_info) * sym_size; - const amt = try self.file.?.copyRangeAll(syms_sect.sh_offset, self.file.?, new_offset, existing_size); - if (amt != existing_size) return error.InputOutput; - syms_sect.sh_offset = new_offset; + const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); + const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size; + switch (self.ptr_width) { + .p32 => { + const buf = try self.allocator.alloc(elf.Elf32_Sym, self.global_symbols.items.len); + defer self.allocator.free(buf); + + for (buf) |*sym, i| { + sym.* = .{ + .st_name = self.global_symbols.items[i].st_name, + .st_value = @intCast(u32, self.global_symbols.items[i].st_value), + .st_size = @intCast(u32, self.global_symbols.items[i].st_size), + .st_info = self.global_symbols.items[i].st_info, + .st_other = self.global_symbols.items[i].st_other, + .st_shndx = self.global_symbols.items[i].st_shndx, + }; + if (foreign_endian) { + bswapAllFields(elf.Elf32_Sym, sym); + } + } + try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off); + }, + .p64 => { + const buf = try self.allocator.alloc(elf.Elf64_Sym, self.global_symbols.items.len); + defer self.allocator.free(buf); + + for (buf) |*sym, i| { + sym.* = .{ + .st_name = self.global_symbols.items[i].st_name, + .st_value = self.global_symbols.items[i].st_value, + .st_size = self.global_symbols.items[i].st_size, + .st_info = self.global_symbols.items[i].st_info, + .st_other = self.global_symbols.items[i].st_other, + .st_shndx = self.global_symbols.items[i].st_shndx, + }; + if (foreign_endian) { + bswapAllFields(elf.Elf64_Sym, sym); + } + } + try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off); + }, } - syms_sect.sh_info = @intCast(u32, self.local_symbols.items.len); - syms_sect.sh_size = needed_size; // anticipating adding the global symbols later - self.shdr_table_dirty = true; // TODO look into only writing one section } - const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); - switch (self.ptr_width) { - .p32 => { - var sym = [1]elf.Elf32_Sym{ - .{ - .st_name = self.local_symbols.items[index].st_name, - .st_value = @intCast(u32, self.local_symbols.items[index].st_value), - .st_size = @intCast(u32, self.local_symbols.items[index].st_size), - .st_info = self.local_symbols.items[index].st_info, - .st_other = self.local_symbols.items[index].st_other, - .st_shndx = self.local_symbols.items[index].st_shndx, - }, - }; - if (foreign_endian) { - bswapAllFields(elf.Elf32_Sym, &sym[0]); - } - const off = syms_sect.sh_offset + @sizeOf(elf.Elf32_Sym) * index; - try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); - }, - .p64 => { - var sym = [1]elf.Elf64_Sym{self.local_symbols.items[index]}; - if (foreign_endian) { - bswapAllFields(elf.Elf64_Sym, &sym[0]); - } - const off = syms_sect.sh_offset + @sizeOf(elf.Elf64_Sym) * index; - try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off); - }, - } - } - - fn writeAllGlobalSymbols(self: *ElfFile) !void { - const syms_sect = &self.sections.items[self.symtab_section_index.?]; - const sym_size: u64 = switch (self.ptr_width) { - .p32 => @sizeOf(elf.Elf32_Sym), - .p64 => @sizeOf(elf.Elf64_Sym), - }; - const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); - const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size; - switch (self.ptr_width) { - .p32 => { - const buf = try self.allocator.alloc(elf.Elf32_Sym, self.global_symbols.items.len); - defer self.allocator.free(buf); - - for (buf) |*sym, i| { - sym.* = .{ - .st_name = self.global_symbols.items[i].st_name, - .st_value = @intCast(u32, self.global_symbols.items[i].st_value), - .st_size = @intCast(u32, self.global_symbols.items[i].st_size), - .st_info = self.global_symbols.items[i].st_info, - .st_other = self.global_symbols.items[i].st_other, - .st_shndx = self.global_symbols.items[i].st_shndx, - }; - if (foreign_endian) { - bswapAllFields(elf.Elf32_Sym, sym); - } - } - try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off); - }, - .p64 => { - const buf = try self.allocator.alloc(elf.Elf64_Sym, self.global_symbols.items.len); - defer self.allocator.free(buf); - - for (buf) |*sym, i| { - sym.* = .{ - .st_name = self.global_symbols.items[i].st_name, - .st_value = self.global_symbols.items[i].st_value, - .st_size = self.global_symbols.items[i].st_size, - .st_info = self.global_symbols.items[i].st_info, - .st_other = self.global_symbols.items[i].st_other, - .st_shndx = self.global_symbols.items[i].st_shndx, - }; - if (foreign_endian) { - bswapAllFields(elf.Elf64_Sym, sym); - } - } - try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off); - }, - } - } + }; }; /// Truncates the existing file contents and overwrites the contents. /// Returns an error if `file` is not already open with +read +write +seek abilities. -pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile { +pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf { switch (options.output_mode) { .Exe => {}, .Obj => {}, @@ -1368,7 +1455,7 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !El .wasm => return error.TODOImplementWritingWasmObjects, } - var self: ElfFile = .{ + var self: File.Elf = .{ .allocator = allocator, .file = file, .options = options, @@ -1412,7 +1499,7 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !El } /// Returns error.IncrFailed if incremental update could not be performed. -fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !ElfFile { +fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !File.Elf { switch (options.output_mode) { .Exe => {}, .Obj => {}, @@ -1425,7 +1512,7 @@ fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !Elf .macho => return error.IncrFailed, .wasm => return error.IncrFailed, } - var self: ElfFile = .{ + var self: File.Elf = .{ .allocator = allocator, .file = file, .owns_file_handle = false, diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index ae5cecce209d5ea461b8a3926c3a958274356c68..a1dd659e754a9ca02314b6703cd8564e019c2cd0 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -64,10 +64,11 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. target: std.zig.CrossTarget, /// In order to be able to run e.g. Execution updates, this must be set - /// to Executable. + /// to Executable. This is ignored when generating C output. output_mode: std.builtin.OutputMode, updates: std.ArrayList(Update), extension: TestType, + c_standard: ?Module.CStandard = null, /// Adds a subcase in which the module is updated with `src`, and the /// resulting ZIR is validated against `result`. @@ -187,6 +188,22 @@ pub const TestContext = struct { return ctx.addObj(name, target, .ZIR); } + pub fn addC(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, T: TestType, standard: Module.CStandard) *Case { + ctx.cases.append(Case{ + .name = name, + .target = target, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Obj, + .extension = T, + .c_standard = standard, + }) catch unreachable; + return &ctx.cases.items[ctx.cases.items.len - 1]; + } + + pub fn c11(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, c: [:0]const u8) void { + ctx.addC(name, target, .Zig, .C11).addTransform(src, c); + } + pub fn addCompareOutput( ctx: *TestContext, name: []const u8, @@ -425,6 +442,7 @@ pub const TestContext = struct { .bin_file_path = bin_name, .root_pkg = root_pkg, .keep_source_files_loaded = true, + .c_standard = case.c_standard, }); defer module.deinit(); @@ -463,14 +481,15 @@ pub const TestContext = struct { var test_node = update_node.start("assert", null); test_node.activate(); defer test_node.end(); + const label = if (case.c_standard) |_| "C" else "ZIR"; if (expected_output.len != out_zir.items.len) { - std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); + std.debug.warn("{}\nTransformed {} length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); std.process.exit(1); } for (expected_output) |e, i| { if (out_zir.items[i] != e) { if (expected_output.len != out_zir.items.len) { - std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); + std.debug.warn("{}\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); std.process.exit(1); } } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig new file mode 100644 index 0000000000000000000000000000000000000000..884f5c927f51db7c614f541f1c05788ddfed96c3 --- /dev/null +++ b/test/stage2/cbe.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const TestContext = @import("../../src-self-hosted/test.zig").TestContext; + +// These tests should work with all platforms, but we're using linux_x64 for +// now for consistency. Will be expanded eventually. +const linux_x64 = std.zig.CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .linux, +}; + +pub fn addCases(ctx: *TestContext) !void { + // // These tests should work on every platform + // ctx.c11("empty start function", linux_x64, + // \\export fn start() void {} + // , + // \\void start(void) {} + // ); +} diff --git a/test/stage2/test.zig b/test/stage2/test.zig index e0ef2915885bb7f8078c19a1d3fe660b84ff4827..f5acc72f93d2725f0b224f5f096c996f550306ef 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -4,4 +4,5 @@ pub fn addCases(ctx: *TestContext) !void { try @import("compile_errors.zig").addCases(ctx); try @import("compare_output.zig").addCases(ctx); try @import("zir.zig").addCases(ctx); + try @import("cbe.zig").addCases(ctx); } -- 2.54.0 From a17200dab1b0b373e85c6c2cc266078012a81948 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 16:40:14 -0400 Subject: [PATCH 198/295] CBE skeleton --- src-self-hosted/link.zig | 59 +++++++++++++++++++++++++++++++- src-self-hosted/test.zig | 73 ++++++++++++++++++++++++++-------------- test/stage2/cbe.zig | 12 +++---- 3 files changed, 112 insertions(+), 32 deletions(-) diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 9e273a2b9e284b506c207f989c3a4ca586ffe789..e37f567d9270e3fd46e362847605174fa03f70d8 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -38,7 +38,11 @@ pub fn openBinFilePath( errdefer file.close(); if (options.c_standard) |cstd| { - return error.Unimplemented; + var bin_file = try allocator.create(File.C); + errdefer allocator.destroy(bin_file); + bin_file.* = try openCFile(allocator, file, options); + bin_file.owns_file_handle = true; + return &bin_file.base; } else { var bin_file = try allocator.create(File.Elf); errdefer allocator.destroy(bin_file); @@ -82,6 +86,17 @@ pub fn writeFilePath( return result; } +pub fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C { + var self: File.C = .{ + .allocator = allocator, + .file = file, + .options = options, + .owns_file_handle = false, + }; + errdefer self.deinit(); + return self; +} + /// Attempts incremental linking, if the file already exists. /// If incremental linking fails, falls back to truncating the file and rewriting it. /// Returns an error if `file` is not already open with +read +write +seek abilities. @@ -108,6 +123,7 @@ pub const File = struct { pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { try switch (base.tag) { .Elf => @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path), + .C => @fieldParentPtr(C, "base", base).makeWritable(dir, sub_path), else => unreachable, }; } @@ -122,6 +138,7 @@ pub const File = struct { pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void { try switch (base.tag) { .Elf => @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), + .C => @fieldParentPtr(C, "base", base).updateDecl(module, decl), else => unreachable, }; } @@ -129,6 +146,9 @@ pub const File = struct { pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void { try switch (base.tag) { .Elf => @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), + .C => { + //TODO + }, else => unreachable, }; } @@ -136,6 +156,7 @@ pub const File = struct { pub fn deinit(base: *File) void { switch (base.tag) { .Elf => @fieldParentPtr(Elf, "base", base).deinit(), + .C => @fieldParentPtr(C, "base", base).deinit(), else => unreachable, } } @@ -143,6 +164,9 @@ pub const File = struct { pub fn flush(base: *File) !void { try switch (base.tag) { .Elf => @fieldParentPtr(Elf, "base", base).flush(), + .C => { + //TODO + }, else => unreachable, }; } @@ -157,6 +181,7 @@ pub const File = struct { pub fn errorFlags(base: *File) ErrorFlags { return switch (base.tag) { .Elf => @fieldParentPtr(Elf, "base", base).error_flags, + .C => return .{ .no_entry_point_found = false }, else => unreachable, }; } @@ -176,6 +201,38 @@ pub const File = struct { pub const ErrorFlags = struct { no_entry_point_found: bool = false, }; + + pub const C = struct { + pub const base_tag: Tag = .C; + base: File = File{ .tag = base_tag }, + + allocator: *Allocator, + file: ?fs.File, + owns_file_handle: bool, + options: Options, + + pub fn makeWritable(self: *File.C, dir: fs.Dir, sub_path: []const u8) !void { + assert(self.owns_file_handle); + if (self.file != null) return; + self.file = try dir.createFile(sub_path, .{ + .truncate = false, + .read = true, + .mode = determineMode(self.options), + }); + } + + pub fn deinit(self: *File.C) void { + if (self.owns_file_handle) { + if (self.file) |f| + f.close(); + } + } + + pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { + return error.Unimplemented; + } + }; + pub const Elf = struct { pub const base_tag: Tag = .Elf; base: File = File{ .tag = base_tag }, diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index a1dd659e754a9ca02314b6703cd8564e019c2cd0..ce20c172cf891518018663c777f608440fae9e5b 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -464,33 +464,54 @@ pub const TestContext = struct { switch (update.case) { .Transformation => |expected_output| { - update_node.estimated_total_items = 5; - var emit_node = update_node.start("emit", null); - emit_node.activate(); - var new_zir_module = try zir.emit(allocator, module); - defer new_zir_module.deinit(allocator); - emit_node.end(); + var label: []const u8 = "ZIR"; + if (case.c_standard) |cstd| { + label = @tagName(cstd); + var c: *link.File.C = module.bin_file.cast(link.File.C).?; + var out = c.file.?.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!"); + defer allocator.free(out); - var write_node = update_node.start("write", null); - write_node.activate(); - var out_zir = std.ArrayList(u8).init(allocator); - defer out_zir.deinit(); - try new_zir_module.writeToStream(allocator, out_zir.outStream()); - write_node.end(); + if (expected_output.len != out.len) { + std.debug.warn("{}\nTransformed {} length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out }); + std.process.exit(1); + } + for (expected_output) |e, i| { + if (out[i] != e) { + if (expected_output.len != out.len) { + std.debug.warn("{}\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out }); + std.process.exit(1); + } + } + } + } else { + update_node.estimated_total_items = 5; + var emit_node = update_node.start("emit", null); + emit_node.activate(); + var new_zir_module = try zir.emit(allocator, module); + defer new_zir_module.deinit(allocator); + emit_node.end(); - var test_node = update_node.start("assert", null); - test_node.activate(); - defer test_node.end(); - const label = if (case.c_standard) |_| "C" else "ZIR"; - if (expected_output.len != out_zir.items.len) { - std.debug.warn("{}\nTransformed {} length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); - std.process.exit(1); - } - for (expected_output) |e, i| { - if (out_zir.items[i] != e) { - if (expected_output.len != out_zir.items.len) { - std.debug.warn("{}\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); - std.process.exit(1); + var write_node = update_node.start("write", null); + write_node.activate(); + var out_zir = std.ArrayList(u8).init(allocator); + defer out_zir.deinit(); + try new_zir_module.writeToStream(allocator, out_zir.outStream()); + write_node.end(); + + var test_node = update_node.start("assert", null); + test_node.activate(); + defer test_node.end(); + + if (expected_output.len != out_zir.items.len) { + std.debug.warn("{}\nTransformed {} length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); + std.process.exit(1); + } + for (expected_output) |e, i| { + if (out_zir.items[i] != e) { + if (expected_output.len != out_zir.items.len) { + std.debug.warn("{}\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); + std.process.exit(1); + } } } } @@ -527,6 +548,8 @@ pub const TestContext = struct { } }, .Execution => |expected_stdout| { + std.debug.assert(case.c_standard == null); + update_node.estimated_total_items = 4; var exec_result = x: { var exec_node = update_node.start("execute", null); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 884f5c927f51db7c614f541f1c05788ddfed96c3..a599367aecfc9fdc0f6a8a6f2db6b6a5d8dbb999 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -9,10 +9,10 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - // // These tests should work on every platform - // ctx.c11("empty start function", linux_x64, - // \\export fn start() void {} - // , - // \\void start(void) {} - // ); + // These tests should work on every platform + ctx.c11("empty start function", linux_x64, + \\export fn start() void {} + , + \\void start(void) {} + ); } -- 2.54.0 From aaaebfe97fa876356c22469001c6511dbdb8354d Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 16:47:39 -0400 Subject: [PATCH 199/295] Detect unexpected compilation errors in tests --- src-self-hosted/test.zig | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index ce20c172cf891518018663c777f608440fae9e5b..0a4e9708ac5b9fc040da9de2cd2fe7909826fa5c 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -462,6 +462,19 @@ pub const TestContext = struct { try module.update(); module_node.end(); + if (update.case != .Error) { + var all_errors = try module.getAllErrorsAlloc(); + defer all_errors.deinit(allocator); + if (all_errors.list.len != 0) { + std.debug.warn("\nErrors occurred updating the module:\n================\n", .{}); + for (all_errors.list) |err| { + std.debug.warn(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg }); + } + std.debug.warn("Test failed.\n", .{}); + std.process.exit(1); + } + } + switch (update.case) { .Transformation => |expected_output| { var label: []const u8 = "ZIR"; @@ -472,13 +485,13 @@ pub const TestContext = struct { defer allocator.free(out); if (expected_output.len != out.len) { - std.debug.warn("{}\nTransformed {} length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out }); + std.debug.warn("\nTransformed {} length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ label, expected_output, out }); std.process.exit(1); } for (expected_output) |e, i| { if (out[i] != e) { if (expected_output.len != out.len) { - std.debug.warn("{}\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out }); + std.debug.warn("\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ label, expected_output, out }); std.process.exit(1); } } -- 2.54.0 From 2f28ecf946b566f40e648145af18d207d0c5770d Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 17:06:07 -0400 Subject: [PATCH 200/295] CBE: Get test more useful --- src-self-hosted/cgen.zig | 11 +++++++++++ src-self-hosted/link.zig | 3 ++- src-self-hosted/test.zig | 5 ++++- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src-self-hosted/cgen.zig diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig new file mode 100644 index 0000000000000000000000000000000000000000..f06f9df6ac7dce6aee68a8627075b3b950ae61f3 --- /dev/null +++ b/src-self-hosted/cgen.zig @@ -0,0 +1,11 @@ +const link = @import("link.zig"); +const Module = @import("Module.zig"); + +const C = link.File.C; +const Decl = Module.Decl; +const CStandard = Module.CStandard; + +pub fn generate(file: *C, decl: *Decl, standard: CStandard) !void { + const writer = file.file.?.writer(); + try writer.print("Generating decl '{}', targeting {}", .{ decl.name, @tagName(standard) }); +} diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index e37f567d9270e3fd46e362847605174fa03f70d8..98133a4f42b0f49d510623012b85850fd49b79da 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -7,6 +7,7 @@ const Module = @import("Module.zig"); const fs = std.fs; const elf = std.elf; const codegen = @import("codegen.zig"); +const cgen = @import("cgen.zig"); const default_entry_addr = 0x8000000; @@ -229,7 +230,7 @@ pub const File = struct { } pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { - return error.Unimplemented; + try cgen.generate(self, decl, self.options.c_standard.?); } }; diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 0a4e9708ac5b9fc040da9de2cd2fe7909826fa5c..153c77ce47d50e9f4c25a88c007571995eacada7 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -481,7 +481,10 @@ pub const TestContext = struct { if (case.c_standard) |cstd| { label = @tagName(cstd); var c: *link.File.C = module.bin_file.cast(link.File.C).?; - var out = c.file.?.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!"); + c.file.?.close(); + var file = try tmp.dir.openFile(bin_name, .{ .read = true }); + defer file.close(); + var out = file.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!"); defer allocator.free(out); if (expected_output.len != out.len) { -- 2.54.0 From 6ece36a051f5cf2bd2329d3f6512a7e9d55486a3 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 17:51:59 -0400 Subject: [PATCH 201/295] Working translation of empty function --- src-self-hosted/cgen.zig | 41 +++++++++++++++++++++++++++++++++++++++- src-self-hosted/test.zig | 1 + test/stage2/cbe.zig | 7 +++++-- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index f06f9df6ac7dce6aee68a8627075b3b950ae61f3..48b725c988c55ccba38a833d9deaa71fa7688381 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -1,11 +1,50 @@ const link = @import("link.zig"); const Module = @import("Module.zig"); +const std = @import("std"); +const Value = @import("value.zig").Value; const C = link.File.C; const Decl = Module.Decl; const CStandard = Module.CStandard; +const mem = std.mem; + +/// Maps a name from Zig source to C. This will always give the same output for +/// any given input. +fn map(name: []const u8) ![]const u8 { + return name; +} pub fn generate(file: *C, decl: *Decl, standard: CStandard) !void { const writer = file.file.?.writer(); - try writer.print("Generating decl '{}', targeting {}", .{ decl.name, @tagName(standard) }); + const tv = decl.typed_value.most_recent.typed_value; + switch (tv.ty.zigTypeTag()) { + .Fn => { + const return_type = tv.ty.fnReturnType(); + switch (return_type.zigTypeTag()) { + .NoReturn => try writer.writeAll("_Noreturn void "), + else => return error.Unimplemented, + } + + const name = try map(mem.spanZ(decl.name)); + try writer.print("{} (", .{name}); + if (tv.ty.fnParamLen() == 0) { + try writer.writeAll("void){"); + } else { + return error.Unimplemented; + } + + const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; + const instructions = func.analysis.success.instructions; + if (instructions.len > 0) { + try writer.writeAll("\n\t"); + for (instructions) |inst| { + std.debug.warn("\nTranslating {}\n", .{inst.*}); + } + try writer.writeAll("\n"); + } + + try writer.writeAll("}\n"); + }, + else => return error.Unimplemented, + } } diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 153c77ce47d50e9f4c25a88c007571995eacada7..9e7a7bbf50a9e4321676a79d0e7d478b0052997b 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -482,6 +482,7 @@ pub const TestContext = struct { label = @tagName(cstd); var c: *link.File.C = module.bin_file.cast(link.File.C).?; c.file.?.close(); + c.file = null; var file = try tmp.dir.openFile(bin_name, .{ .read = true }); defer file.close(); var out = file.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!"); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index a599367aecfc9fdc0f6a8a6f2db6b6a5d8dbb999..86f966af82807270c54042900947284af8495099 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -11,8 +11,11 @@ const linux_x64 = std.zig.CrossTarget{ pub fn addCases(ctx: *TestContext) !void { // These tests should work on every platform ctx.c11("empty start function", linux_x64, - \\export fn start() void {} + \\export fn _start() noreturn {} , - \\void start(void) {} + // A newline is always generated after every function; this ensures, among + // other things, that there is always a newline at the end of the file + \\_Noreturn void _start(void) {} + \\ ); } -- 2.54.0 From 417c92895263250ca399bcf7a1a2abaee6067624 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 8 Jul 2020 00:03:45 +0200 Subject: [PATCH 202/295] Add comment about memory invalidation in Iterator.next on Win --- lib/std/fs.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index e932253bdba90161969edd023a58a77c161a43ee..f8777f28e49b8f23bc3d22951cc6f933df37155d 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -453,6 +453,8 @@ pub const Dir = struct { pub const Error = IteratorError; + /// Memory such as file names referenced in this returned entry becomes invalid + /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { start_over: while (true) { const w = os.windows; -- 2.54.0 From cf86aa8772f266cb475711aeee7b0c1b2083e6aa Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 18:43:25 -0400 Subject: [PATCH 203/295] Fix a dumb in tests --- src-self-hosted/test.zig | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 9e7a7bbf50a9e4321676a79d0e7d478b0052997b..80e40900a0e3f0a5725fe30d4d64550a872f34c1 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -494,10 +494,8 @@ pub const TestContext = struct { } for (expected_output) |e, i| { if (out[i] != e) { - if (expected_output.len != out.len) { - std.debug.warn("\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ label, expected_output, out }); - std.process.exit(1); - } + std.debug.warn("\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ label, expected_output, out }); + std.process.exit(1); } } } else { @@ -525,10 +523,8 @@ pub const TestContext = struct { } for (expected_output) |e, i| { if (out_zir.items[i] != e) { - if (expected_output.len != out_zir.items.len) { - std.debug.warn("{}\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); - std.process.exit(1); - } + std.debug.warn("{}\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); + std.process.exit(1); } } } -- 2.54.0 From cf09b335d8b7ad5b43ceefd169b4c2e6be8ed249 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 19:35:33 -0400 Subject: [PATCH 204/295] CBE: Working function call w/ no args or return value --- src-self-hosted/cgen.zig | 71 ++++++++++++++++++++++++++++++---------- src-self-hosted/link.zig | 28 ++++++++++++++-- test/stage2/cbe.zig | 21 +++++++++--- 3 files changed, 95 insertions(+), 25 deletions(-) diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 48b725c988c55ccba38a833d9deaa71fa7688381..99828ee36265b9621bd0ed7a454b87eede0e7c07 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -1,7 +1,9 @@ const link = @import("link.zig"); const Module = @import("Module.zig"); -const std = @import("std"); +const ir = @import("ir.zig"); const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; +const std = @import("std"); const C = link.File.C; const Decl = Module.Decl; @@ -14,36 +16,69 @@ fn map(name: []const u8) ![]const u8 { return name; } +fn renderFunctionSignature(writer: std.ArrayList(u8).Writer, decl: *Decl) !void { + const tv = decl.typed_value.most_recent.typed_value; + switch (tv.ty.fnReturnType().zigTypeTag()) { + .NoReturn => { + try writer.writeAll("_Noreturn void "); + }, + else => return error.Unimplemented, + } + const name = try map(mem.spanZ(decl.name)); + try writer.print("{}(", .{name}); + if (tv.ty.fnParamLen() == 0) { + try writer.writeAll("void)"); + } else { + return error.Unimplemented; + } +} + pub fn generate(file: *C, decl: *Decl, standard: CStandard) !void { - const writer = file.file.?.writer(); + const writer = file.main.writer(); + const header = file.header.writer(); const tv = decl.typed_value.most_recent.typed_value; switch (tv.ty.zigTypeTag()) { .Fn => { - const return_type = tv.ty.fnReturnType(); - switch (return_type.zigTypeTag()) { - .NoReturn => try writer.writeAll("_Noreturn void "), - else => return error.Unimplemented, - } - - const name = try map(mem.spanZ(decl.name)); - try writer.print("{} (", .{name}); - if (tv.ty.fnParamLen() == 0) { - try writer.writeAll("void){"); - } else { - return error.Unimplemented; - } + try renderFunctionSignature(writer, decl); + try writer.writeAll(" {"); const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; const instructions = func.analysis.success.instructions; if (instructions.len > 0) { - try writer.writeAll("\n\t"); for (instructions) |inst| { - std.debug.warn("\nTranslating {}\n", .{inst.*}); + try writer.writeAll("\n\t"); + switch (inst.tag) { + .call => { + const call = inst.cast(ir.Inst.Call).?.args; + if (call.func.cast(ir.Inst.Constant)) |func_inst| { + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const target = func_val.func.owner_decl; + const tname = mem.spanZ(target.name); + if (file.called.get(tname) == null) { + try file.called.put(tname, void{}); + try renderFunctionSignature(header, target); + try header.writeAll(";\n"); + } + try writer.print("{}();", .{tname}); + } else { + return error.Unimplemented; + } + if (call.args.len != 0) { + return error.Unimplemented; + } + } else { + return error.Unimplemented; + } + }, + else => { + std.debug.warn("\nTranslating {}\n", .{inst.*}); + }, + } } try writer.writeAll("\n"); } - try writer.writeAll("}\n"); + try writer.writeAll("}\n\n"); }, else => return error.Unimplemented, } diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 98133a4f42b0f49d510623012b85850fd49b79da..c47e1597c687c151d9acffe2caba36d9015529b2 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -93,6 +93,9 @@ pub fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C .file = file, .options = options, .owns_file_handle = false, + .main = std.ArrayList(u8).init(allocator), + .header = std.ArrayList(u8).init(allocator), + .called = std.StringHashMap(void).init(allocator), }; errdefer self.deinit(); return self; @@ -165,9 +168,7 @@ pub const File = struct { pub fn flush(base: *File) !void { try switch (base.tag) { .Elf => @fieldParentPtr(Elf, "base", base).flush(), - .C => { - //TODO - }, + .C => @fieldParentPtr(C, "base", base).flush(), else => unreachable, }; } @@ -208,9 +209,12 @@ pub const File = struct { base: File = File{ .tag = base_tag }, allocator: *Allocator, + header: std.ArrayList(u8), + main: std.ArrayList(u8), file: ?fs.File, owns_file_handle: bool, options: Options, + called: std.StringHashMap(void), pub fn makeWritable(self: *File.C, dir: fs.Dir, sub_path: []const u8) !void { assert(self.owns_file_handle); @@ -223,6 +227,9 @@ pub const File = struct { } pub fn deinit(self: *File.C) void { + self.main.deinit(); + self.header.deinit(); + self.called.deinit(); if (self.owns_file_handle) { if (self.file) |f| f.close(); @@ -232,6 +239,21 @@ pub const File = struct { pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { try cgen.generate(self, decl, self.options.c_standard.?); } + + pub fn flush(self: *File.C) !void { + const writer = self.file.?.writer(); + if (self.header.items.len > 0) { + try self.header.append('\n'); + } + try writer.writeAll(self.header.items); + if (self.main.items.len > 1) { + const last_two = self.main.items[self.main.items.len - 2 ..]; + if (std.mem.eql(u8, last_two, "\n\n")) { + self.main.items.len -= 1; + } + } + try writer.writeAll(self.main.items); + } }; pub const Elf = struct { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 86f966af82807270c54042900947284af8495099..88eb6c595325f170d165967f9882897e063496b1 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -9,13 +9,26 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - // These tests should work on every platform ctx.c11("empty start function", linux_x64, \\export fn _start() noreturn {} , - // A newline is always generated after every function; this ensures, among - // other things, that there is always a newline at the end of the file - \\_Noreturn void _start(void) {} + \\_Noreturn void _start(void) {} + \\ + ); + ctx.c11("less empty start function", linux_x64, + \\fn main() noreturn {} + \\ + \\export fn _start() noreturn { + \\ main(); + \\} + , + \\_Noreturn void main(void); + \\ + \\_Noreturn void _start(void) { + \\ main(); + \\} + \\ + \\_Noreturn void main(void) {} \\ ); } -- 2.54.0 From 64bf1301822866904f52a70213dd71eefce4b99a Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 21:35:00 -0400 Subject: [PATCH 205/295] CBE: working asm Inputs and Outputs; std{int,def}.h auto-inclusion --- src-self-hosted/cgen.zig | 97 +++++++++++++++++++++++++++++++++++----- src-self-hosted/link.zig | 19 ++++++-- test/stage2/cbe.zig | 51 +++++++++++++++++++++ 3 files changed, 153 insertions(+), 14 deletions(-) diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 99828ee36265b9621bd0ed7a454b87eede0e7c07..5b04d98119790f4a82f29aeeb46f4cb29f1f455f 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -16,16 +16,24 @@ fn map(name: []const u8) ![]const u8 { return name; } -fn renderFunctionSignature(writer: std.ArrayList(u8).Writer, decl: *Decl) !void { - const tv = decl.typed_value.most_recent.typed_value; - switch (tv.ty.fnReturnType().zigTypeTag()) { - .NoReturn => { - try writer.writeAll("_Noreturn void "); - }, - else => return error.Unimplemented, +fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type) !void { + if (T.tag() == .usize) { + file.need_stddef = true; + try writer.writeAll("size_t"); + } else { + switch (T.zigTypeTag()) { + .NoReturn => try writer.writeAll("_Noreturn void"), + .Void => try writer.writeAll("void"), + else => return error.Unimplemented, + } } +} + +fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { + const tv = decl.typed_value.most_recent.typed_value; + try renderType(file, writer, tv.ty.fnReturnType()); const name = try map(mem.spanZ(decl.name)); - try writer.print("{}(", .{name}); + try writer.print(" {}(", .{name}); if (tv.ty.fnParamLen() == 0) { try writer.writeAll("void)"); } else { @@ -39,7 +47,7 @@ pub fn generate(file: *C, decl: *Decl, standard: CStandard) !void { const tv = decl.typed_value.most_recent.typed_value; switch (tv.ty.zigTypeTag()) { .Fn => { - try renderFunctionSignature(writer, decl); + try renderFunctionSignature(file, writer, decl); try writer.writeAll(" {"); const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; @@ -48,6 +56,55 @@ pub fn generate(file: *C, decl: *Decl, standard: CStandard) !void { for (instructions) |inst| { try writer.writeAll("\n\t"); switch (inst.tag) { + .assembly => { + const as = inst.cast(ir.Inst.Assembly).?.args; + for (as.inputs) |i, index| { + if (i[0] == '{' and i[i.len - 1] == '}') { + const reg = i[1 .. i.len - 1]; + const arg = as.args[index]; + if (arg.cast(ir.Inst.Constant)) |c| { + if (c.val.tag() == .int_u64) { + try writer.writeAll("register "); + try renderType(file, writer, arg.ty); + try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() }); + } else { + return error.Unimplemented; + } + } else { + return error.Unimplemented; + } + } else { + return error.Unimplemented; + } + } + try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); + if (as.output) |o| { + return error.Unimplemented; + } + if (as.inputs.len > 0) { + if (as.output == null) { + try writer.writeAll(" :"); + } + try writer.writeAll(": "); + for (as.inputs) |i, index| { + if (i[0] == '{' and i[i.len - 1] == '}') { + const reg = i[1 .. i.len - 1]; + const arg = as.args[index]; + if (index > 0) { + try writer.writeAll(", "); + } + if (arg.cast(ir.Inst.Constant)) |c| { + try writer.print("\"\"({}_constant)", .{reg}); + } else { + return error.Unimplemented; + } + } else { + return error.Unimplemented; + } + } + } + try writer.writeAll(");"); + }, .call => { const call = inst.cast(ir.Inst.Call).?.args; if (call.func.cast(ir.Inst.Constant)) |func_inst| { @@ -56,17 +113,20 @@ pub fn generate(file: *C, decl: *Decl, standard: CStandard) !void { const tname = mem.spanZ(target.name); if (file.called.get(tname) == null) { try file.called.put(tname, void{}); - try renderFunctionSignature(header, target); + try renderFunctionSignature(file, header, target); try header.writeAll(";\n"); } try writer.print("{}();", .{tname}); } else { + std.debug.warn("non-function call target?\n", .{}); return error.Unimplemented; } if (call.args.len != 0) { + std.debug.warn("parameters\n", .{}); return error.Unimplemented; } } else { + std.debug.warn("non-constant call inst?\n", .{}); return error.Unimplemented; } }, @@ -80,6 +140,21 @@ pub fn generate(file: *C, decl: *Decl, standard: CStandard) !void { try writer.writeAll("}\n\n"); }, - else => return error.Unimplemented, + .Array => { + if (mem.indexOf(u8, mem.span(decl.name), "$") == null) { + // TODO: prevent inline asm constants from being emitted + if (tv.val.cast(Value.Payload.Bytes)) |payload| { + try writer.print("const char *const {} = \"{}\";\n", .{ decl.name, payload.data }); + std.debug.warn("\n\nARRAYTRANS\n", .{}); + if (tv.ty.arraySentinel()) |sentinel| {} + } else { + return error.Unimplemented; + } + } + }, + else => |e| { + std.debug.warn("\nTODO implement {}\n", .{e}); + return error.Unimplemented; + }, } } diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index c47e1597c687c151d9acffe2caba36d9015529b2..8352f64fcaa6cd84fe1565816889a69d6cd2d78f 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -191,7 +191,7 @@ pub const File = struct { pub fn options(base: *File) Options { return switch (base.tag) { .Elf => @fieldParentPtr(Elf, "base", base).options, - else => unreachable, + .C => @fieldParentPtr(C, "base", base).options, }; } @@ -215,6 +215,8 @@ pub const File = struct { owns_file_handle: bool, options: Options, called: std.StringHashMap(void), + need_stddef: bool = false, + need_stdint: bool = false, pub fn makeWritable(self: *File.C, dir: fs.Dir, sub_path: []const u8) !void { assert(self.owns_file_handle); @@ -242,10 +244,21 @@ pub const File = struct { pub fn flush(self: *File.C) !void { const writer = self.file.?.writer(); + var includes = false; + if (self.need_stddef) { + try writer.writeAll("#include \n"); + includes = true; + } + if (self.need_stdint) { + try writer.writeAll("#include \n"); + includes = true; + } + if (includes) { + try writer.writeByte('\n'); + } if (self.header.items.len > 0) { - try self.header.append('\n'); + try writer.print("{}\n", .{self.header.items}); } - try writer.writeAll(self.header.items); if (self.main.items.len > 1) { const last_two = self.main.items[self.main.items.len - 2 ..]; if (std.mem.eql(u8, last_two, "\n\n")) { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 88eb6c595325f170d165967f9882897e063496b1..9a28c25e276b48cbb8cd38626920acdc29e3f947 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -31,4 +31,55 @@ pub fn addCases(ctx: *TestContext) !void { \\_Noreturn void main(void) {} \\ ); + // TODO: implement return values + ctx.c11("inline asm", linux_x64, + \\fn exitGood() void { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ ); + \\} + \\ + \\export fn _start() noreturn { + \\ exitGood(); + \\} + , + \\#include + \\ + \\void exitGood(void); + \\ + \\_Noreturn void _start(void) { + \\ exitGood(); + \\} + \\ + \\void exitGood(void) { + \\ register size_t rax_constant __asm__("rax") = 231; + \\ register size_t rdi_constant __asm__("rdi") = 0; + \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\} + \\ + ); + //ctx.c11("basic return", linux_x64, + // \\fn main() u8 { + // \\ return 103; + // \\} + // \\ + // \\export fn _start() noreturn { + // \\ _ = main(); + // \\} + //, + // \\#include + // \\ + // \\uint8_t main(void); + // \\ + // \\_Noreturn void _start(void) { + // \\ (void)main(); + // \\} + // \\ + // \\uint8_t main(void) { + // \\ return 103; + // \\} + // \\ + //); } -- 2.54.0 From 5461c482d0643abd83ac834be0a95da066c53c69 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 21:54:34 -0400 Subject: [PATCH 206/295] CBE: Integrate into stage2 via --c-standard --- src-self-hosted/main.zig | 106 +++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 33f422692c9209bf2542af227d1b86256817fca1..fdbc5fc1aef599eeb4479dbb550d7ebbffd4c5e4 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -71,7 +71,7 @@ pub fn main() !void { const args = try process.argsAlloc(arena); if (args.len <= 1) { - std.debug.warn("expected command argument\n\n{}", .{usage}); + std.debug.print("expected command argument\n\n{}", .{usage}); process.exit(1); } @@ -91,14 +91,14 @@ pub fn main() !void { return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target); } else if (mem.eql(u8, cmd, "version")) { // Need to set up the build script to give the version as a comptime value. - std.debug.warn("TODO version command not implemented yet\n", .{}); + std.debug.print("TODO version command not implemented yet\n", .{}); return error.Unimplemented; } else if (mem.eql(u8, cmd, "zen")) { try io.getStdOut().writeAll(info_zen); } else if (mem.eql(u8, cmd, "help")) { try io.getStdOut().writeAll(usage); } else { - std.debug.warn("unknown command: {}\n\n{}", .{ args[1], usage }); + std.debug.print("unknown command: {}\n\n{}", .{ args[1], usage }); process.exit(1); } } @@ -191,6 +191,7 @@ fn buildOutputType( var emit_zir: Emit = .no; var target_arch_os_abi: []const u8 = "native"; var target_mcpu: ?[]const u8 = null; + var target_c_standard: ?Module.CStandard = null; var target_dynamic_linker: ?[]const u8 = null; var system_libs = std.ArrayList([]const u8).init(gpa); @@ -206,7 +207,7 @@ fn buildOutputType( process.exit(0); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { - std.debug.warn("expected [auto|on|off] after --color\n", .{}); + std.debug.print("expected [auto|on|off] after --color\n", .{}); process.exit(1); } i += 1; @@ -218,12 +219,12 @@ fn buildOutputType( } else if (mem.eql(u8, next_arg, "off")) { color = .Off; } else { - std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); + std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); process.exit(1); } } else if (mem.eql(u8, arg, "--mode")) { if (i + 1 >= args.len) { - std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{}); + std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{}); process.exit(1); } i += 1; @@ -237,52 +238,64 @@ fn buildOutputType( } else if (mem.eql(u8, next_arg, "ReleaseSmall")) { build_mode = .ReleaseSmall; } else { - std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg}); + std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg}); process.exit(1); } } else if (mem.eql(u8, arg, "--name")) { if (i + 1 >= args.len) { - std.debug.warn("expected parameter after --name\n", .{}); + std.debug.print("expected parameter after --name\n", .{}); process.exit(1); } i += 1; provided_name = args[i]; } else if (mem.eql(u8, arg, "--library")) { if (i + 1 >= args.len) { - std.debug.warn("expected parameter after --library\n", .{}); + std.debug.print("expected parameter after --library\n", .{}); process.exit(1); } i += 1; try system_libs.append(args[i]); } else if (mem.eql(u8, arg, "--version")) { if (i + 1 >= args.len) { - std.debug.warn("expected parameter after --version\n", .{}); + std.debug.print("expected parameter after --version\n", .{}); process.exit(1); } i += 1; version = std.builtin.Version.parse(args[i]) catch |err| { - std.debug.warn("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) }); + std.debug.print("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) }); process.exit(1); }; } else if (mem.eql(u8, arg, "-target")) { if (i + 1 >= args.len) { - std.debug.warn("expected parameter after -target\n", .{}); + std.debug.print("expected parameter after -target\n", .{}); process.exit(1); } i += 1; target_arch_os_abi = args[i]; } else if (mem.eql(u8, arg, "-mcpu")) { if (i + 1 >= args.len) { - std.debug.warn("expected parameter after -mcpu\n", .{}); + std.debug.print("expected parameter after -mcpu\n", .{}); process.exit(1); } i += 1; target_mcpu = args[i]; + } else if (mem.eql(u8, arg, "--c-standard")) { + if (i + 1 >= args.len) { + std.debug.print("expected parameter after --c-standard\n", .{}); + process.exit(1); + } + i += 1; + if (std.meta.stringToEnum(Module.CStandard, args[i])) |cstd| { + target_c_standard = cstd; + } else { + std.debug.print("Invalid C standard: {}\n", .{args[i]}); + process.exit(1); + } } else if (mem.startsWith(u8, arg, "-mcpu=")) { target_mcpu = arg["-mcpu=".len..]; } else if (mem.eql(u8, arg, "--dynamic-linker")) { if (i + 1 >= args.len) { - std.debug.warn("expected parameter after --dynamic-linker\n", .{}); + std.debug.print("expected parameter after --dynamic-linker\n", .{}); process.exit(1); } i += 1; @@ -324,56 +337,61 @@ fn buildOutputType( } else if (mem.startsWith(u8, arg, "-l")) { try system_libs.append(arg[2..]); } else { - std.debug.warn("unrecognized parameter: '{}'", .{arg}); + std.debug.print("unrecognized parameter: '{}'", .{arg}); process.exit(1); } } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) { - std.debug.warn("assembly files not supported yet", .{}); + std.debug.print("assembly files not supported yet", .{}); process.exit(1); } else if (mem.endsWith(u8, arg, ".o") or mem.endsWith(u8, arg, ".obj") or mem.endsWith(u8, arg, ".a") or mem.endsWith(u8, arg, ".lib")) { - std.debug.warn("object files and static libraries not supported yet", .{}); + std.debug.print("object files and static libraries not supported yet", .{}); process.exit(1); } else if (mem.endsWith(u8, arg, ".c") or mem.endsWith(u8, arg, ".cpp")) { - std.debug.warn("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{}); + std.debug.print("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{}); process.exit(1); } else if (mem.endsWith(u8, arg, ".so") or mem.endsWith(u8, arg, ".dylib") or mem.endsWith(u8, arg, ".dll")) { - std.debug.warn("linking against dynamic libraries not yet supported", .{}); + std.debug.print("linking against dynamic libraries not yet supported", .{}); process.exit(1); } else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) { if (root_src_file) |other| { - std.debug.warn("found another zig file '{}' after root source file '{}'", .{ arg, other }); + std.debug.print("found another zig file '{}' after root source file '{}'", .{ arg, other }); process.exit(1); } else { root_src_file = arg; } } else { - std.debug.warn("unrecognized file extension of parameter '{}'", .{arg}); + std.debug.print("unrecognized file extension of parameter '{}'", .{arg}); } } } + if (target_c_standard != null and output_mode != .Obj) { + std.debug.print("The C backend must be used with build-obj\n", .{}); + process.exit(1); + } + const root_name = if (provided_name) |n| n else blk: { if (root_src_file) |file| { const basename = fs.path.basename(file); var it = mem.split(basename, "."); break :blk it.next() orelse basename; } else { - std.debug.warn("--name [name] not provided and unable to infer\n", .{}); + std.debug.print("--name [name] not provided and unable to infer\n", .{}); process.exit(1); } }; if (system_libs.items.len != 0) { - std.debug.warn("linking against system libraries not yet supported", .{}); + std.debug.print("linking against system libraries not yet supported", .{}); process.exit(1); } @@ -385,17 +403,17 @@ fn buildOutputType( .diagnostics = &diags, }) catch |err| switch (err) { error.UnknownCpuModel => { - std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{ + std.debug.print("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{ diags.cpu_name.?, @tagName(diags.arch.?), }); for (diags.arch.?.allCpuModels()) |cpu| { - std.debug.warn(" {}\n", .{cpu.name}); + std.debug.print(" {}\n", .{cpu.name}); } process.exit(1); }, error.UnknownCpuFeature => { - std.debug.warn( + std.debug.print( \\Unknown CPU feature: '{}' \\Available CPU features for architecture '{}': \\ @@ -404,7 +422,7 @@ fn buildOutputType( @tagName(diags.arch.?), }); for (diags.arch.?.allFeaturesList()) |feature| { - std.debug.warn(" {}: {}\n", .{ feature.name, feature.description }); + std.debug.print(" {}: {}\n", .{ feature.name, feature.description }); } process.exit(1); }, @@ -416,21 +434,22 @@ fn buildOutputType( if (target_info.cpu_detection_unimplemented) { // TODO We want to just use detected_info.target but implementing // CPU model & feature detection is todo so here we rely on LLVM. - std.debug.warn("CPU features detection is not yet available for this system without LLVM extensions\n", .{}); + std.debug.print("CPU features detection is not yet available for this system without LLVM extensions\n", .{}); process.exit(1); } const src_path = root_src_file orelse { - std.debug.warn("expected at least one file argument", .{}); + std.debug.print("expected at least one file argument", .{}); process.exit(1); }; const bin_path = switch (emit_bin) { .no => { - std.debug.warn("-fno-emit-bin not supported yet", .{}); + std.debug.print("-fno-emit-bin not supported yet", .{}); process.exit(1); }, - .yes_default_path => try std.zig.binNameAlloc(arena, root_name, target_info.target, output_mode, link_mode), + .yes_default_path => try std.fmt.allocPrint(arena, "{}.c", .{root_name}), + .yes => |p| p, }; @@ -460,6 +479,7 @@ fn buildOutputType( .object_format = object_format, .optimize_mode = build_mode, .keep_source_files_loaded = zir_out_path != null, + .c_standard = target_c_standard, }); defer module.deinit(); @@ -506,7 +526,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo if (errors.list.len != 0) { for (errors.list) |full_err_msg| { - std.debug.warn("{}:{}:{}: error: {}\n", .{ + std.debug.print("{}:{}:{}: error: {}\n", .{ full_err_msg.src_path, full_err_msg.line + 1, full_err_msg.column + 1, @@ -583,7 +603,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { process.exit(0); } else if (mem.eql(u8, arg, "--color")) { if (i + 1 >= args.len) { - std.debug.warn("expected [auto|on|off] after --color\n", .{}); + std.debug.print("expected [auto|on|off] after --color\n", .{}); process.exit(1); } i += 1; @@ -595,7 +615,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { } else if (mem.eql(u8, next_arg, "off")) { color = .Off; } else { - std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); + std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg}); process.exit(1); } } else if (mem.eql(u8, arg, "--stdin")) { @@ -603,7 +623,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { } else if (mem.eql(u8, arg, "--check")) { check_flag = true; } else { - std.debug.warn("unrecognized parameter: '{}'", .{arg}); + std.debug.print("unrecognized parameter: '{}'", .{arg}); process.exit(1); } } else { @@ -614,7 +634,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { if (stdin_flag) { if (input_files.items.len != 0) { - std.debug.warn("cannot use --stdin with positional arguments\n", .{}); + std.debug.print("cannot use --stdin with positional arguments\n", .{}); process.exit(1); } @@ -624,7 +644,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { defer gpa.free(source_code); const tree = std.zig.parse(gpa, source_code) catch |err| { - std.debug.warn("error parsing stdin: {}\n", .{err}); + std.debug.print("error parsing stdin: {}\n", .{err}); process.exit(1); }; defer tree.deinit(); @@ -647,7 +667,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { } if (input_files.items.len == 0) { - std.debug.warn("expected at least one source file argument\n", .{}); + std.debug.print("expected at least one source file argument\n", .{}); process.exit(1); } @@ -664,7 +684,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void { for (input_files.span()) |file_path| { // Get the real path here to avoid Windows failing on relative file paths with . or .. in them. const real_path = fs.realpathAlloc(gpa, file_path) catch |err| { - std.debug.warn("unable to open '{}': {}\n", .{ file_path, err }); + std.debug.print("unable to open '{}': {}\n", .{ file_path, err }); process.exit(1); }; defer gpa.free(real_path); @@ -702,7 +722,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_ fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) { error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path), else => { - std.debug.warn("unable to format '{}': {}\n", .{ file_path, err }); + std.debug.print("unable to format '{}': {}\n", .{ file_path, err }); fmt.any_error = true; return; }, @@ -733,7 +753,7 @@ fn fmtPathDir( try fmtPathDir(fmt, full_path, check_mode, dir, entry.name); } else { fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| { - std.debug.warn("unable to format '{}': {}\n", .{ full_path, err }); + std.debug.print("unable to format '{}': {}\n", .{ full_path, err }); fmt.any_error = true; return; }; @@ -784,7 +804,7 @@ fn fmtPathFile( if (check_mode) { const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree); if (anything_changed) { - std.debug.warn("{}\n", .{file_path}); + std.debug.print("{}\n", .{file_path}); fmt.any_error = true; } } else { @@ -800,7 +820,7 @@ fn fmtPathFile( try af.file.writeAll(fmt.out_buffer.items); try af.finish(); - std.debug.warn("{}\n", .{file_path}); + std.debug.print("{}\n", .{file_path}); } } -- 2.54.0 From b91cf1597267275dbbf57551acbe0461216ff08e Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 22:57:34 -0400 Subject: [PATCH 207/295] CBE: Move standards determination to generated code --- src-self-hosted/Module.zig | 10 ++------- src-self-hosted/cbe.h | 8 ++++++++ src-self-hosted/cgen.zig | 9 +++++--- src-self-hosted/link.zig | 8 +++++--- src-self-hosted/main.zig | 20 +++++------------- src-self-hosted/test.zig | 42 +++++++++++++++++++------------------- test/stage2/cbe.zig | 20 +++++++++--------- 7 files changed, 57 insertions(+), 60 deletions(-) create mode 100644 src-self-hosted/cbe.h diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index ac7bd0017099d8fdffbef054f4445c10cdc44aae..2ef85c4d5025e7629bf3178e18ffe12595d57fe6 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -722,12 +722,6 @@ pub const AllErrors = struct { } }; -pub const CStandard = enum { - C99, - GNU99, - C11, -}; - pub const InitOptions = struct { target: std.Target, root_pkg: *Package, @@ -738,7 +732,7 @@ pub const InitOptions = struct { object_format: ?std.builtin.ObjectFormat = null, optimize_mode: std.builtin.Mode = .Debug, keep_source_files_loaded: bool = false, - c_standard: ?CStandard = null, + cbe: bool = false, }; pub fn init(gpa: *Allocator, options: InitOptions) !Module { @@ -748,7 +742,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .output_mode = options.output_mode, .link_mode = options.link_mode orelse .Static, .object_format = options.object_format orelse options.target.getObjectFormat(), - .c_standard = options.c_standard, + .cbe = options.cbe, }); errdefer bin_file.*.deinit(); diff --git a/src-self-hosted/cbe.h b/src-self-hosted/cbe.h new file mode 100644 index 0000000000000000000000000000000000000000..85b07eb48a8ac67f98463e766faf6f6611f23d02 --- /dev/null +++ b/src-self-hosted/cbe.h @@ -0,0 +1,8 @@ +#if __STDC_VERSION__ >= 201112L +#define noreturn _Noreturn +#elif !__STRICT_ANSI__ +#define noreturn __attribute__ ((noreturn)) +#else +#define noreturn +#endif + diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 5b04d98119790f4a82f29aeeb46f4cb29f1f455f..872fa3e830e1e7f6bf298a061a5a768d5093afa5 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -7,7 +7,6 @@ const std = @import("std"); const C = link.File.C; const Decl = Module.Decl; -const CStandard = Module.CStandard; const mem = std.mem; /// Maps a name from Zig source to C. This will always give the same output for @@ -22,7 +21,10 @@ fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type) !void { try writer.writeAll("size_t"); } else { switch (T.zigTypeTag()) { - .NoReturn => try writer.writeAll("_Noreturn void"), + .NoReturn => { + file.need_noreturn = true; + try writer.writeAll("noreturn void"); + }, .Void => try writer.writeAll("void"), else => return error.Unimplemented, } @@ -41,13 +43,14 @@ fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *De } } -pub fn generate(file: *C, decl: *Decl, standard: CStandard) !void { +pub fn generate(file: *C, decl: *Decl) !void { const writer = file.main.writer(); const header = file.header.writer(); const tv = decl.typed_value.most_recent.typed_value; switch (tv.ty.zigTypeTag()) { .Fn => { try renderFunctionSignature(file, writer, decl); + try writer.writeAll(" {"); const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 8352f64fcaa6cd84fe1565816889a69d6cd2d78f..0efab9657f9dff342db20767a119b1db5282a173 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -22,7 +22,7 @@ pub const Options = struct { /// Used for calculating how much space to reserve for executable program code in case /// the binary file deos not already have such a section. program_code_size_hint: u64 = 256 * 1024, - c_standard: ?Module.CStandard = null, + cbe: bool = false, }; /// Attempts incremental linking, if the file already exists. @@ -38,7 +38,7 @@ pub fn openBinFilePath( const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) }); errdefer file.close(); - if (options.c_standard) |cstd| { + if (options.cbe) { var bin_file = try allocator.create(File.C); errdefer allocator.destroy(bin_file); bin_file.* = try openCFile(allocator, file, options); @@ -217,6 +217,7 @@ pub const File = struct { called: std.StringHashMap(void), need_stddef: bool = false, need_stdint: bool = false, + need_noreturn: bool = false, pub fn makeWritable(self: *File.C, dir: fs.Dir, sub_path: []const u8) !void { assert(self.owns_file_handle); @@ -239,11 +240,12 @@ pub const File = struct { } pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { - try cgen.generate(self, decl, self.options.c_standard.?); + try cgen.generate(self, decl); } pub fn flush(self: *File.C) !void { const writer = self.file.?.writer(); + try writer.writeAll(@embedFile("cbe.h")); var includes = false; if (self.need_stddef) { try writer.writeAll("#include \n"); diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index fdbc5fc1aef599eeb4479dbb550d7ebbffd4c5e4..fc02a57cc8c53ed8d91a9bf268f6be5b1922e5cf 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -191,7 +191,7 @@ fn buildOutputType( var emit_zir: Emit = .no; var target_arch_os_abi: []const u8 = "native"; var target_mcpu: ?[]const u8 = null; - var target_c_standard: ?Module.CStandard = null; + var cbe: bool = false; var target_dynamic_linker: ?[]const u8 = null; var system_libs = std.ArrayList([]const u8).init(gpa); @@ -279,18 +279,8 @@ fn buildOutputType( } i += 1; target_mcpu = args[i]; - } else if (mem.eql(u8, arg, "--c-standard")) { - if (i + 1 >= args.len) { - std.debug.print("expected parameter after --c-standard\n", .{}); - process.exit(1); - } - i += 1; - if (std.meta.stringToEnum(Module.CStandard, args[i])) |cstd| { - target_c_standard = cstd; - } else { - std.debug.print("Invalid C standard: {}\n", .{args[i]}); - process.exit(1); - } + } else if (mem.eql(u8, arg, "--c")) { + cbe = true; } else if (mem.startsWith(u8, arg, "-mcpu=")) { target_mcpu = arg["-mcpu=".len..]; } else if (mem.eql(u8, arg, "--dynamic-linker")) { @@ -374,7 +364,7 @@ fn buildOutputType( } } - if (target_c_standard != null and output_mode != .Obj) { + if (cbe and output_mode != .Obj) { std.debug.print("The C backend must be used with build-obj\n", .{}); process.exit(1); } @@ -479,7 +469,7 @@ fn buildOutputType( .object_format = object_format, .optimize_mode = build_mode, .keep_source_files_loaded = zir_out_path != null, - .c_standard = target_c_standard, + .cbe = cbe, }); defer module.deinit(); diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 80e40900a0e3f0a5725fe30d4d64550a872f34c1..6eca7ab651a0796c09b5508bb5198359963f9cf3 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -5,6 +5,8 @@ const Allocator = std.mem.Allocator; const zir = @import("zir.zig"); const Package = @import("Package.zig"); +const cheader = @embedFile("cbe.h"); + test "self-hosted" { var ctx = TestContext.init(); defer ctx.deinit(); @@ -68,7 +70,7 @@ pub const TestContext = struct { output_mode: std.builtin.OutputMode, updates: std.ArrayList(Update), extension: TestType, - c_standard: ?Module.CStandard = null, + cbe: bool = false, /// Adds a subcase in which the module is updated with `src`, and the /// resulting ZIR is validated against `result`. @@ -188,20 +190,20 @@ pub const TestContext = struct { return ctx.addObj(name, target, .ZIR); } - pub fn addC(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, T: TestType, standard: Module.CStandard) *Case { + pub fn addC(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, T: TestType) *Case { ctx.cases.append(Case{ .name = name, .target = target, .updates = std.ArrayList(Update).init(ctx.cases.allocator), .output_mode = .Obj, .extension = T, - .c_standard = standard, + .cbe = true, }) catch unreachable; return &ctx.cases.items[ctx.cases.items.len - 1]; } - pub fn c11(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, c: [:0]const u8) void { - ctx.addC(name, target, .Zig, .C11).addTransform(src, c); + pub fn c(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { + ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out); } pub fn addCompareOutput( @@ -382,13 +384,13 @@ pub const TestContext = struct { } fn deinit(self: *TestContext) void { - for (self.cases.items) |c| { - for (c.updates.items) |u| { + for (self.cases.items) |case| { + for (case.updates.items) |u| { if (u.case == .Error) { - c.updates.allocator.free(u.case.Error); + case.updates.allocator.free(u.case.Error); } } - c.updates.deinit(); + case.updates.deinit(); } self.cases.deinit(); self.* = undefined; @@ -442,7 +444,7 @@ pub const TestContext = struct { .bin_file_path = bin_name, .root_pkg = root_pkg, .keep_source_files_loaded = true, - .c_standard = case.c_standard, + .cbe = case.cbe, }); defer module.deinit(); @@ -477,24 +479,22 @@ pub const TestContext = struct { switch (update.case) { .Transformation => |expected_output| { - var label: []const u8 = "ZIR"; - if (case.c_standard) |cstd| { - label = @tagName(cstd); - var c: *link.File.C = module.bin_file.cast(link.File.C).?; - c.file.?.close(); - c.file = null; + if (case.cbe) { + var cfile: *link.File.C = module.bin_file.cast(link.File.C).?; + cfile.file.?.close(); + cfile.file = null; var file = try tmp.dir.openFile(bin_name, .{ .read = true }); defer file.close(); var out = file.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!"); defer allocator.free(out); if (expected_output.len != out.len) { - std.debug.warn("\nTransformed {} length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ label, expected_output, out }); + std.debug.warn("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); std.process.exit(1); } for (expected_output) |e, i| { if (out[i] != e) { - std.debug.warn("\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ label, expected_output, out }); + std.debug.warn("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); std.process.exit(1); } } @@ -518,12 +518,12 @@ pub const TestContext = struct { defer test_node.end(); if (expected_output.len != out_zir.items.len) { - std.debug.warn("{}\nTransformed {} length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); + std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); std.process.exit(1); } for (expected_output) |e, i| { if (out_zir.items[i] != e) { - std.debug.warn("{}\nTransformed {} differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, label, expected_output, out_zir.items }); + std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items }); std.process.exit(1); } } @@ -561,7 +561,7 @@ pub const TestContext = struct { } }, .Execution => |expected_stdout| { - std.debug.assert(case.c_standard == null); + std.debug.assert(!case.cbe); update_node.estimated_total_items = 4; var exec_result = x: { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 9a28c25e276b48cbb8cd38626920acdc29e3f947..e56507bd8450607c5ebe70926da5ff0dbbbc3321 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -9,30 +9,30 @@ const linux_x64 = std.zig.CrossTarget{ }; pub fn addCases(ctx: *TestContext) !void { - ctx.c11("empty start function", linux_x64, + ctx.c("empty start function", linux_x64, \\export fn _start() noreturn {} , - \\_Noreturn void _start(void) {} + \\noreturn void _start(void) {} \\ ); - ctx.c11("less empty start function", linux_x64, + ctx.c("less empty start function", linux_x64, \\fn main() noreturn {} \\ \\export fn _start() noreturn { \\ main(); \\} , - \\_Noreturn void main(void); + \\noreturn void main(void); \\ - \\_Noreturn void _start(void) { + \\noreturn void _start(void) { \\ main(); \\} \\ - \\_Noreturn void main(void) {} + \\noreturn void main(void) {} \\ ); // TODO: implement return values - ctx.c11("inline asm", linux_x64, + ctx.c("inline asm", linux_x64, \\fn exitGood() void { \\ asm volatile ("syscall" \\ : @@ -49,7 +49,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ \\void exitGood(void); \\ - \\_Noreturn void _start(void) { + \\noreturn void _start(void) { \\ exitGood(); \\} \\ @@ -60,7 +60,7 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ ); - //ctx.c11("basic return", linux_x64, + //ctx.c("basic return", linux_x64, // \\fn main() u8 { // \\ return 103; // \\} @@ -73,7 +73,7 @@ pub fn addCases(ctx: *TestContext) !void { // \\ // \\uint8_t main(void); // \\ - // \\_Noreturn void _start(void) { + // \\noreturn void _start(void) { // \\ (void)main(); // \\} // \\ -- 2.54.0 From 5667a21b1e7b8aa2dfcefed81d2b594029a116db Mon Sep 17 00:00:00 2001 From: Vexu Date: Tue, 7 Jul 2020 23:31:38 +0300 Subject: [PATCH 208/295] fix missing check on extern variables with no type --- src/analyze.cpp | 4 ++++ test/compile_errors.zig | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/analyze.cpp b/src/analyze.cpp index 699b1212767d3bb1294f4b2d6490d21b441f4a22..afe0fe6849130ff4e9c5592910cdf8f4d6b12e59 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -4012,6 +4012,10 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var, bool allow_lazy) { } else if (!is_extern) { add_node_error(g, source_node, buf_sprintf("variables must be initialized")); implicit_type = g->builtin_types.entry_invalid; + } else if (explicit_type == nullptr) { + // extern variable without explicit type + add_node_error(g, source_node, buf_sprintf("unable to infer variable type")); + implicit_type = g->builtin_types.entry_invalid; } ZigType *type = explicit_type ? explicit_type : implicit_type; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index adf92800fe44634f62f7059a1e26aa83d543cc5d..611094c0509bba51ea33887fe5f37fff8b7267d9 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,15 @@ const tests = @import("tests.zig"); const std = @import("std"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add("extern variable has no type", + \\extern var foo; + \\pub export fn entry() void { + \\ foo; + \\} + , &[_][]const u8{ + "tmp.zig:1:1: error: unable to infer variable type", + }); + cases.add("@src outside function", \\comptime { \\ @src(); -- 2.54.0 From 173e6712417c8102ff6bbd28fd964b89eda14f36 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 23:11:17 -0400 Subject: [PATCH 209/295] CBE: Some cleanup --- src-self-hosted/link.zig | 88 ++++++++++++++++++++-------------------- src-self-hosted/main.zig | 5 --- src-self-hosted/test.zig | 2 +- 3 files changed, 44 insertions(+), 51 deletions(-) diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 0efab9657f9dff342db20767a119b1db5282a173..6625598d1142cc58f67c573f2b4dfbcf96c38a73 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -125,36 +125,34 @@ pub const File = struct { } pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { - try switch (base.tag) { - .Elf => @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path), - .C => @fieldParentPtr(C, "base", base).makeWritable(dir, sub_path), + switch (base.tag) { + .Elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path), + .C => return @fieldParentPtr(C, "base", base).makeWritable(dir, sub_path), else => unreachable, - }; + } } pub fn makeExecutable(base: *File) !void { - try switch (base.tag) { - .Elf => @fieldParentPtr(Elf, "base", base).makeExecutable(), + switch (base.tag) { + .Elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(), else => unreachable, - }; + } } pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void { - try switch (base.tag) { - .Elf => @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), - .C => @fieldParentPtr(C, "base", base).updateDecl(module, decl), + switch (base.tag) { + .Elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), + .C => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), else => unreachable, - }; + } } pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void { - try switch (base.tag) { - .Elf => @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), - .C => { - //TODO - }, + switch (base.tag) { + .Elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), + .C => {}, else => unreachable, - }; + } } pub fn deinit(base: *File) void { @@ -380,7 +378,7 @@ pub const File = struct { /// Returns how much room there is to grow in virtual address space. /// File offset relocation happens transparently, so it is not included in /// this calculation. - fn capacity(self: TextBlock, elf_file: File.Elf) u64 { + fn capacity(self: TextBlock, elf_file: Elf) u64 { const self_sym = elf_file.local_symbols.items[self.local_sym_index]; if (self.next) |next| { const next_sym = elf_file.local_symbols.items[next.local_sym_index]; @@ -391,7 +389,7 @@ pub const File = struct { } } - fn freeListEligible(self: TextBlock, elf_file: File.Elf) bool { + fn freeListEligible(self: TextBlock, elf_file: Elf) bool { // No need to keep a free list node for the last block. const next = self.next orelse return false; const self_sym = elf_file.local_symbols.items[self.local_sym_index]; @@ -408,7 +406,7 @@ pub const File = struct { sym_index: ?u32 = null, }; - pub fn deinit(self: *File.Elf) void { + pub fn deinit(self: *Elf) void { self.sections.deinit(self.allocator); self.program_headers.deinit(self.allocator); self.shstrtab.deinit(self.allocator); @@ -424,7 +422,7 @@ pub const File = struct { } } - pub fn makeExecutable(self: *File.Elf) !void { + pub fn makeExecutable(self: *Elf) !void { assert(self.owns_file_handle); if (self.file) |f| { f.close(); @@ -432,7 +430,7 @@ pub const File = struct { } } - pub fn makeWritable(self: *File.Elf, dir: fs.Dir, sub_path: []const u8) !void { + pub fn makeWritable(self: *Elf, dir: fs.Dir, sub_path: []const u8) !void { assert(self.owns_file_handle); if (self.file != null) return; self.file = try dir.createFile(sub_path, .{ @@ -443,7 +441,7 @@ pub const File = struct { } /// Returns end pos of collision, if any. - fn detectAllocCollision(self: *File.Elf, start: u64, size: u64) ?u64 { + fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32; const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); if (start < ehdr_size) @@ -488,7 +486,7 @@ pub const File = struct { return null; } - fn allocatedSize(self: *File.Elf, start: u64) u64 { + fn allocatedSize(self: *Elf, start: u64) u64 { var min_pos: u64 = std.math.maxInt(u64); if (self.shdr_table_offset) |off| { if (off > start and off < min_pos) min_pos = off; @@ -507,7 +505,7 @@ pub const File = struct { return min_pos - start; } - fn findFreeSpace(self: *File.Elf, object_size: u64, min_alignment: u16) u64 { + fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u16) u64 { var start: u64 = 0; while (self.detectAllocCollision(start, object_size)) |item_end| { start = mem.alignForwardGeneric(u64, item_end, min_alignment); @@ -515,7 +513,7 @@ pub const File = struct { return start; } - fn makeString(self: *File.Elf, bytes: []const u8) !u32 { + fn makeString(self: *Elf, bytes: []const u8) !u32 { try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1); const result = self.shstrtab.items.len; self.shstrtab.appendSliceAssumeCapacity(bytes); @@ -523,12 +521,12 @@ pub const File = struct { return @intCast(u32, result); } - fn getString(self: *File.Elf, str_off: u32) []const u8 { + fn getString(self: *Elf, str_off: u32) []const u8 { assert(str_off < self.shstrtab.items.len); return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off)); } - fn updateString(self: *File.Elf, old_str_off: u32, new_name: []const u8) !u32 { + fn updateString(self: *Elf, old_str_off: u32, new_name: []const u8) !u32 { const existing_name = self.getString(old_str_off); if (mem.eql(u8, existing_name, new_name)) { return old_str_off; @@ -536,7 +534,7 @@ pub const File = struct { return self.makeString(new_name); } - pub fn populateMissingMetadata(self: *File.Elf) !void { + pub fn populateMissingMetadata(self: *Elf) !void { const small_ptr = switch (self.ptr_width) { .p32 => true, .p64 => false, @@ -703,7 +701,7 @@ pub const File = struct { } /// Commit pending changes and write headers. - pub fn flush(self: *File.Elf) !void { + pub fn flush(self: *Elf) !void { const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); // Unfortunately these have to be buffered and done at the end because ELF does not allow @@ -839,7 +837,7 @@ pub const File = struct { assert(syms_sect.sh_info == self.local_symbols.items.len); } - fn writeElfHeader(self: *File.Elf) !void { + fn writeElfHeader(self: *Elf) !void { var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined; var index: usize = 0; @@ -960,7 +958,7 @@ pub const File = struct { try self.file.?.pwriteAll(hdr_buf[0..index], 0); } - fn freeTextBlock(self: *File.Elf, text_block: *TextBlock) void { + fn freeTextBlock(self: *Elf, text_block: *TextBlock) void { var already_have_free_list_node = false; { var i: usize = 0; @@ -1000,12 +998,12 @@ pub const File = struct { } } - fn shrinkTextBlock(self: *File.Elf, text_block: *TextBlock, new_block_size: u64) void { + fn shrinkTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64) void { // TODO check the new capacity, and if it crosses the size threshold into a big enough // capacity, insert a free list node for it. } - fn growTextBlock(self: *File.Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { + fn growTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { const sym = self.local_symbols.items[text_block.local_sym_index]; const align_ok = mem.alignBackwardGeneric(u64, sym.st_value, alignment) == sym.st_value; const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*); @@ -1013,7 +1011,7 @@ pub const File = struct { return self.allocateTextBlock(text_block, new_block_size, alignment); } - fn allocateTextBlock(self: *File.Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { + fn allocateTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 { const phdr = &self.program_headers.items[self.phdr_load_re_index.?]; const shdr = &self.sections.items[self.text_section_index.?]; const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den; @@ -1127,7 +1125,7 @@ pub const File = struct { return vaddr; } - pub fn allocateDeclIndexes(self: *File.Elf, decl: *Module.Decl) !void { + pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { if (decl.link.local_sym_index != 0) return; // Here we also ensure capacity for the free lists so that they can be appended to without fail. @@ -1166,7 +1164,7 @@ pub const File = struct { self.offset_table.items[decl.link.offset_table_index] = 0; } - pub fn freeDecl(self: *File.Elf, decl: *Module.Decl) void { + pub fn freeDecl(self: *Elf, decl: *Module.Decl) void { self.freeTextBlock(&decl.link); if (decl.link.local_sym_index != 0) { self.local_symbol_free_list.appendAssumeCapacity(decl.link.local_sym_index); @@ -1178,7 +1176,7 @@ pub const File = struct { } } - pub fn updateDecl(self: *File.Elf, module: *Module, decl: *Module.Decl) !void { + pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void { var code_buffer = std.ArrayList(u8).init(self.allocator); defer code_buffer.deinit(); @@ -1258,7 +1256,7 @@ pub const File = struct { /// Must be called only after a successful call to `updateDecl`. pub fn updateDeclExports( - self: *File.Elf, + self: *Elf, module: *Module, decl: *const Module.Decl, exports: []const *Module.Export, @@ -1331,13 +1329,13 @@ pub const File = struct { } } - pub fn deleteExport(self: *File.Elf, exp: Export) void { + pub fn deleteExport(self: *Elf, exp: Export) void { const sym_index = exp.sym_index orelse return; self.global_symbol_free_list.appendAssumeCapacity(sym_index); self.global_symbols.items[sym_index].st_info = 0; } - fn writeProgHeader(self: *File.Elf, index: usize) !void { + fn writeProgHeader(self: *Elf, index: usize) !void { const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); const offset = self.program_headers.items[index].p_offset; switch (self.options.target.cpu.arch.ptrBitWidth()) { @@ -1359,7 +1357,7 @@ pub const File = struct { } } - fn writeSectHeader(self: *File.Elf, index: usize) !void { + fn writeSectHeader(self: *Elf, index: usize) !void { const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); const offset = self.sections.items[index].sh_offset; switch (self.options.target.cpu.arch.ptrBitWidth()) { @@ -1382,7 +1380,7 @@ pub const File = struct { } } - fn writeOffsetTableEntry(self: *File.Elf, index: usize) !void { + fn writeOffsetTableEntry(self: *Elf, index: usize) !void { const shdr = &self.sections.items[self.got_section_index.?]; const phdr = &self.program_headers.items[self.phdr_got_index.?]; const entry_size: u16 = switch (self.ptr_width) { @@ -1426,7 +1424,7 @@ pub const File = struct { } } - fn writeSymbol(self: *File.Elf, index: usize) !void { + fn writeSymbol(self: *Elf, index: usize) !void { const syms_sect = &self.sections.items[self.symtab_section_index.?]; // Make sure we are not pointlessly writing symbol data that will have to get relocated // due to running out of space. @@ -1482,7 +1480,7 @@ pub const File = struct { } } - fn writeAllGlobalSymbols(self: *File.Elf) !void { + fn writeAllGlobalSymbols(self: *Elf) !void { const syms_sect = &self.sections.items[self.symtab_section_index.?]; const sym_size: u64 = switch (self.ptr_width) { .p32 => @sizeOf(elf.Elf32_Sym), diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index fc02a57cc8c53ed8d91a9bf268f6be5b1922e5cf..3d859483a52bedf904e4d600002f7d44e435118f 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -364,11 +364,6 @@ fn buildOutputType( } } - if (cbe and output_mode != .Obj) { - std.debug.print("The C backend must be used with build-obj\n", .{}); - process.exit(1); - } - const root_name = if (provided_name) |n| n else blk: { if (root_src_file) |file| { const basename = fs.path.basename(file); diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 6eca7ab651a0796c09b5508bb5198359963f9cf3..0a320083699c89cde44c672de2dc9ce68a63efbb 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -66,7 +66,7 @@ pub const TestContext = struct { /// such as QEMU is required for tests to complete. target: std.zig.CrossTarget, /// In order to be able to run e.g. Execution updates, this must be set - /// to Executable. This is ignored when generating C output. + /// to Executable. output_mode: std.builtin.OutputMode, updates: std.ArrayList(Update), extension: TestType, -- 2.54.0 From 7a6104929b2d140235597d866dc0fabc93df036f Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 23:19:25 -0400 Subject: [PATCH 210/295] CBE: truncate output file --- src-self-hosted/link.zig | 27 +++++---------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 6625598d1142cc58f67c573f2b4dfbcf96c38a73..297b3d88008b104949bde0cf806e252c577321e6 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -35,14 +35,13 @@ pub fn openBinFilePath( sub_path: []const u8, options: Options, ) !*File { - const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) }); + const file = try dir.createFile(sub_path, .{ .truncate = options.cbe, .read = true, .mode = determineMode(options) }); errdefer file.close(); if (options.cbe) { var bin_file = try allocator.create(File.C); errdefer allocator.destroy(bin_file); bin_file.* = try openCFile(allocator, file, options); - bin_file.owns_file_handle = true; return &bin_file.base; } else { var bin_file = try allocator.create(File.Elf); @@ -88,17 +87,14 @@ pub fn writeFilePath( } pub fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C { - var self: File.C = .{ + return File.C{ .allocator = allocator, .file = file, .options = options, - .owns_file_handle = false, .main = std.ArrayList(u8).init(allocator), .header = std.ArrayList(u8).init(allocator), .called = std.StringHashMap(void).init(allocator), }; - errdefer self.deinit(); - return self; } /// Attempts incremental linking, if the file already exists. @@ -127,7 +123,7 @@ pub const File = struct { pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { switch (base.tag) { .Elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path), - .C => return @fieldParentPtr(C, "base", base).makeWritable(dir, sub_path), + .C => {}, else => unreachable, } } @@ -210,31 +206,18 @@ pub const File = struct { header: std.ArrayList(u8), main: std.ArrayList(u8), file: ?fs.File, - owns_file_handle: bool, options: Options, called: std.StringHashMap(void), need_stddef: bool = false, need_stdint: bool = false, need_noreturn: bool = false, - pub fn makeWritable(self: *File.C, dir: fs.Dir, sub_path: []const u8) !void { - assert(self.owns_file_handle); - if (self.file != null) return; - self.file = try dir.createFile(sub_path, .{ - .truncate = false, - .read = true, - .mode = determineMode(self.options), - }); - } - pub fn deinit(self: *File.C) void { self.main.deinit(); self.header.deinit(); self.called.deinit(); - if (self.owns_file_handle) { - if (self.file) |f| - f.close(); - } + if (self.file) |f| + f.close(); } pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { -- 2.54.0 From 089c056dbe1fdbb1da9b1a5bab970ca688d3aa01 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 23:24:30 -0400 Subject: [PATCH 211/295] CBE: Improve resource cleanup --- src-self-hosted/Module.zig | 5 ++--- src-self-hosted/link.zig | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 2ef85c4d5025e7629bf3178e18ffe12595d57fe6..9df02dd1863122148909efe2b7e555c42aae43d3 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -744,7 +744,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .object_format = options.object_format orelse options.target.getObjectFormat(), .cbe = options.cbe, }); - errdefer bin_file.*.deinit(); + errdefer bin_file.destroy(); const root_scope = blk: { if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zig")) { @@ -793,9 +793,8 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { } pub fn deinit(self: *Module) void { - self.bin_file.deinit(); + self.bin_file.destroy(); const allocator = self.allocator; - allocator.destroy(self.bin_file); self.deletion_set.deinit(allocator); self.work_queue.deinit(); diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 297b3d88008b104949bde0cf806e252c577321e6..da0128299534b0825df32028b9098863c760c0db 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -159,6 +159,22 @@ pub const File = struct { } } + pub fn destroy(base: *File) void { + switch (base.tag) { + .Elf => { + const parent = @fieldParentPtr(Elf, "base", base); + parent.deinit(); + parent.allocator.destroy(parent); + }, + .C => { + const parent = @fieldParentPtr(C, "base", base); + parent.deinit(); + parent.allocator.destroy(parent); + }, + else => unreachable, + } + } + pub fn flush(base: *File) !void { try switch (base.tag) { .Elf => @fieldParentPtr(Elf, "base", base).flush(), -- 2.54.0 From 9aaffe00d3450d2fabd5fbb6a099b3cdc56d04d2 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Tue, 7 Jul 2020 23:59:55 -0400 Subject: [PATCH 212/295] CBE: Cleanup unimplementeds --- src-self-hosted/cgen.zig | 42 +++++++++++++++++++--------------------- src-self-hosted/link.zig | 15 ++++++++++++-- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 872fa3e830e1e7f6bf298a061a5a768d5093afa5..0de28748c6ab6248d9aff12558ccd80aa2ca0c68 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -15,7 +15,7 @@ fn map(name: []const u8) ![]const u8 { return name; } -fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type) !void { +fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) !void { if (T.tag() == .usize) { file.need_stddef = true; try writer.writeAll("size_t"); @@ -26,20 +26,20 @@ fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type) !void { try writer.writeAll("noreturn void"); }, .Void => try writer.writeAll("void"), - else => return error.Unimplemented, + else => |e| return file.fail(src, "TODO implement type {}", .{e}), } } } fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; - try renderType(file, writer, tv.ty.fnReturnType()); + try renderType(file, writer, tv.ty.fnReturnType(), decl.src()); const name = try map(mem.spanZ(decl.name)); try writer.print(" {}(", .{name}); if (tv.ty.fnParamLen() == 0) { try writer.writeAll("void)"); } else { - return error.Unimplemented; + return file.fail(decl.src(), "TODO implement parameters", .{}); } } @@ -68,21 +68,21 @@ pub fn generate(file: *C, decl: *Decl) !void { if (arg.cast(ir.Inst.Constant)) |c| { if (c.val.tag() == .int_u64) { try writer.writeAll("register "); - try renderType(file, writer, arg.ty); + try renderType(file, writer, arg.ty, decl.src()); try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() }); } else { - return error.Unimplemented; + return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()}); } } else { - return error.Unimplemented; + return file.fail(decl.src(), "TODO non-constant inline asm args", .{}); } } else { - return error.Unimplemented; + return file.fail(decl.src(), "TODO non-explicit inline asm regs", .{}); } } try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); if (as.output) |o| { - return error.Unimplemented; + return file.fail(decl.src(), "TODO inline asm output", .{}); } if (as.inputs.len > 0) { if (as.output == null) { @@ -99,10 +99,12 @@ pub fn generate(file: *C, decl: *Decl) !void { if (arg.cast(ir.Inst.Constant)) |c| { try writer.print("\"\"({}_constant)", .{reg}); } else { - return error.Unimplemented; + // This is blocked by the earlier test + unreachable; } } else { - return error.Unimplemented; + // This is blocked by the earlier test + unreachable; } } } @@ -121,20 +123,17 @@ pub fn generate(file: *C, decl: *Decl) !void { } try writer.print("{}();", .{tname}); } else { - std.debug.warn("non-function call target?\n", .{}); - return error.Unimplemented; + return file.fail(decl.src(), "TODO non-function call target?", .{}); } if (call.args.len != 0) { - std.debug.warn("parameters\n", .{}); - return error.Unimplemented; + return file.fail(decl.src(), "TODO function arguments", .{}); } } else { - std.debug.warn("non-constant call inst?\n", .{}); - return error.Unimplemented; + return file.fail(decl.src(), "TODO non-constant call inst?", .{}); } }, - else => { - std.debug.warn("\nTranslating {}\n", .{inst.*}); + else => |e| { + return file.fail(decl.src(), "TODO {}", .{e}); }, } } @@ -151,13 +150,12 @@ pub fn generate(file: *C, decl: *Decl) !void { std.debug.warn("\n\nARRAYTRANS\n", .{}); if (tv.ty.arraySentinel()) |sentinel| {} } else { - return error.Unimplemented; + return file.fail(decl.src(), "TODO non-byte arrays", .{}); } } }, else => |e| { - std.debug.warn("\nTODO implement {}\n", .{e}); - return error.Unimplemented; + return file.fail(decl.src(), "TODO {}", .{e}); }, } } diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index da0128299534b0825df32028b9098863c760c0db..0683a741c5d61126a7e20fed6e7eafd993e25cac 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -227,6 +227,12 @@ pub const File = struct { need_stddef: bool = false, need_stdint: bool = false, need_noreturn: bool = false, + error_msg: *Module.ErrorMsg = undefined, + + pub fn fail(self: *C, src: usize, comptime format: []const u8, args: var) !void { + self.error_msg = try Module.ErrorMsg.create(self.allocator, src, format, args); + return error.CGenFailure; + } pub fn deinit(self: *File.C) void { self.main.deinit(); @@ -237,7 +243,12 @@ pub const File = struct { } pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { - try cgen.generate(self, decl); + cgen.generate(self, decl) catch |err| { + if (err == error.CGenFailure) { + try module.failed_decls.put(decl, self.error_msg); + } + return err; + }; } pub fn flush(self: *File.C) !void { @@ -1185,7 +1196,7 @@ pub const File = struct { .appended => code_buffer.items, .fail => |em| { decl.analysis = .codegen_failure; - _ = try module.failed_decls.put(decl, em); + try module.failed_decls.put(decl, em); return; }, }; -- 2.54.0 From e2aad33d4eafe482fa145dc971a423307ae83a39 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 8 Jul 2020 00:11:41 -0400 Subject: [PATCH 213/295] Stage2: facepalm. --- src-self-hosted/main.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 3d859483a52bedf904e4d600002f7d44e435118f..518be8c0234fceba44e0b072a12cfad8ed613448 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -433,7 +433,10 @@ fn buildOutputType( std.debug.print("-fno-emit-bin not supported yet", .{}); process.exit(1); }, - .yes_default_path => try std.fmt.allocPrint(arena, "{}.c", .{root_name}), + .yes_default_path => if (cbe) + try std.fmt.allocPrint(arena, "{}.c", .{root_name}) + else + try std.zig.binNameAlloc(arena, root_name, target_info.target, output_mode, link_mode), .yes => |p| p, }; -- 2.54.0 From 9d92d62525324453694190528021ab0216af0ed5 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 8 Jul 2020 00:33:44 -0400 Subject: [PATCH 214/295] CBE: Only try to use GNU attribute when __GNUC__is set --- src-self-hosted/cbe.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-self-hosted/cbe.h b/src-self-hosted/cbe.h index 85b07eb48a8ac67f98463e766faf6f6611f23d02..66e7b8bd3e5ac1910895880f983e9fa91aa59dc0 100644 --- a/src-self-hosted/cbe.h +++ b/src-self-hosted/cbe.h @@ -1,6 +1,6 @@ #if __STDC_VERSION__ >= 201112L #define noreturn _Noreturn -#elif !__STRICT_ANSI__ +#elif __GNUC__ && !__STRICT_ANSI__ #define noreturn __attribute__ ((noreturn)) #else #define noreturn -- 2.54.0 From ab9df5b04b862800ffee720635b6bec0185b5054 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Jul 2020 05:35:41 +0000 Subject: [PATCH 215/295] stage2: machine code for condbr jumps --- lib/std/math.zig | 5 -- src-self-hosted/codegen.zig | 124 +++++++++++++++++++++++++++--------- src-self-hosted/ir.zig | 3 + src-self-hosted/zir.zig | 29 +++++---- 4 files changed, 114 insertions(+), 47 deletions(-) diff --git a/lib/std/math.zig b/lib/std/math.zig index 799c42846b22233d6e529c505327b4034c99bd40..14ffd61c29ea15f20bda76128ff15d6fa52be388 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -1047,19 +1047,14 @@ pub fn order(a: var, b: var) Order { pub const CompareOperator = enum { /// Less than (`<`) lt, - /// Less than or equal (`<=`) lte, - /// Equal (`==`) eq, - /// Greater than or equal (`>=`) gte, - /// Greater than (`>`) gt, - /// Not equal (`!=`) neq, }; diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index d1b32f4ed4a875a130fdf293d9b37cdf3d43dfc9..b3fa94f08826401bc1ef8840c22bc88fcb7eb8e6 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -12,6 +12,18 @@ const Target = std.Target; const Allocator = mem.Allocator; const trace = @import("tracy.zig").trace; +/// The codegen-related data that is stored in `ir.Inst.Block` instructions. +pub const BlockData = struct { + relocs: std.ArrayListUnmanaged(Reloc) = .{}, +}; + +pub const Reloc = union(enum) { + /// The value is an offset into the `Function` `code` from the beginning. + /// To perform the reloc, write 32-bit signed little-endian integer + /// which is a relative jump, based on the address following the reloc. + rel32: usize, +}; + pub const Result = union(enum) { /// The `code` parameter passed to `generateSymbol` has the value appended. appended: void, @@ -290,9 +302,12 @@ const Function = struct { memory: u64, /// The value is one of the stack variables. stack_offset: u64, - /// The value is the compare flag, with this operator - /// applied on top of it. - compare_flag: std.math.CompareOperator, + /// The value is in the compare flags assuming an unsigned operation, + /// with this operator applied on top of it. + compare_flags_unsigned: std.math.CompareOperator, + /// The value is in the compare flags assuming a signed operation, + /// with this operator applied on top of it. + compare_flags_signed: std.math.CompareOperator, fn isMemory(mcv: MCValue) bool { return switch (mcv) { @@ -317,7 +332,8 @@ const Function = struct { .immediate, .embedded_in_code, .memory, - .compare_flag, + .compare_flags_unsigned, + .compare_flags_signed, => false, .register, @@ -513,7 +529,8 @@ const Function = struct { switch (dst_mcv) { .none => unreachable, .dead, .unreach, .immediate => unreachable, - .compare_flag => unreachable, + .compare_flags_unsigned => unreachable, + .compare_flags_signed => unreachable, .register => |dst_reg_usize| { const dst_reg = @intToEnum(Reg(.x86_64), @intCast(u8, dst_reg_usize)); switch (src_mcv) { @@ -546,8 +563,11 @@ const Function = struct { .embedded_in_code, .memory, .stack_offset => { return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{}); }, - .compare_flag => { - return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag", .{}); + .compare_flags_unsigned => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{}); + }, + .compare_flags_signed => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{}); }, } }, @@ -650,7 +670,12 @@ const Function = struct { const src_mcv = try self.limitImmediateType(inst.args.rhs, i32); try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38); - return MCValue{.compare_flag = inst.args.op}; + const info = inst.args.lhs.ty.intInfo(self.target.*); + if (info.signed) { + return MCValue{.compare_flags_signed = inst.args.op}; + } else { + return MCValue{.compare_flags_unsigned = inst.args.op}; + } }, else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), } @@ -658,8 +683,34 @@ const Function = struct { fn genCondBr(self: *Function, inst: *ir.Inst.CondBr, comptime arch: std.Target.Cpu.Arch) !MCValue { switch (arch) { + .i386, .x86_64 => { + try self.code.ensureCapacity(self.code.items.len + 6); + + const cond = try self.resolveInst(inst.args.condition); + switch (cond) { + .compare_flags_signed => |cmp_op| { + // Here we map to the opposite opcode because the jump is to the false branch. + const opcode: u8 = switch (cmp_op) { + .gte => 0x8c, + .gt => 0x8e, + .neq => 0x84, + .lt => 0x8d, + .lte => 0x8f, + .eq => 0x85, + }; + self.code.appendSliceAssumeCapacity(&[_]u8{0x0f, opcode}); + const reloc = Reloc{ .rel32 = self.code.items.len }; + self.code.items.len += 4; + try self.genBody(inst.args.true_body, arch); + try self.performReloc(inst.base.src, reloc); + try self.genBody(inst.args.false_body, arch); + }, + else => return self.fail(inst.base.src, "TODO implement condbr {} when condition not already in the compare flags", .{self.target.cpu.arch}), + } + }, else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}), } + return MCValue.unreach; } fn genIsNull(self: *Function, inst: *ir.Inst.IsNull, comptime arch: std.Target.Cpu.Arch) !MCValue { @@ -676,33 +727,43 @@ const Function = struct { } } - fn genRelativeFwdJump(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, amount: u32) !void { - switch (arch) { - .i386, .x86_64 => { - // TODO x86 treats the operands as signed - if (amount <= std.math.maxInt(u8)) { - try self.code.resize(self.code.items.len + 2); - self.code.items[self.code.items.len - 2] = 0xeb; - self.code.items[self.code.items.len - 1] = @intCast(u8, amount); - } else { - try self.code.resize(self.code.items.len + 5); - self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 - const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; - mem.writeIntLittle(u32, imm_ptr, amount); - } - }, - else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.target.cpu.arch}), - } - } - fn genBlock(self: *Function, inst: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { + if (inst.base.ty.hasCodeGenBits()) { + return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{}); + } // A block is nothing but a setup to be able to jump to the end. + defer inst.codegen.relocs.deinit(self.gpa); try self.genBody(inst.args.body, arch); - return self.fail(inst.base.src, "TODO process jump relocs after block end", .{}); + + for (inst.codegen.relocs.items) |reloc| try self.performReloc(inst.base.src, reloc); + + return MCValue.none; + } + + fn performReloc(self: *Function, src: usize, reloc: Reloc) !void { + switch (reloc) { + .rel32 => |pos| { + const amt = self.code.items.len - (pos + 4); + const s32_amt = std.math.cast(i32, amt) catch + return self.fail(src, "unable to perform relocation: jump too far", .{}); + mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt); + }, + } } fn genBreakVoid(self: *Function, inst: *ir.Inst.BreakVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { + // Emit a jump with a relocation. It will be patched up after the block ends. + try inst.args.block.codegen.relocs.ensureCapacity(self.gpa, inst.args.block.codegen.relocs.items.len + 1); + switch (arch) { + .i386, .x86_64 => { + // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction + // which is available if the jump is 127 bytes or less forward. + try self.code.resize(self.code.items.len + 5); + self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 + // Leave the jump offset undefined + inst.args.block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); + }, else => return self.fail(inst.base.src, "TODO implement breakvoid for {}", .{self.target.cpu.arch}), } return .none; @@ -776,8 +837,11 @@ const Function = struct { .dead => unreachable, .none => unreachable, .unreach => unreachable, - .compare_flag => |op| { - return self.fail(src, "TODO set register with compare flag value", .{}); + .compare_flags_unsigned => |op| { + return self.fail(src, "TODO set register with compare flags value (unsigned)", .{}); + }, + .compare_flags_signed => |op| { + return self.fail(src, "TODO set register with compare flags value (signed)", .{}); }, .immediate => |x| { if (reg.size() != 64) { diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 81b617c8a2c359dad001b171221001f62bc551ae..f2ba3801a175343fb881cdfc6a2e36eb16d2ad94 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -3,6 +3,7 @@ const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const Module = @import("Module.zig"); const assert = std.debug.assert; +const codegen = @import("codegen.zig"); /// These are in-memory, analyzed instructions. See `zir.Inst` for the representation /// of instructions that correspond to the ZIR text format. @@ -157,6 +158,8 @@ pub const Inst = struct { args: struct { body: Body, }, + /// This memory is reserved for codegen code to do whatever it needs to here. + codegen: codegen.BlockData = .{}, }; pub const Breakpoint = struct { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 8cc6cdaf05f19c4050a591f693eab6022e4014ce..0f36ac99f3cf07be81523d3339ef599d61b0c9bb 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -1589,22 +1589,27 @@ const EmitZIR = struct { const old_inst = inst.cast(ir.Inst.Block).?; const new_inst = try self.arena.allocator.create(Inst.Block); + // We do this now so that the break instructions within the block + // can find it. + try inst_table.put(&old_inst.base, &new_inst.base); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.Block.base_tag, + }, + .positionals = .{ + .label = try self.autoName(), + .body = undefined, + }, + .kw_args = .{}, + }; + var block_body = std.ArrayList(*Inst).init(self.allocator); defer block_body.deinit(); try self.emitBody(old_inst.args.body, inst_table, &block_body); + new_inst.positionals.body = .{ .instructions = block_body.toOwnedSlice() }; - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Block.base_tag, - }, - .positionals = .{ - .label = try self.autoName(), - .body = .{ .instructions = block_body.toOwnedSlice() }, - }, - .kw_args = .{}, - }; break :blk &new_inst.base; }, .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), @@ -1811,7 +1816,7 @@ const EmitZIR = struct { }, }; try instructions.append(new_inst); - try inst_table.putNoClobber(inst, new_inst); + try inst_table.put(inst, new_inst); } } -- 2.54.0 From 88496041312023948cfd34f6e96acc5d27ee4cf6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Jul 2020 05:44:51 +0000 Subject: [PATCH 216/295] stage2: proper indenting when printing ZIR text --- src-self-hosted/zir.zig | 90 ++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 0f36ac99f3cf07be81523d3339ef599d61b0c9bb..697c7673ac2f272aa8963287f2281382822248e6 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -648,7 +648,7 @@ pub const Module = struct { for (self.decls) |decl, i| { try stream.print("@{} ", .{decl.name}); - try self.writeInstToStream(stream, decl.inst, &inst_table); + try self.writeInstToStream(stream, decl.inst, &inst_table, 2); try stream.writeByte('\n'); } } @@ -658,43 +658,44 @@ pub const Module = struct { stream: var, inst: *Inst, inst_table: *const InstPtrTable, + indent: usize, ) @TypeOf(stream).Error!void { // TODO I tried implementing this with an inline for loop and hit a compiler bug switch (inst.tag) { - .arg => return self.writeInstToStreamGeneric(stream, .arg, inst, inst_table), - .block => return self.writeInstToStreamGeneric(stream, .block, inst, inst_table), - .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst, inst_table), - .breakvoid => return self.writeInstToStreamGeneric(stream, .breakvoid, inst, inst_table), - .call => return self.writeInstToStreamGeneric(stream, .call, inst, inst_table), - .declref => return self.writeInstToStreamGeneric(stream, .declref, inst, inst_table), - .declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst, inst_table), - .declval => return self.writeInstToStreamGeneric(stream, .declval, inst, inst_table), - .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst, inst_table), - .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst, inst_table), - .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst, inst_table), - .str => return self.writeInstToStreamGeneric(stream, .str, inst, inst_table), - .int => return self.writeInstToStreamGeneric(stream, .int, inst, inst_table), - .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst, inst_table), - .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst, inst_table), - .deref => return self.writeInstToStreamGeneric(stream, .deref, inst, inst_table), - .as => return self.writeInstToStreamGeneric(stream, .as, inst, inst_table), - .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", inst, inst_table), - .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", inst, inst_table), - .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", inst, inst_table), - .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, inst, inst_table), - .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", inst, inst_table), - .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", inst, inst_table), - .primitive => return self.writeInstToStreamGeneric(stream, .primitive, inst, inst_table), - .fntype => return self.writeInstToStreamGeneric(stream, .fntype, inst, inst_table), - .intcast => return self.writeInstToStreamGeneric(stream, .intcast, inst, inst_table), - .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst, inst_table), - .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst, inst_table), - .add => return self.writeInstToStreamGeneric(stream, .add, inst, inst_table), - .sub => return self.writeInstToStreamGeneric(stream, .sub, inst, inst_table), - .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst, inst_table), - .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst, inst_table), - .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst, inst_table), - .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, inst, inst_table), + .arg => return self.writeInstToStreamGeneric(stream, .arg, inst, inst_table, indent), + .block => return self.writeInstToStreamGeneric(stream, .block, inst, inst_table, indent), + .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst, inst_table, indent), + .breakvoid => return self.writeInstToStreamGeneric(stream, .breakvoid, inst, inst_table, indent), + .call => return self.writeInstToStreamGeneric(stream, .call, inst, inst_table, indent), + .declref => return self.writeInstToStreamGeneric(stream, .declref, inst, inst_table, indent), + .declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst, inst_table, indent), + .declval => return self.writeInstToStreamGeneric(stream, .declval, inst, inst_table, indent), + .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst, inst_table, indent), + .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst, inst_table, indent), + .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst, inst_table, indent), + .str => return self.writeInstToStreamGeneric(stream, .str, inst, inst_table, indent), + .int => return self.writeInstToStreamGeneric(stream, .int, inst, inst_table, indent), + .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst, inst_table, indent), + .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst, inst_table, indent), + .deref => return self.writeInstToStreamGeneric(stream, .deref, inst, inst_table, indent), + .as => return self.writeInstToStreamGeneric(stream, .as, inst, inst_table, indent), + .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", inst, inst_table, indent), + .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", inst, inst_table, indent), + .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", inst, inst_table, indent), + .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, inst, inst_table, indent), + .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", inst, inst_table, indent), + .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", inst, inst_table, indent), + .primitive => return self.writeInstToStreamGeneric(stream, .primitive, inst, inst_table, indent), + .fntype => return self.writeInstToStreamGeneric(stream, .fntype, inst, inst_table, indent), + .intcast => return self.writeInstToStreamGeneric(stream, .intcast, inst, inst_table, indent), + .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst, inst_table, indent), + .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst, inst_table, indent), + .add => return self.writeInstToStreamGeneric(stream, .add, inst, inst_table, indent), + .sub => return self.writeInstToStreamGeneric(stream, .sub, inst, inst_table, indent), + .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst, inst_table, indent), + .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst, inst_table, indent), + .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst, inst_table, indent), + .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, inst, inst_table, indent), } } @@ -704,7 +705,8 @@ pub const Module = struct { comptime inst_tag: Inst.Tag, base: *Inst, inst_table: *const InstPtrTable, - ) !void { + indent: usize, + ) @TypeOf(stream).Error!void { const SpecificInst = Inst.TagToType(inst_tag); const inst = @fieldParentPtr(SpecificInst, "base", base); const Positionals = @TypeOf(inst.positionals); @@ -714,7 +716,7 @@ pub const Module = struct { if (i != 0) { try stream.writeAll(", "); } - try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table); + try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table, indent); } comptime var need_comma = pos_fields.len != 0; @@ -724,13 +726,13 @@ pub const Module = struct { if (@field(inst.kw_args, arg_field.name)) |non_optional| { if (need_comma) try stream.writeAll(", "); try stream.print("{}=", .{arg_field.name}); - try self.writeParamToStream(stream, non_optional, inst_table); + try self.writeParamToStream(stream, non_optional, inst_table, indent); need_comma = true; } } else { if (need_comma) try stream.writeAll(", "); try stream.print("{}=", .{arg_field.name}); - try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table); + try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table, indent); need_comma = true; } } @@ -738,7 +740,7 @@ pub const Module = struct { try stream.writeByte(')'); } - fn writeParamToStream(self: Module, stream: var, param: var, inst_table: *const InstPtrTable) !void { + fn writeParamToStream(self: Module, stream: var, param: var, inst_table: *const InstPtrTable, indent: usize) !void { if (@typeInfo(@TypeOf(param)) == .Enum) { return stream.writeAll(@tagName(param)); } @@ -757,10 +759,12 @@ pub const Module = struct { Module.Body => { try stream.writeAll("{\n"); for (param.instructions) |inst, i| { - try stream.print(" %{} ", .{i}); - try self.writeInstToStream(stream, inst, inst_table); + try stream.writeByteNTimes(' ', indent); + try stream.print("%{} ", .{i}); + try self.writeInstToStream(stream, inst, inst_table, indent + 2); try stream.writeByte('\n'); } + try stream.writeByteNTimes(' ', indent - 2); try stream.writeByte('}'); }, bool => return stream.writeByte("01"[@boolToInt(param)]), @@ -1205,6 +1209,7 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { .next_auto_name = 0, .names = std.StringHashMap(void).init(allocator), .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), + .indent = 0, }; defer ctx.decls.deinit(allocator); defer ctx.names.deinit(); @@ -1227,6 +1232,7 @@ const EmitZIR = struct { names: std.StringHashMap(void), next_auto_name: usize, primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), + indent: usize, fn emit(self: *EmitZIR) !void { // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced -- 2.54.0 From 5e60872060da894c38343f2afc1ddc45bce14fc6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Jul 2020 06:56:20 +0000 Subject: [PATCH 217/295] stage2 misc fixes --- src-self-hosted/Module.zig | 13 ++++++++++--- src-self-hosted/codegen.zig | 33 +++++++++++++++++++++++---------- src-self-hosted/zir.zig | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index d40afebb64aedda62d9c825363224cd485fb89c5..0bd917b2b15e7b96576575d6f4406af9e6922592 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -867,8 +867,10 @@ pub fn update(self: *Module) !void { try self.deleteDecl(decl); } - // This is needed before reading the error flags. - try self.bin_file.flush(); + if (self.totalErrorCount() == 0) { + // This is needed before reading the error flags. + try self.bin_file.flush(); + } self.link_error_flags = self.bin_file.error_flags; std.log.debug(.module, "link_error_flags: {}\n", .{self.link_error_flags}); @@ -2388,6 +2390,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In const big_int = old_inst.cast(zir.Inst.Int).?.positionals.int; return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); }, + .inttype => return self.analyzeInstIntType(scope, old_inst.cast(zir.Inst.IntType).?), .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.cast(zir.Inst.PtrToInt).?), .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.cast(zir.Inst.FieldPtr).?), .deref => return self.analyzeInstDeref(scope, old_inst.cast(zir.Inst.Deref).?), @@ -2737,6 +2740,10 @@ fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError }); } +fn analyzeInstIntType(self: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst { + return self.fail(scope, inttype.base.src, "TODO implement inttype", .{}); +} + fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { const return_type = try self.resolveType(scope, fntype.positionals.return_type); @@ -3333,7 +3340,7 @@ fn cmpNumeric( break :blk try self.makeIntType(scope, dest_int_is_signed, casted_bits); }; const casted_lhs = try self.coerce(scope, dest_type, lhs); - const casted_rhs = try self.coerce(scope, dest_type, lhs); + const casted_rhs = try self.coerce(scope, dest_type, rhs); return self.addNewInstArgs(b, src, Type.initTag(.bool), Inst.Cmp, .{ .lhs = casted_lhs, diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index b3fa94f08826401bc1ef8840c22bc88fcb7eb8e6..232703b69723f425bb5a8b8d2d0d73069412c1d7 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -698,18 +698,34 @@ const Function = struct { .lte => 0x8f, .eq => 0x85, }; - self.code.appendSliceAssumeCapacity(&[_]u8{0x0f, opcode}); - const reloc = Reloc{ .rel32 = self.code.items.len }; - self.code.items.len += 4; - try self.genBody(inst.args.true_body, arch); - try self.performReloc(inst.base.src, reloc); - try self.genBody(inst.args.false_body, arch); + return self.genX86CondBr(inst, opcode, arch); + }, + .compare_flags_unsigned => |cmp_op| { + // Here we map to the opposite opcode because the jump is to the false branch. + const opcode: u8 = switch (cmp_op) { + .gte => 0x82, + .gt => 0x86, + .neq => 0x84, + .lt => 0x83, + .lte => 0x87, + .eq => 0x85, + }; + return self.genX86CondBr(inst, opcode, arch); }, else => return self.fail(inst.base.src, "TODO implement condbr {} when condition not already in the compare flags", .{self.target.cpu.arch}), } }, else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}), } + } + + fn genX86CondBr(self: *Function, inst: *ir.Inst.CondBr, opcode: u8, comptime arch: std.Target.Cpu.Arch) !MCValue { + self.code.appendSliceAssumeCapacity(&[_]u8{0x0f, opcode}); + const reloc = Reloc{ .rel32 = self.code.items.len }; + self.code.items.len += 4; + try self.genBody(inst.args.true_body, arch); + try self.performReloc(inst.base.src, reloc); + try self.genBody(inst.args.false_body, arch); return MCValue.unreach; } @@ -1028,10 +1044,7 @@ const Function = struct { const branch = &self.branch_stack.items[0]; const gop = try branch.inst_table.getOrPut(self.gpa, inst); if (!gop.found_existing) { - const mcv = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); - try branch.inst_table.putNoClobber(self.gpa, inst, mcv); - gop.entry.value = mcv; - return mcv; + gop.entry.value = try self.genTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val }); } return gop.entry.value; } diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 697c7673ac2f272aa8963287f2281382822248e6..ab48bf535f5d722ea2c124742b25863615eae504 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -57,6 +57,7 @@ pub const Inst = struct { /// String Literal. Makes an anonymous Decl and then takes a pointer to it. str, int, + inttype, ptrtoint, fieldptr, deref, @@ -95,6 +96,7 @@ pub const Inst = struct { .@"const" => Const, .str => Str, .int => Int, + .inttype => IntType, .ptrtoint => PtrToInt, .fieldptr => FieldPtr, .deref => Deref, @@ -369,6 +371,17 @@ pub const Inst = struct { }, }; + pub const IntType = struct { + pub const base_tag = Tag.inttype; + base: Inst, + + positionals: struct { + signed: *Inst, + bits: *Inst, + }, + kw_args: struct {}, + }; + pub const Export = struct { pub const base_tag = Tag.@"export"; base: Inst, @@ -675,6 +688,7 @@ pub const Module = struct { .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst, inst_table, indent), .str => return self.writeInstToStreamGeneric(stream, .str, inst, inst_table, indent), .int => return self.writeInstToStreamGeneric(stream, .int, inst, inst_table, indent), + .inttype => return self.writeInstToStreamGeneric(stream, .inttype, inst, inst_table, indent), .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst, inst_table, indent), .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst, inst_table, indent), .deref => return self.writeInstToStreamGeneric(stream, .deref, inst, inst_table, indent), @@ -1886,6 +1900,26 @@ const EmitZIR = struct { }; return self.emitUnnamedDecl(&fntype_inst.base); }, + .Int => { + const info = ty.intInfo(self.old_module.target()); + const signed = try self.emitPrimitive(src, if (info.signed) .@"true" else .@"false"); + const bits_payload = try self.arena.allocator.create(Value.Payload.Int_u64); + bits_payload.* = .{ .int = info.bits }; + const bits = try self.emitComptimeIntVal(src, Value.initPayload(&bits_payload.base)); + const inttype_inst = try self.arena.allocator.create(Inst.IntType); + inttype_inst.* = .{ + .base = .{ + .src = src, + .tag = Inst.IntType.base_tag, + }, + .positionals = .{ + .signed = signed.inst, + .bits = bits.inst, + }, + .kw_args = .{}, + }; + return self.emitUnnamedDecl(&inttype_inst.base); + }, else => std.debug.panic("TODO implement emitType for {}", .{ty}), }, } -- 2.54.0 From be0546d877e0df18201e73e803d8a966b57625c5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Jul 2020 07:04:43 +0000 Subject: [PATCH 218/295] stage2: implement compare operator AST->ZIR --- src-self-hosted/Module.zig | 27 +++++++++++++++++++++++++++ src-self-hosted/zir.zig | 2 ++ 2 files changed, 29 insertions(+) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 0bd917b2b15e7b96576575d6f4406af9e6922592..67c1b00a1ea7d50164b604605bf6207d55762750 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1309,6 +1309,33 @@ fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) In return self.addZIRInst(scope, src, zir.Inst.Add, .{ .lhs = lhs, .rhs = rhs }, .{}); }, + .BangEqual, + .EqualEqual, + .GreaterThan, + .GreaterOrEqual, + .LessThan, + .LessOrEqual, + => { + const lhs = try self.astGenExpr(scope, infix_node.lhs); + const rhs = try self.astGenExpr(scope, infix_node.rhs); + + const tree = scope.tree(); + const src = tree.token_locs[infix_node.op_token].start; + + return self.addZIRInst(scope, src, zir.Inst.Cmp, .{ + .lhs = lhs, + .op = @as(std.math.CompareOperator, switch (infix_node.op) { + .BangEqual => .neq, + .EqualEqual => .eq, + .GreaterThan => .gt, + .GreaterOrEqual => .gte, + .LessThan => .lt, + .LessOrEqual => .lte, + else => unreachable, + }), + .rhs = rhs, + }, .{}); + }, else => |op| { return self.failNode(scope, &infix_node.base, "TODO implement infix operator {}", .{op}); }, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index ab48bf535f5d722ea2c124742b25863615eae504..0221e4d330871086f01c5daf3915b7e2af78cd65 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -538,6 +538,8 @@ pub const Inst = struct { kw_args: struct {}, }; + /// TODO get rid of the op positional arg and make that data part of + /// the base Inst tag. pub const Cmp = struct { pub const base_tag = Tag.cmp; base: Inst, -- 2.54.0 From 6b4863416611253c68539136ae9e4b83359efe74 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 8 Jul 2020 14:05:07 -0400 Subject: [PATCH 219/295] CBE: Emit asm decls for now, but rename to make them valid --- src-self-hosted/Module.zig | 2 +- src-self-hosted/cgen.zig | 27 +++++++++++++++++---------- src-self-hosted/link.zig | 6 ++++++ test/stage2/cbe.zig | 5 +++++ test/stage2/zir.zig | 18 +++++++++--------- 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 9df02dd1863122148909efe2b7e555c42aae43d3..733aac7c714dbe142d22331fd3fcfce6ccbcbbc8 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2456,7 +2456,7 @@ fn createAnonymousDecl( ) !*Decl { const name_index = self.getNextAnonNameIndex(); const scope_decl = scope.decl().?; - const name = try std.fmt.allocPrint(self.allocator, "{}${}", .{ scope_decl.name, name_index }); + const name = try std.fmt.allocPrint(self.allocator, "{}__anon_{}", .{ scope_decl.name, name_index }); defer self.allocator.free(name); const name_hash = scope.namespace().fullyQualifiedNameHash(name); const src_hash: std.zig.SrcHash = undefined; diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 0de28748c6ab6248d9aff12558ccd80aa2ca0c68..75caddc131a1134b6a06b44d6c65bd91d5ed9b8e 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -11,8 +11,8 @@ const mem = std.mem; /// Maps a name from Zig source to C. This will always give the same output for /// any given input. -fn map(name: []const u8) ![]const u8 { - return name; +fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { + return allocator.dupe(u8, name); } fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) !void { @@ -34,7 +34,8 @@ fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) ! fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; try renderType(file, writer, tv.ty.fnReturnType(), decl.src()); - const name = try map(mem.spanZ(decl.name)); + const name = try map(file.allocator, mem.spanZ(decl.name)); + defer file.allocator.free(name); try writer.print(" {}(", .{name}); if (tv.ty.fnParamLen() == 0) { try writer.writeAll("void)"); @@ -143,15 +144,21 @@ pub fn generate(file: *C, decl: *Decl) !void { try writer.writeAll("}\n\n"); }, .Array => { - if (mem.indexOf(u8, mem.span(decl.name), "$") == null) { - // TODO: prevent inline asm constants from being emitted - if (tv.val.cast(Value.Payload.Bytes)) |payload| { - try writer.print("const char *const {} = \"{}\";\n", .{ decl.name, payload.data }); - std.debug.warn("\n\nARRAYTRANS\n", .{}); - if (tv.ty.arraySentinel()) |sentinel| {} + // TODO: prevent inline asm constants from being emitted + const name = try map(file.allocator, mem.span(decl.name)); + defer file.allocator.free(name); + if (tv.val.cast(Value.Payload.Bytes)) |payload| { + if (tv.ty.arraySentinel()) |sentinel| { + if (sentinel.toUnsignedInt() == 0) { + try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }); + } else { + return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{}); + } } else { - return file.fail(decl.src(), "TODO non-byte arrays", .{}); + return file.fail(decl.src(), "TODO byte arrays without sentinels", .{}); } + } else { + return file.fail(decl.src(), "TODO non-byte arrays", .{}); } }, else => |e| { diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 0683a741c5d61126a7e20fed6e7eafd993e25cac..2f21dcbfde3f6b427a9d3d350f8019babcf08a50 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -93,6 +93,7 @@ pub fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C .options = options, .main = std.ArrayList(u8).init(allocator), .header = std.ArrayList(u8).init(allocator), + .constants = std.ArrayList(u8).init(allocator), .called = std.StringHashMap(void).init(allocator), }; } @@ -220,6 +221,7 @@ pub const File = struct { allocator: *Allocator, header: std.ArrayList(u8), + constants: std.ArrayList(u8), main: std.ArrayList(u8), file: ?fs.File, options: Options, @@ -237,6 +239,7 @@ pub const File = struct { pub fn deinit(self: *File.C) void { self.main.deinit(); self.header.deinit(); + self.constants.deinit(); self.called.deinit(); if (self.file) |f| f.close(); @@ -269,6 +272,9 @@ pub const File = struct { if (self.header.items.len > 0) { try writer.print("{}\n", .{self.header.items}); } + if (self.constants.items.len > 0) { + try writer.print("{}\n", .{self.constants.items}); + } if (self.main.items.len > 1) { const last_two = self.main.items[self.main.items.len - 2 ..]; if (std.mem.eql(u8, last_two, "\n\n")) { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index e56507bd8450607c5ebe70926da5ff0dbbbc3321..5961ab4c0c823c5b4cbc2a021994513dfbfe9ff0 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -32,6 +32,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ ); // TODO: implement return values + // TODO: figure out a way to prevent asm constants from being generated ctx.c("inline asm", linux_x64, \\fn exitGood() void { \\ asm volatile ("syscall" @@ -49,6 +50,10 @@ pub fn addCases(ctx: *TestContext) !void { \\ \\void exitGood(void); \\ + \\const char *const exitGood__anon_0 = "{rax}"; + \\const char *const exitGood__anon_1 = "{rdi}"; + \\const char *const exitGood__anon_2 = "syscall"; + \\ \\noreturn void _start(void) { \\ exitGood(); \\} diff --git a/test/stage2/zir.zig b/test/stage2/zir.zig index 052ada667e7ca4906c2432f863de2bbcea806224..1a0aed85cfd67a94eb79fc73d2b42018ce9bd5a0 100644 --- a/test/stage2/zir.zig +++ b/test/stage2/zir.zig @@ -22,8 +22,8 @@ pub fn addCases(ctx: *TestContext) !void { , \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) - \\@9 = declref("9$0") - \\@9$0 = str("entry") + \\@9 = declref("9__anon_0") + \\@9__anon_0 = str("entry") \\@unnamed$4 = str("entry") \\@unnamed$5 = export(@unnamed$4, "entry") \\@unnamed$6 = fntype([], @void, cc=C) @@ -77,9 +77,9 @@ pub fn addCases(ctx: *TestContext) !void { \\@entry = fn(@unnamed$6, { \\ %0 = returnvoid() \\}) - \\@entry$1 = str("2\x08\x01\n") - \\@9 = declref("9$0") - \\@9$0 = str("entry") + \\@entry__anon_1 = str("2\x08\x01\n") + \\@9 = declref("9__anon_0") + \\@9__anon_0 = str("entry") \\@unnamed$11 = str("entry") \\@unnamed$12 = export(@unnamed$11, "entry") \\ @@ -111,8 +111,8 @@ pub fn addCases(ctx: *TestContext) !void { , \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) - \\@9 = declref("9$0") - \\@9$0 = str("entry") + \\@9 = declref("9__anon_0") + \\@9__anon_0 = str("entry") \\@unnamed$4 = str("entry") \\@unnamed$5 = export(@unnamed$4, "entry") \\@unnamed$6 = fntype([], @void, cc=C) @@ -187,8 +187,8 @@ pub fn addCases(ctx: *TestContext) !void { , \\@void = primitive(void) \\@fnty = fntype([], @void, cc=C) - \\@9 = declref("9$2") - \\@9$2 = str("entry") + \\@9 = declref("9__anon_2") + \\@9__anon_2 = str("entry") \\@unnamed$4 = str("entry") \\@unnamed$5 = export(@unnamed$4, "entry") \\@unnamed$6 = fntype([], @void, cc=C) -- 2.54.0 From d060be880460598dcf89cb3d59bfcdf5059a923d Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 8 Jul 2020 14:10:11 -0400 Subject: [PATCH 220/295] CBE: Don't expose openCFile, always close file after an update --- src-self-hosted/link.zig | 4 +++- src-self-hosted/test.zig | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 2f21dcbfde3f6b427a9d3d350f8019babcf08a50..98a2578822c2e1130d9d1c4de6d019ce97a21e8f 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -86,7 +86,7 @@ pub fn writeFilePath( return result; } -pub fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C { +fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C { return File.C{ .allocator = allocator, .file = file, @@ -282,6 +282,8 @@ pub const File = struct { } } try writer.writeAll(self.main.items); + self.file.?.close(); + self.file = null; } }; diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 0a320083699c89cde44c672de2dc9ce68a63efbb..3e4958cc9559313293934a5e2a8ff70997a0c4f4 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -480,9 +480,8 @@ pub const TestContext = struct { switch (update.case) { .Transformation => |expected_output| { if (case.cbe) { - var cfile: *link.File.C = module.bin_file.cast(link.File.C).?; - cfile.file.?.close(); - cfile.file = null; + // The C file is always closed after an update, because we don't support + // incremental updates var file = try tmp.dir.openFile(bin_name, .{ .read = true }); defer file.close(); var out = file.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!"); -- 2.54.0 From eeae3a8f9df49e53392bcd7b04b463f6bffcec9c Mon Sep 17 00:00:00 2001 From: Paul Espinosa Date: Wed, 8 Jul 2020 11:42:02 +0700 Subject: [PATCH 221/295] Rename langref's Index to Contents (TOC) The language reference's Index is a list of the documentation's contents in order of appearance. This commit renames "Index" to "Contents" as in table of contents. It also renames the HTML/CSS identifiers from "index" to "toc". --- doc/langref.html.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index d7b9de3c6e493200a3f697dccb990adb0f58ab3d..1e4d993fe9f6c8b0dee058d81ae143c60380ed4b 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -97,7 +97,7 @@ margin: auto; } - #index { + #toc { padding: 0 1em; } @@ -105,7 +105,7 @@ #main-wrapper { flex-direction: row; } - #contents-wrapper, #index { + #contents-wrapper, #toc { overflow: auto; } } @@ -181,7 +181,7 @@

-
+
0.1.1 | 0.2.0 | 0.3.0 | @@ -189,7 +189,7 @@ 0.5.0 | 0.6.0 | master -

Index

+

Contents

{#nav#}
-- 2.54.0 From f77c968cf841046238eb37f54600cda201f09ce2 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 8 Jul 2020 00:53:19 -0700 Subject: [PATCH 222/295] langref: Add test case for "if error union with optional" This is an edge case that isn't too uncommon but is rather confusing to try to deduce without documentation, since it feels like `else` is being overloaded in this scenario and there's no obvious 'correct' behavior here. This just adds a test demonstrating how Zig currently behaves in this scenario. --- doc/langref.html.in | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/doc/langref.html.in b/doc/langref.html.in index 1e4d993fe9f6c8b0dee058d81ae143c60380ed4b..032e05d6001a42e6e61ec4c0c049869ca81bce8e 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -3861,6 +3861,32 @@ test "if error union" { unreachable; } } + +test "if error union with optional" { + // If expressions test for errors before unwrapping optionals. + // The |optional_value| capture's type is ?u32. + + const a: anyerror!?u32 = 0; + if (a) |optional_value| { + assert(optional_value.? == 0); + } else |err| { + unreachable; + } + + const b: anyerror!?u32 = null; + if (b) |optional_value| { + assert(optional_value == null); + } else |err| { + unreachable; + } + + const c: anyerror!?u32 = error.BadValue; + if (c) |optional_value| { + unreachable; + } else |err| { + assert(err == error.BadValue); + } +} {#code_end#} {#see_also|Optionals|Errors#} {#header_close#} -- 2.54.0 From 2064e84cdd596b5bc63c8d3e7e5aa4130221faee Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Tue, 7 Jul 2020 21:20:33 +0200 Subject: [PATCH 223/295] ci: check langref.html for html errors --- ci/azure/linux_script | 6 +++++- doc/langref.html.in | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ci/azure/linux_script b/ci/azure/linux_script index b313a76161b28745c37c40149b7239f238f48099..a55de24496cc13cce8795b17f9cabb4564d7e00b 100755 --- a/ci/azure/linux_script +++ b/ci/azure/linux_script @@ -12,7 +12,7 @@ sudo apt-get update -q sudo apt-get remove -y llvm-* sudo rm -rf /usr/local/* -sudo apt-get install -y libxml2-dev libclang-10-dev llvm-10 llvm-10-dev liblld-10-dev cmake s3cmd gcc-7 g++-7 ninja-build +sudo apt-get install -y libxml2-dev libclang-10-dev llvm-10 llvm-10-dev liblld-10-dev cmake s3cmd gcc-7 g++-7 ninja-build tidy QEMUBASE="qemu-linux-x86_64-5.0.0-49ee115552" wget https://ziglang.org/deps/$QEMUBASE.tar.xz @@ -51,6 +51,10 @@ cd build cmake .. -DCMAKE_BUILD_TYPE=Release -GNinja ninja install ./zig build test -Denable-qemu -Denable-wasmtime + +# look for HTML errors +tidy -qe ../zig-cache/langref.html + VERSION="$(./zig version)" if [ "${BUILD_REASON}" != "PullRequest" ]; then diff --git a/doc/langref.html.in b/doc/langref.html.in index 032e05d6001a42e6e61ec4c0c049869ca81bce8e..78dcd537b8c48548aa92e4e1cc0832e9d1535401 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8419,6 +8419,7 @@ fn foo(comptime T: type, ptr: *T) T { {#header_close#} {#header_open|Opaque Types#} +

{#syntax#}@Type(.Opaque){#endsyntax#} creates a new type with an unknown (but non-zero) size and alignment.

-- 2.54.0 From 12a7dedb1f1ce34b16993d33aa97d7b78d5d5ca2 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 8 Jul 2020 13:40:16 -0700 Subject: [PATCH 224/295] langref: Expand "if error union with optional" test case Follow-up to #5818, closes #5819 --- doc/langref.html.in | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/langref.html.in b/doc/langref.html.in index 78dcd537b8c48548aa92e4e1cc0832e9d1535401..f64170817f3e46d7ba8d24ec6b1d0b0cd9f41301 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -3886,6 +3886,22 @@ test "if error union with optional" { } else |err| { assert(err == error.BadValue); } + + // Access the value by reference by using a pointer capture each time. + var d: anyerror!?u32 = 3; + if (d) |*optional_value| { + if (optional_value.*) |*value| { + value.* = 9; + } + } else |err| { + unreachable; + } + + if (d) |optional_value| { + assert(optional_value.? == 9); + } else |err| { + unreachable; + } } {#code_end#} {#see_also|Optionals|Errors#} -- 2.54.0 From 8e425c0c8d78acc64a4223a35010df478d5b7e16 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Jul 2020 20:33:33 -0700 Subject: [PATCH 225/295] stage2: `if` AST=>ZIR --- lib/std/zig/ast.zig | 2 + src-self-hosted/Module.zig | 186 +++++++++++++++++++++------- src-self-hosted/codegen.zig | 13 +- src-self-hosted/ir.zig | 19 ++- src-self-hosted/zir.zig | 233 +++++++++++++++++++++++------------- 5 files changed, 321 insertions(+), 132 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 370f42b463e741c57020ee2726a9c72fe7e30bff..72d8f3560e36a657daba2e7855f77d0658218e41 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -959,6 +959,8 @@ pub const Node = struct { }; /// The params are directly after the FnProto in memory. + /// TODO have a flags field for the optional nodes, and have them appended + /// before or after the parameters in memory. pub const FnProto = struct { base: Node = Node{ .id = .FnProto }, doc_comments: ?*DocComment, diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 67c1b00a1ea7d50164b604605bf6207d55762750..328b043153b05efafa24c715ae3a802e9b4378c0 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -303,14 +303,14 @@ pub const Scope = struct { switch (self.tag) { .block => return self.cast(Block).?.arena, .decl => return &self.cast(DeclAnalysis).?.arena.allocator, - .gen_zir => return &self.cast(GenZIR).?.arena.allocator, + .gen_zir => return self.cast(GenZIR).?.arena, .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, .file => unreachable, } } - /// Asserts the scope has a parent which is a DeclAnalysis and - /// returns the Decl. + /// If the scope has a parent which is a `DeclAnalysis`, + /// returns the `Decl`, otherwise returns `null`. pub fn decl(self: *Scope) ?*Decl { return switch (self.tag) { .block => self.cast(Block).?.decl, @@ -653,7 +653,7 @@ pub const Scope = struct { label: ?Label = null, pub const Label = struct { - name: []const u8, + zir_block: *zir.Inst.Block, results: ArrayListUnmanaged(*Inst), block_inst: *Inst.Block, }; @@ -674,8 +674,8 @@ pub const Scope = struct { pub const base_tag: Tag = .gen_zir; base: Scope = Scope{ .tag = base_tag }, decl: *Decl, - arena: std.heap.ArenaAllocator, - instructions: std.ArrayList(*zir.Inst), + arena: *Allocator, + instructions: std.ArrayListUnmanaged(*zir.Inst) = .{}, }; }; @@ -1115,19 +1115,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { // This arena allocator's memory is discarded at the end of this function. It is used // to determine the type of the function, and hence the type of the decl, which is needed // to complete the Decl analysis. + var fn_type_scope_arena = std.heap.ArenaAllocator.init(self.gpa); + defer fn_type_scope_arena.deinit(); var fn_type_scope: Scope.GenZIR = .{ .decl = decl, - .arena = std.heap.ArenaAllocator.init(self.gpa), - .instructions = std.ArrayList(*zir.Inst).init(self.gpa), + .arena = &fn_type_scope_arena.allocator, }; - defer fn_type_scope.arena.deinit(); - defer fn_type_scope.instructions.deinit(); + defer fn_type_scope.instructions.deinit(self.gpa); const body_node = fn_proto.body_node orelse return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); const param_decls = fn_proto.params(); - const param_types = try fn_type_scope.arena.allocator.alloc(*zir.Inst, param_decls.len); + const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_decls.len); for (param_decls) |param_decl, i| { const param_type_node = switch (param_decl.param_type) { .var_type => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement anytype parameter", .{}), @@ -1190,24 +1190,24 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const fn_zir = blk: { // This scope's arena memory is discarded after the ZIR generation // pass completes, and semantic analysis of it completes. + var gen_scope_arena = std.heap.ArenaAllocator.init(self.gpa); + errdefer gen_scope_arena.deinit(); var gen_scope: Scope.GenZIR = .{ .decl = decl, - .arena = std.heap.ArenaAllocator.init(self.gpa), - .instructions = std.ArrayList(*zir.Inst).init(self.gpa), + .arena = &gen_scope_arena.allocator, }; - errdefer gen_scope.arena.deinit(); - defer gen_scope.instructions.deinit(); + defer gen_scope.instructions.deinit(self.gpa); const body_block = body_node.cast(ast.Node.Block).?; try self.astGenBlock(&gen_scope.base, body_block); - const fn_zir = try gen_scope.arena.allocator.create(Fn.ZIR); + const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); fn_zir.* = .{ .body = .{ - .instructions = try gen_scope.arena.allocator.dupe(*zir.Inst, gen_scope.instructions.items), + .instructions = try gen_scope.arena.dupe(*zir.Inst, gen_scope.instructions.items), }, - .arena = gen_scope.arena.state, + .arena = gen_scope_arena.state, }; break :blk fn_zir; }; @@ -1351,9 +1351,70 @@ fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir return self.failNode(scope, payload, "TODO implement astGenIf for error unions", .{}); } } - const cond = try self.astGenExpr(scope, if_node.condition); - const body = try self.astGenExpr(scope, if_node.condition); - return self.failNode(scope, if_node.condition, "TODO implement astGenIf", .{}); + var block_scope: Scope.GenZIR = .{ + .decl = scope.decl().?, + .arena = scope.arena(), + .instructions = .{}, + }; + defer block_scope.instructions.deinit(self.gpa); + + const cond = try self.astGenExpr(&block_scope.base, if_node.condition); + + const tree = scope.tree(); + const if_src = tree.token_locs[if_node.if_token].start; + const condbr = try self.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{ + .condition = cond, + .true_body = undefined, // populated below + .false_body = undefined, // populated below + }, .{}); + + const block = try self.addZIRInstBlock(scope, if_src, .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), + }); + var then_scope: Scope.GenZIR = .{ + .decl = block_scope.decl, + .arena = block_scope.arena, + .instructions = .{}, + }; + defer then_scope.instructions.deinit(self.gpa); + + const then_result = try self.astGenExpr(&then_scope.base, if_node.body); + const then_src = tree.token_locs[if_node.body.lastToken()].start; + _ = try self.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ + .block = block, + .operand = then_result, + }, .{}); + condbr.positionals.true_body = .{ + .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items), + }; + + var else_scope: Scope.GenZIR = .{ + .decl = block_scope.decl, + .arena = block_scope.arena, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(self.gpa); + + if (if_node.@"else") |else_node| { + const else_result = try self.astGenExpr(&else_scope.base, else_node.body); + const else_src = tree.token_locs[else_node.body.lastToken()].start; + _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ + .block = block, + .operand = else_result, + }, .{}); + } else { + // TODO Optimization opportunity: we can avoid an allocation and a memcpy here + // by directly allocating the body for this one instruction. + const else_src = tree.token_locs[if_node.lastToken()].start; + _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.BreakVoid, .{ + .block = block, + }, .{}); + } + condbr.positionals.false_body = .{ + .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), + }; + + return &block.base; } fn astGenControlFlowExpression( @@ -1379,12 +1440,12 @@ fn astGenControlFlowExpression( fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerError!*zir.Inst { const tree = scope.tree(); const ident_name = tree.tokenSlice(ident.token); + const src = tree.token_locs[ident.token].start; if (mem.eql(u8, ident_name, "_")) { return self.failNode(scope, &ident.base, "TODO implement '_' identifier", .{}); } if (getSimplePrimitiveValue(ident_name)) |typed_value| { - const src = tree.token_locs[ident.token].start; return self.addZIRInstConst(scope, src, typed_value); } @@ -1408,7 +1469,6 @@ fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerE 64 => if (is_signed) Value.initTag(.i64_type) else Value.initTag(.u64_type), else => return self.failNode(scope, &ident.base, "TODO implement arbitrary integer bitwidth types", .{}), }; - const src = tree.token_locs[ident.token].start; return self.addZIRInstConst(scope, src, .{ .ty = Type.initTag(.type), .val = val, @@ -1417,10 +1477,21 @@ fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerE } if (self.lookupDeclName(scope, ident_name)) |decl| { - const src = tree.token_locs[ident.token].start; return try self.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); } + // Function parameter + if (scope.decl()) |decl| { + if (tree.root_node.decls()[decl.src_index].cast(ast.Node.FnProto)) |fn_proto| { + for (fn_proto.params()) |param, i| { + const param_name = tree.tokenSlice(param.name_token.?); + if (mem.eql(u8, param_name, ident_name)) { + return try self.addZIRInst(scope, src, zir.Inst.Arg, .{ .index = i }, .{}); + } + } + } + } + return self.failNode(scope, &ident.base, "TODO implement local variable identifier lookup", .{}); } @@ -1563,7 +1634,7 @@ fn astGenCall(self: *Module, scope: *Scope, call: *ast.Node.Call) InnerError!*zi const lhs = try self.astGenExpr(scope, call.lhs); const param_nodes = call.params(); - const args = try scope.cast(Scope.GenZIR).?.arena.allocator.alloc(*zir.Inst, param_nodes.len); + const args = try scope.cast(Scope.GenZIR).?.arena.alloc(*zir.Inst, param_nodes.len); for (param_nodes) |param_node, i| { args[i] = try self.astGenExpr(scope, param_node); } @@ -2239,7 +2310,7 @@ fn newZIRInst( comptime T: type, positionals: std.meta.fieldInfo(T, "positionals").field_type, kw_args: std.meta.fieldInfo(T, "kw_args").field_type, -) !*zir.Inst { +) !*T { const inst = try gpa.create(T); inst.* = .{ .base = .{ @@ -2249,30 +2320,48 @@ fn newZIRInst( .positionals = positionals, .kw_args = kw_args, }; - return &inst.base; + return inst; } -fn addZIRInst( +fn addZIRInstSpecial( self: *Module, scope: *Scope, src: usize, comptime T: type, positionals: std.meta.fieldInfo(T, "positionals").field_type, kw_args: std.meta.fieldInfo(T, "kw_args").field_type, -) !*zir.Inst { +) !*T { const gen_zir = scope.cast(Scope.GenZIR).?; - try gen_zir.instructions.ensureCapacity(gen_zir.instructions.items.len + 1); - const inst = try newZIRInst(&gen_zir.arena.allocator, src, T, positionals, kw_args); - gen_zir.instructions.appendAssumeCapacity(inst); + try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); + const inst = try newZIRInst(gen_zir.arena, src, T, positionals, kw_args); + gen_zir.instructions.appendAssumeCapacity(&inst.base); return inst; } +fn addZIRInst( + self: *Module, + scope: *Scope, + src: usize, + comptime T: type, + positionals: std.meta.fieldInfo(T, "positionals").field_type, + kw_args: std.meta.fieldInfo(T, "kw_args").field_type, +) !*zir.Inst { + const inst_special = try self.addZIRInstSpecial(scope, src, T, positionals, kw_args); + return &inst_special.base; +} + /// TODO The existence of this function is a workaround for a bug in stage1. fn addZIRInstConst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst { const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type; return self.addZIRInst(scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); } +/// TODO The existence of this function is a workaround for a bug in stage1. +fn addZIRInstBlock(self: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block { + const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type; + return self.addZIRInstSpecial(scope, src, zir.Inst.Block, P{ .body = body }, .{}); +} + fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime T: type) !*T { const inst = try block.arena.create(T); inst.* = .{ @@ -2403,6 +2492,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In switch (old_inst.tag) { .arg => return self.analyzeInstArg(scope, old_inst.cast(zir.Inst.Arg).?), .block => return self.analyzeInstBlock(scope, old_inst.cast(zir.Inst.Block).?), + .@"break" => return self.analyzeInstBreak(scope, old_inst.cast(zir.Inst.Break).?), .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.cast(zir.Inst.Breakpoint).?), .breakvoid => return self.analyzeInstBreakVoid(scope, old_inst.cast(zir.Inst.BreakVoid).?), .call => return self.analyzeInstCall(scope, old_inst.cast(zir.Inst.Call).?), @@ -2559,7 +2649,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr .arena = parent_block.arena, // TODO @as here is working around a miscompilation compiler bug :( .label = @as(?Scope.Block.Label, Scope.Block.Label{ - .name = inst.positionals.label, + .zir_block = inst, .results = .{}, .block_inst = block_inst, }), @@ -2588,25 +2678,39 @@ fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.Breakpoin return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, {}); } +fn analyzeInstBreak(self: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst { + const operand = try self.resolveInst(scope, inst.positionals.operand); + const block = inst.positionals.block; + return self.analyzeBreak(scope, inst.base.src, block, operand); +} + fn analyzeInstBreakVoid(self: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst { - const label_name = inst.positionals.label; + const block = inst.positionals.block; const void_inst = try self.constVoid(scope, inst.base.src); + return self.analyzeBreak(scope, inst.base.src, block, void_inst); +} +fn analyzeBreak( + self: *Module, + scope: *Scope, + src: usize, + zir_block: *zir.Inst.Block, + operand: *Inst, +) InnerError!*Inst { var opt_block = scope.cast(Scope.Block); while (opt_block) |block| { if (block.label) |*label| { - if (mem.eql(u8, label.name, label_name)) { - try label.results.append(self.gpa, void_inst); - const b = try self.requireRuntimeBlock(scope, inst.base.src); - return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.BreakVoid, .{ + if (label.zir_block == zir_block) { + try label.results.append(self.gpa, operand); + const b = try self.requireRuntimeBlock(scope, src); + return self.addNewInstArgs(b, src, Type.initTag(.noreturn), Inst.Br, .{ .block = label.block_inst, + .operand = operand, }); } } opt_block = block.parent; - } else { - return self.fail(scope, inst.base.src, "use of undeclared label '{}'", .{label_name}); - } + } else unreachable; } fn analyzeInstDeclRefStr(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 232703b69723f425bb5a8b8d2d0d73069412c1d7..e2c5c989ac2ec05350c3d216eff7afcdba5a5055 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -418,8 +418,9 @@ const Function = struct { .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?, arch), .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), .block => return self.genBlock(inst.cast(ir.Inst.Block).?, arch), + .br => return self.genBr(inst.cast(ir.Inst.Br).?, arch), .breakpoint => return self.genBreakpoint(inst.src, arch), - .breakvoid => return self.genBreakVoid(inst.cast(ir.Inst.BreakVoid).?, arch), + .brvoid => return self.genBrVoid(inst.cast(ir.Inst.BrVoid).?, arch), .call => return self.genCall(inst.cast(ir.Inst.Call).?, arch), .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?, arch), .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?, arch), @@ -767,7 +768,13 @@ const Function = struct { } } - fn genBreakVoid(self: *Function, inst: *ir.Inst.BreakVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { + fn genBr(self: *Function, inst: *ir.Inst.Br, comptime arch: std.Target.Cpu.Arch) !MCValue { + switch (arch) { + else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}), + } + } + + fn genBrVoid(self: *Function, inst: *ir.Inst.BrVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { // Emit a jump with a relocation. It will be patched up after the block ends. try inst.args.block.codegen.relocs.ensureCapacity(self.gpa, inst.args.block.codegen.relocs.items.len + 1); @@ -780,7 +787,7 @@ const Function = struct { // Leave the jump offset undefined inst.args.block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); }, - else => return self.fail(inst.base.src, "TODO implement breakvoid for {}", .{self.target.cpu.arch}), + else => return self.fail(inst.base.src, "TODO implement brvoid for {}", .{self.target.cpu.arch}), } return .none; } diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index f2ba3801a175343fb881cdfc6a2e36eb16d2ad94..1de7c626eac7cededc195b4ceeb0832a1c74d620 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -46,8 +46,9 @@ pub const Inst = struct { assembly, bitcast, block, + br, breakpoint, - breakvoid, + brvoid, call, cmp, condbr, @@ -80,7 +81,8 @@ pub const Inst = struct { .sub, => false, - .breakvoid, + .br, + .brvoid, .condbr, .ret, .retvoid, @@ -162,14 +164,23 @@ pub const Inst = struct { codegen: codegen.BlockData = .{}, }; + pub const Br = struct { + pub const base_tag = Tag.br; + base: Inst, + args: struct { + block: *Block, + operand: *Inst, + }, + }; + pub const Breakpoint = struct { pub const base_tag = Tag.breakpoint; base: Inst, args: void, }; - pub const BreakVoid = struct { - pub const base_tag = Tag.breakvoid; + pub const BrVoid = struct { + pub const base_tag = Tag.brvoid; base: Inst, args: struct { block: *Block, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 0221e4d330871086f01c5daf3915b7e2af78cd65..2a4db02c190e277994aa4b784337075de2421716 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -38,6 +38,8 @@ pub const Inst = struct { arg, /// A labeled block of code, which can return a value. block, + /// Return a value from a `Block`. + @"break", breakpoint, /// Same as `break` but without an operand; the operand is assumed to be the void value. breakvoid, @@ -85,6 +87,7 @@ pub const Inst = struct { return switch (tag) { .arg => Arg, .block => Block, + .@"break" => Break, .breakpoint => Breakpoint, .breakvoid => BreakVoid, .call => Call, @@ -143,12 +146,22 @@ pub const Inst = struct { base: Inst, positionals: struct { - label: []const u8, body: Module.Body, }, kw_args: struct {}, }; + pub const Break = struct { + pub const base_tag = Tag.@"break"; + base: Inst, + + positionals: struct { + block: *Block, + operand: *Inst, + }, + kw_args: struct {}, + }; + pub const Breakpoint = struct { pub const base_tag = Tag.breakpoint; base: Inst, @@ -162,7 +175,7 @@ pub const Inst = struct { base: Inst, positionals: struct { - label: []const u8, + block: *Block, }, kw_args: struct {}, }; @@ -610,8 +623,6 @@ pub const Module = struct { self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {}; } - const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize, name: []const u8 }); - const DeclAndIndex = struct { decl: *Decl, index: usize, @@ -645,84 +656,100 @@ pub const Module = struct { /// The allocator is used for temporary storage, but this function always returns /// with no resources allocated. pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void { + var write = Writer{ + .module = &self, + .inst_table = InstPtrTable.init(allocator), + .block_table = std.AutoHashMap(*Inst.Block, []const u8).init(allocator), + .arena = std.heap.ArenaAllocator.init(allocator), + .indent = 2, + }; + defer write.arena.deinit(); + defer write.inst_table.deinit(); + defer write.block_table.deinit(); + // First, build a map of *Inst to @ or % indexes - var inst_table = InstPtrTable.init(allocator); - defer inst_table.deinit(); - - try inst_table.ensureCapacity(self.decls.len); + try write.inst_table.ensureCapacity(self.decls.len); for (self.decls) |decl, decl_i| { - try inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); + try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); if (decl.inst.cast(Inst.Fn)) |fn_inst| { for (fn_inst.positionals.body.instructions) |inst, inst_i| { - try inst_table.putNoClobber(inst, .{ .inst = inst, .index = inst_i, .name = undefined }); + try write.inst_table.putNoClobber(inst, .{ .inst = inst, .index = inst_i, .name = undefined }); } } } for (self.decls) |decl, i| { try stream.print("@{} ", .{decl.name}); - try self.writeInstToStream(stream, decl.inst, &inst_table, 2); + try write.writeInstToStream(stream, decl.inst); try stream.writeByte('\n'); } } +}; + +const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize, name: []const u8 }); + +const Writer = struct { + module: *const Module, + inst_table: InstPtrTable, + block_table: std.AutoHashMap(*Inst.Block, []const u8), + arena: std.heap.ArenaAllocator, + indent: usize, + fn writeInstToStream( - self: Module, + self: *Writer, stream: var, inst: *Inst, - inst_table: *const InstPtrTable, - indent: usize, - ) @TypeOf(stream).Error!void { + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { // TODO I tried implementing this with an inline for loop and hit a compiler bug switch (inst.tag) { - .arg => return self.writeInstToStreamGeneric(stream, .arg, inst, inst_table, indent), - .block => return self.writeInstToStreamGeneric(stream, .block, inst, inst_table, indent), - .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst, inst_table, indent), - .breakvoid => return self.writeInstToStreamGeneric(stream, .breakvoid, inst, inst_table, indent), - .call => return self.writeInstToStreamGeneric(stream, .call, inst, inst_table, indent), - .declref => return self.writeInstToStreamGeneric(stream, .declref, inst, inst_table, indent), - .declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst, inst_table, indent), - .declval => return self.writeInstToStreamGeneric(stream, .declval, inst, inst_table, indent), - .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst, inst_table, indent), - .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst, inst_table, indent), - .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst, inst_table, indent), - .str => return self.writeInstToStreamGeneric(stream, .str, inst, inst_table, indent), - .int => return self.writeInstToStreamGeneric(stream, .int, inst, inst_table, indent), - .inttype => return self.writeInstToStreamGeneric(stream, .inttype, inst, inst_table, indent), - .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst, inst_table, indent), - .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst, inst_table, indent), - .deref => return self.writeInstToStreamGeneric(stream, .deref, inst, inst_table, indent), - .as => return self.writeInstToStreamGeneric(stream, .as, inst, inst_table, indent), - .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", inst, inst_table, indent), - .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", inst, inst_table, indent), - .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", inst, inst_table, indent), - .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, inst, inst_table, indent), - .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", inst, inst_table, indent), - .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", inst, inst_table, indent), - .primitive => return self.writeInstToStreamGeneric(stream, .primitive, inst, inst_table, indent), - .fntype => return self.writeInstToStreamGeneric(stream, .fntype, inst, inst_table, indent), - .intcast => return self.writeInstToStreamGeneric(stream, .intcast, inst, inst_table, indent), - .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst, inst_table, indent), - .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst, inst_table, indent), - .add => return self.writeInstToStreamGeneric(stream, .add, inst, inst_table, indent), - .sub => return self.writeInstToStreamGeneric(stream, .sub, inst, inst_table, indent), - .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst, inst_table, indent), - .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst, inst_table, indent), - .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst, inst_table, indent), - .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, inst, inst_table, indent), + .arg => return self.writeInstToStreamGeneric(stream, .arg, inst), + .block => return self.writeInstToStreamGeneric(stream, .block, inst), + .@"break" => return self.writeInstToStreamGeneric(stream, .@"break", inst), + .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst), + .breakvoid => return self.writeInstToStreamGeneric(stream, .breakvoid, inst), + .call => return self.writeInstToStreamGeneric(stream, .call, inst), + .declref => return self.writeInstToStreamGeneric(stream, .declref, inst), + .declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst), + .declval => return self.writeInstToStreamGeneric(stream, .declval, inst), + .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst), + .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst), + .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst), + .str => return self.writeInstToStreamGeneric(stream, .str, inst), + .int => return self.writeInstToStreamGeneric(stream, .int, inst), + .inttype => return self.writeInstToStreamGeneric(stream, .inttype, inst), + .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst), + .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst), + .deref => return self.writeInstToStreamGeneric(stream, .deref, inst), + .as => return self.writeInstToStreamGeneric(stream, .as, inst), + .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", inst), + .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", inst), + .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", inst), + .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, inst), + .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", inst), + .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", inst), + .primitive => return self.writeInstToStreamGeneric(stream, .primitive, inst), + .fntype => return self.writeInstToStreamGeneric(stream, .fntype, inst), + .intcast => return self.writeInstToStreamGeneric(stream, .intcast, inst), + .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst), + .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst), + .add => return self.writeInstToStreamGeneric(stream, .add, inst), + .sub => return self.writeInstToStreamGeneric(stream, .sub, inst), + .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst), + .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst), + .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst), + .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, inst), } } fn writeInstToStreamGeneric( - self: Module, + self: *Writer, stream: var, comptime inst_tag: Inst.Tag, base: *Inst, - inst_table: *const InstPtrTable, - indent: usize, - ) @TypeOf(stream).Error!void { + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { const SpecificInst = Inst.TagToType(inst_tag); const inst = @fieldParentPtr(SpecificInst, "base", base); const Positionals = @TypeOf(inst.positionals); @@ -732,7 +759,7 @@ pub const Module = struct { if (i != 0) { try stream.writeAll(", "); } - try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table, indent); + try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name)); } comptime var need_comma = pos_fields.len != 0; @@ -742,13 +769,13 @@ pub const Module = struct { if (@field(inst.kw_args, arg_field.name)) |non_optional| { if (need_comma) try stream.writeAll(", "); try stream.print("{}=", .{arg_field.name}); - try self.writeParamToStream(stream, non_optional, inst_table, indent); + try self.writeParamToStream(stream, non_optional); need_comma = true; } } else { if (need_comma) try stream.writeAll(", "); try stream.print("{}=", .{arg_field.name}); - try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table, indent); + try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name)); need_comma = true; } } @@ -756,31 +783,37 @@ pub const Module = struct { try stream.writeByte(')'); } - fn writeParamToStream(self: Module, stream: var, param: var, inst_table: *const InstPtrTable, indent: usize) !void { + fn writeParamToStream(self: *Writer, stream: var, param: var) !void { if (@typeInfo(@TypeOf(param)) == .Enum) { return stream.writeAll(@tagName(param)); } switch (@TypeOf(param)) { - *Inst => return self.writeInstParamToStream(stream, param, inst_table), + *Inst => return self.writeInstParamToStream(stream, param), []*Inst => { try stream.writeByte('['); for (param) |inst, i| { if (i != 0) { try stream.writeAll(", "); } - try self.writeInstParamToStream(stream, inst, inst_table); + try self.writeInstParamToStream(stream, inst); } try stream.writeByte(']'); }, Module.Body => { try stream.writeAll("{\n"); for (param.instructions) |inst, i| { - try stream.writeByteNTimes(' ', indent); + try stream.writeByteNTimes(' ', self.indent); try stream.print("%{} ", .{i}); - try self.writeInstToStream(stream, inst, inst_table, indent + 2); + if (inst.cast(Inst.Block)) |block| { + const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{}", .{i}); + try self.block_table.put(block, name); + } + self.indent += 2; + try self.writeInstToStream(stream, inst); + self.indent -= 2; try stream.writeByte('\n'); } - try stream.writeByteNTimes(' ', indent - 2); + try stream.writeByteNTimes(' ', self.indent - 2); try stream.writeByte('}'); }, bool => return stream.writeByte("01"[@boolToInt(param)]), @@ -788,12 +821,16 @@ pub const Module = struct { BigIntConst, usize => return stream.print("{}", .{param}), TypedValue => unreachable, // this is a special case *IrModule.Decl => unreachable, // this is a special case + *Inst.Block => { + const name = self.block_table.get(param).?; + return std.zig.renderStringLiteral(name, stream); + }, else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } - fn writeInstParamToStream(self: Module, stream: var, inst: *Inst, inst_table: *const InstPtrTable) !void { - if (inst_table.get(inst)) |info| { + fn writeInstParamToStream(self: *Writer, stream: var, inst: *Inst) !void { + if (self.inst_table.get(inst)) |info| { if (info.index) |i| { try stream.print("%{}", .{info.index}); } else { @@ -823,7 +860,9 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module .global_name_map = &global_name_map, .decls = .{}, .unnamed_index = 0, + .block_table = std.StringHashMap(*Inst.Block).init(allocator), }; + defer parser.block_table.deinit(); errdefer parser.arena.deinit(); parser.parseRoot() catch |err| switch (err) { @@ -849,6 +888,7 @@ const Parser = struct { global_name_map: *std.StringHashMap(*Inst), error_msg: ?ErrorMsg = null, unnamed_index: usize, + block_table: std.StringHashMap(*Inst.Block), const Body = struct { instructions: std.ArrayList(*Inst), @@ -1057,6 +1097,10 @@ const Parser = struct { .tag = InstType.base_tag, }; + if (InstType == Inst.Block) { + try self.block_table.put(inst_name, inst_specific); + } + if (@hasField(InstType, "ty")) { inst_specific.ty = opt_type orelse { return self.fail("instruction '" ++ fn_name ++ "' requires type", .{}); @@ -1162,6 +1206,10 @@ const Parser = struct { }, TypedValue => return self.fail("'const' is a special instruction; not legal in ZIR text", .{}), *IrModule.Decl => return self.fail("'declval_in_module' is a special instruction; not legal in ZIR text", .{}), + *Inst.Block => { + const name = try self.parseStringLiteral(); + return self.block_table.get(name).?; + }, else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), } return self.fail("TODO parse parameter {}", .{@typeName(T)}); @@ -1226,7 +1274,9 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { .names = std.StringHashMap(void).init(allocator), .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), .indent = 0, + .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), }; + defer ctx.block_table.deinit(); defer ctx.decls.deinit(allocator); defer ctx.names.deinit(); defer ctx.primitive_table.deinit(); @@ -1249,6 +1299,7 @@ const EmitZIR = struct { next_auto_name: usize, primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl), indent: usize, + block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block), fn emit(self: *EmitZIR) !void { // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced @@ -1611,33 +1662,47 @@ const EmitZIR = struct { const old_inst = inst.cast(ir.Inst.Block).?; const new_inst = try self.arena.allocator.create(Inst.Block); - // We do this now so that the break instructions within the block - // can find it. - try inst_table.put(&old_inst.base, &new_inst.base); - new_inst.* = .{ - .base = .{ - .src = inst.src, - .tag = Inst.Block.base_tag, - }, - .positionals = .{ - .label = try self.autoName(), - .body = undefined, - }, - .kw_args = .{}, - }; + try self.block_table.put(old_inst, new_inst); var block_body = std.ArrayList(*Inst).init(self.allocator); defer block_body.deinit(); try self.emitBody(old_inst.args.body, inst_table, &block_body); - new_inst.positionals.body = .{ .instructions = block_body.toOwnedSlice() }; + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.Block.base_tag, + }, + .positionals = .{ + .body = .{ .instructions = block_body.toOwnedSlice() }, + }, + .kw_args = .{}, + }; + + break :blk &new_inst.base; + }, + .br => blk: { + const old_inst = inst.cast(ir.Inst.Br).?; + const new_block = self.block_table.get(old_inst.args.block).?; + const new_inst = try self.arena.allocator.create(Inst.Break); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.Break.base_tag, + }, + .positionals = .{ + .block = new_block, + .operand = try self.resolveInst(new_body, old_inst.args.operand), + }, + .kw_args = .{}, + }; break :blk &new_inst.base; }, .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), - .breakvoid => blk: { - const old_inst = inst.cast(ir.Inst.BreakVoid).?; - const new_block = inst_table.get(&old_inst.args.block.base).?; + .brvoid => blk: { + const old_inst = inst.cast(ir.Inst.BrVoid).?; + const new_block = self.block_table.get(old_inst.args.block).?; const new_inst = try self.arena.allocator.create(Inst.BreakVoid); new_inst.* = .{ .base = .{ @@ -1645,7 +1710,7 @@ const EmitZIR = struct { .tag = Inst.BreakVoid.base_tag, }, .positionals = .{ - .label = new_block.cast(Inst.Block).?.positionals.label, + .block = new_block, }, .kw_args = .{}, }; -- 2.54.0 From bf56cdd9edffd5b97d2084b46cda6e6a89a391c1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Jul 2020 21:01:13 -0700 Subject: [PATCH 226/295] start to make test runner aware of logging by default the test runner will only print logs with "warning" or higher. this can be configured via the std.testing API. See #5738 for future plans --- lib/std/special/test_runner.zig | 12 ++++++++++++ lib/std/testing.zig | 3 +++ 2 files changed, 15 insertions(+) diff --git a/lib/std/special/test_runner.zig b/lib/std/special/test_runner.zig index 828d3165db0b29b5205a18f6b5701f669303ef1c..fd8c068f0527e3ed54a70a0cb27dc1477fb381fb 100644 --- a/lib/std/special/test_runner.zig +++ b/lib/std/special/test_runner.zig @@ -21,6 +21,7 @@ pub fn main() anyerror!void { for (test_fn_list) |test_fn, i| { std.testing.base_allocator_instance.reset(); + std.testing.log_level = .warn; var test_node = root_node.start(test_fn.name, null); test_node.activate(); @@ -73,3 +74,14 @@ pub fn main() anyerror!void { std.debug.warn("{} passed; {} skipped.\n", .{ ok_count, skip_count }); } } + +pub fn log( + comptime message_level: std.log.Level, + comptime scope: @Type(.EnumLiteral), + comptime format: []const u8, + args: var, +) void { + if (@enumToInt(message_level) <= @enumToInt(std.testing.log_level)) { + std.debug.print("[{}] ({}): " ++ format, .{@tagName(scope), @tagName(message_level)} ++ args); + } +} diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 670d8fd5d6acb98322e54823a050cc59a269e1e7..bdaf759d626cbe1072cfc45163c568c9c9eda97a 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -14,6 +14,9 @@ pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_insta pub var base_allocator_instance = std.mem.validationWrap(std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..])); var allocator_mem: [2 * 1024 * 1024]u8 = undefined; +/// TODO https://github.com/ziglang/zig/issues/5738 +pub var log_level = std.log.Level.warn; + /// This function is intended to be used only in tests. It prints diagnostics to stderr /// and then aborts when actual_error_union is not expected_error. pub fn expectError(expected_error: anyerror, actual_error_union: var) void { -- 2.54.0 From f510f385920b9a22bd1e68839cd4be3eea092e4d Mon Sep 17 00:00:00 2001 From: Paul Espinosa Date: Thu, 9 Jul 2020 18:38:02 +0700 Subject: [PATCH 227/295] Explain Language Ref's Hello World To introduce the Zig programming language, the "Hello, world!" code sample now has documentation to explain some of the features shown in the code sample and contains links to those features in the rest of the documentation. Writing style goals: * Balance writing style to keep beginner and experience programmers interested. * Be concise: allow the rest of the documentation to clarify language features. --- doc/langref.html.in | 91 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index f64170817f3e46d7ba8d24ec6b1d0b0cd9f41301..cf9e485e8a3fb2f8ead41e7475780cfc212e5a0a 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -218,6 +218,8 @@

The code samples in this document are compiled and tested as part of the main test suite of Zig. +

+

This HTML document depends on no external files, so you can use it offline.

@@ -236,10 +238,89 @@ pub fn main() !void { } {#code_end#}

- Usually you don't want to write to stdout. You want to write to stderr, and you - don't care if it fails. For that you can use a simpler API: + The Zig code sample above demonstrates one way to create a program that will output Hello, world!.

- {#code_begin|exe|hello#} +

+ The code sample shows the contents of a file named hello.zig. Files storing Zig + source code are {#link|UTF-8 encoded|Source Encoding#} text files. The files storing + Zig source code are usually named with the .zig extension. +

+

+ Following the hello.zig Zig code sample, the {#link|Zig Build System#} is used + to build an executable program from the hello.zig source code. Then, the + hello program is executed showing its output Hello, world!. The + lines beginning with $ represent command line prompts and a command. + Everything else is program output. +

+

+ The code sample begins by adding Zig's Standard Library to the build using the {#link|@import#} builtin function. + The {#syntax#}@import("std"){#endsyntax#} function call creates a structure to represent the Standard Library. + The code then makes a {#link|top-level declaration|Global Variables#} of a + {#link|constant identifier|Assignment#}, named std, for easy access to + Zig's standard library. +

+

+ Next, a {#link|public function|Functions#}, {#syntax#}pub fn{#endsyntax#}, named main + is declared. The main function is necessary because it tells the Zig compiler where the start of + the program exists. Programs designed to be executed will need a {#syntax#}pub fn main{#endsyntax#} function. + For more advanced Zig use cases, Zig offers other features to inform the compiler where the start of + the program exists. Libraries, on the other hand, do not need a main function because + library code is usually called by other programs. +

+

+ A function is a block of any number of statements and expressions that, as a whole, perform a task. + Functions may or may not return data after they are done performing their task. +

+

+ In the hello.zig code sample, the main function is declared + with the {#syntax#}!void{#endsyntax#} return type. This return type tells the Zig compiler, + and other people reading the code, the function will not return a value and it might fail. + The {#syntax#}!{#endsyntax#} (bang, exclamation mark) before the {#syntax#}void{#endsyntax#} + {#link|type|Primitive Types#} is what tells the Zig compiler an {#link|error|Errors#} might + occur. The {#syntax#}void{#endsyntax#} return type tells the Zig compiler the main + function will not return a value. +

+

+ In Zig, a function's block of statements and expressions are surrounded by { and + } curly-braces. Inside of the main function are expressions that perform + the task of outputting Hello, world! to standard output. +

+

+ First, a constant identifier, stdout, is initialized to represent the standard output + stream. Then, the program tries to print the Hello, world! message to the standard output + stream. +

+

+ Functions sometimes need information to perform their task. In Zig, information is passed + to functions between open ( and close ) parenthesis placed after + the function's name. The information passed to functions are its arguments. When there are + multiple arguments passed to a function, they are separated by commas ,. +

+

+ The two arguments passed to the stdout.print() function, "Hello, {}!\n" + and .{"world"}, are evaluated at {#link|compile-time|comptime#}. The code sample is + purposely written to show how to perform {#link|string|String Literals and Character Literals#} + substitution in the print function. The curly-braces inside of the first argument + are substituted with the compile-time known value inside of the second argument + (known as an {#link|anonymous struct literal|Anonymous Struct Literals#}). The \n + inside of the double-quotes of the first argument is the {#link|escape sequence|Escape Sequences#} for the + newline character. The {#link|try#} expression evaluates the result of stdout.print. + If the result is an error, then the {#syntax#}try{#endsyntax#} expression will return from + main with the error. Otherwise, the program will continue. In this case, there are no + more statements or expressions left to execute in the main function, so the program exits. +

+

+ In Zig, the standard output stream's print function is allowed to fail because + it is actually a function defined for a generic output stream. Consider a generic output stream that + represents writing data to a file and the disk is full; a write to the file will fail. However, + we typically do not expect writing text to the standard output stream to fail. To avoid having + to handle the failure case of printing to a standard output, you can use alternate functions: the + std.log function for proper logging or the std.debug.print function. + This documentation will use the latter option to print to standard error (stderr) and silently + return on failure. The next code sample, hello_again.zig demonstrates the use of + std.debug.print. +

+ {#code_begin|exe|hello_again#} const print = @import("std").debug.print; pub fn main() void { @@ -247,9 +328,9 @@ pub fn main() void { } {#code_end#}

- Note that you can leave off the {#syntax#}!{#endsyntax#} from the return type because {#syntax#}print{#endsyntax#} cannot fail. + Note that you can leave off the {#syntax#}!{#endsyntax#} from the return type because std.debug.print cannot fail.

- {#see_also|Values|@import|Errors|Root Source File#} + {#see_also|Values|@import|Errors|Root Source File|Source Encoding#} {#header_close#} {#header_open|Comments#} {#code_begin|test|comments#} -- 2.54.0 From 2e1bdd0d14f490a80bbed3ee0e0479a908715d33 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 9 Jul 2020 21:25:55 +0300 Subject: [PATCH 228/295] use correct cast function when doing `@floatCast` at comptime Closes #5832 --- src/ir.cpp | 2 +- test/stage1/behavior/cast.zig | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index a45455ad39e03c5354ed1b7883c547628db85502..81b7f14f84db7a0bdd78ff8eb0a0f1151bcaacc8 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -26718,7 +26718,7 @@ static IrInstGen *ir_analyze_instruction_float_cast(IrAnalyze *ira, IrInstSrcFlo } if (instr_is_comptime(target) || dest_type->id == ZigTypeIdComptimeFloat) { - return ir_implicit_cast2(ira, &instruction->target->base, target, dest_type); + return ir_analyze_widen_or_shorten(ira, &instruction->target->base, target, dest_type); } if (target->value->type->id != ZigTypeIdFloat) { diff --git a/test/stage1/behavior/cast.zig b/test/stage1/behavior/cast.zig index 77cdacc307a860a98d45c29f281b8d33b84000b3..4325b0b9e40788824a9e74f9bad6239ea803da40 100644 --- a/test/stage1/behavior/cast.zig +++ b/test/stage1/behavior/cast.zig @@ -384,6 +384,19 @@ test "@intCast i32 to u7" { expect(z == 0xff); } +test "@floatCast cast down" { + { + var double: f64 = 0.001534; + var single = @floatCast(f32, double); + expect(@TypeOf(single) == f32); + } + { + const double: f64 = 0.001534; + const single = @floatCast(f32, double); + expect(@TypeOf(single) == f32); + } +} + test "implicit cast undefined to optional" { expect(MakeType(void).getNull() == null); expect(MakeType(void).getNonNull() != null); -- 2.54.0 From 02619edf413cd3b8b01a6a3514ecd257d7c08b6e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 9 Jul 2020 23:24:21 -0700 Subject: [PATCH 229/295] Revert "use correct cast function when doing `@floatCast` at comptime" This reverts commit 2e1bdd0d14f490a80bbed3ee0e0479a908715d33. Test failures --- src/ir.cpp | 2 +- test/stage1/behavior/cast.zig | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 81b7f14f84db7a0bdd78ff8eb0a0f1151bcaacc8..a45455ad39e03c5354ed1b7883c547628db85502 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -26718,7 +26718,7 @@ static IrInstGen *ir_analyze_instruction_float_cast(IrAnalyze *ira, IrInstSrcFlo } if (instr_is_comptime(target) || dest_type->id == ZigTypeIdComptimeFloat) { - return ir_analyze_widen_or_shorten(ira, &instruction->target->base, target, dest_type); + return ir_implicit_cast2(ira, &instruction->target->base, target, dest_type); } if (target->value->type->id != ZigTypeIdFloat) { diff --git a/test/stage1/behavior/cast.zig b/test/stage1/behavior/cast.zig index 4325b0b9e40788824a9e74f9bad6239ea803da40..77cdacc307a860a98d45c29f281b8d33b84000b3 100644 --- a/test/stage1/behavior/cast.zig +++ b/test/stage1/behavior/cast.zig @@ -384,19 +384,6 @@ test "@intCast i32 to u7" { expect(z == 0xff); } -test "@floatCast cast down" { - { - var double: f64 = 0.001534; - var single = @floatCast(f32, double); - expect(@TypeOf(single) == f32); - } - { - const double: f64 = 0.001534; - const single = @floatCast(f32, double); - expect(@TypeOf(single) == f32); - } -} - test "implicit cast undefined to optional" { expect(MakeType(void).getNull() == null); expect(MakeType(void).getNonNull() != null); -- 2.54.0 From eddc68ad9470730a2742084a29ab08fa5363c754 Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Thu, 9 Jul 2020 21:51:55 -0400 Subject: [PATCH 230/295] remove stray allocator parameter --- lib/std/hash_map.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index 645789a04b76210dd9ba743120db72b86e7128fc..cfb75f077ed7177f4ab21d2ce7819eddefd29cd3 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -96,7 +96,7 @@ pub fn HashMap( return self.unmanaged.clearRetainingCapacity(); } - pub fn clearAndFree(self: *Self, allocator: *Allocator) void { + pub fn clearAndFree(self: *Self) void { return self.unmanaged.clearAndFree(self.allocator); } -- 2.54.0 From 5afa7f2545a5f350bbac4c9d9349a7fbbd8f0977 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 11 Jul 2020 09:09:07 +0700 Subject: [PATCH 231/295] Update doc/langref.html.in Co-authored-by: Joachim Schmidt --- doc/langref.html.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index cf9e485e8a3fb2f8ead41e7475780cfc212e5a0a..0f17f4c09668c439bdebdfb053207c14b87bc33b 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -273,8 +273,8 @@ pub fn main() !void {

In the hello.zig code sample, the main function is declared - with the {#syntax#}!void{#endsyntax#} return type. This return type tells the Zig compiler, - and other people reading the code, the function will not return a value and it might fail. + with the {#syntax#}!void{#endsyntax#} return type. This return type tells the Zig compiler + and other people reading the code that the function will not return a value and it might fail. The {#syntax#}!{#endsyntax#} (bang, exclamation mark) before the {#syntax#}void{#endsyntax#} {#link|type|Primitive Types#} is what tells the Zig compiler an {#link|error|Errors#} might occur. The {#syntax#}void{#endsyntax#} return type tells the Zig compiler the main -- 2.54.0 From 656b640e79e8cd391a046bb69713bffc3e0ff7b3 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 11 Jul 2020 09:09:43 +0700 Subject: [PATCH 232/295] Update doc/langref.html.in Co-authored-by: Joachim Schmidt --- doc/langref.html.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 0f17f4c09668c439bdebdfb053207c14b87bc33b..c84edbc6a22503072f90ab727324326dccd2c4b8 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -312,7 +312,7 @@ pub fn main() !void {

In Zig, the standard output stream's print function is allowed to fail because it is actually a function defined for a generic output stream. Consider a generic output stream that - represents writing data to a file and the disk is full; a write to the file will fail. However, + represents writing data to a file. When the disk is full, a write to the file will fail. However, we typically do not expect writing text to the standard output stream to fail. To avoid having to handle the failure case of printing to a standard output, you can use alternate functions: the std.log function for proper logging or the std.debug.print function. -- 2.54.0 From 50df1334f35411e0dbf12624dab68667a4e7a6ac Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 11 Jul 2020 09:09:57 +0700 Subject: [PATCH 233/295] Update doc/langref.html.in Co-authored-by: Joachim Schmidt --- doc/langref.html.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index c84edbc6a22503072f90ab727324326dccd2c4b8..6821dfd9f914a2d9e39c16173c7390ec7853cfd3 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -293,7 +293,7 @@ pub fn main() !void {

Functions sometimes need information to perform their task. In Zig, information is passed to functions between open ( and close ) parenthesis placed after - the function's name. The information passed to functions are its arguments. When there are + the function's name. This information is also known as arguments. When there are multiple arguments passed to a function, they are separated by commas ,.

-- 2.54.0 From e57458a94f677c54ba3ff6698831312e622e8b2b Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 11 Jul 2020 09:10:08 +0700 Subject: [PATCH 234/295] Update doc/langref.html.in Co-authored-by: Joachim Schmidt --- doc/langref.html.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 6821dfd9f914a2d9e39c16173c7390ec7853cfd3..d181844e90fc10f18444e8e9f461cd8c8131a1a0 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -263,7 +263,7 @@ pub fn main() !void { Next, a {#link|public function|Functions#}, {#syntax#}pub fn{#endsyntax#}, named main is declared. The main function is necessary because it tells the Zig compiler where the start of the program exists. Programs designed to be executed will need a {#syntax#}pub fn main{#endsyntax#} function. - For more advanced Zig use cases, Zig offers other features to inform the compiler where the start of + For more advanced use cases, Zig offers other features to inform the compiler where the start of the program exists. Libraries, on the other hand, do not need a main function because library code is usually called by other programs.

-- 2.54.0 From 2e037fd827487ce94ae0beb313a1f0536085d61e Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 10 Jul 2020 17:30:50 +0300 Subject: [PATCH 235/295] use correct cast function when doing `@floatCast` at comptime --- src/ir.cpp | 20 +++++++++++++------- test/compile_errors.zig | 32 ++++++++++++++++++-------------- test/stage1/behavior/cast.zig | 13 +++++++++++++ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index a45455ad39e03c5354ed1b7883c547628db85502..ca0fd47c4924a6dd1cc3ed1ce6b7edfec458fa95 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -13020,7 +13020,11 @@ static IrInstGen *ir_resolve_cast(IrAnalyze *ira, IrInst *source_instr, IrInstGe { if (instr_is_comptime(value) || !type_has_bits(ira->codegen, wanted_type)) { IrInstGen *result = ir_const(ira, source_instr, wanted_type); - if (!eval_const_expr_implicit_cast(ira, source_instr, cast_op, value->value, value->value->type, + ZigValue *val = ir_resolve_const(ira, value, UndefBad); + if (val == nullptr) + return ira->codegen->invalid_inst_gen; + + if (!eval_const_expr_implicit_cast(ira, source_instr, cast_op, val, val->type, result->value, wanted_type)) { return ira->codegen->invalid_inst_gen; @@ -26680,6 +26684,10 @@ static IrInstGen *ir_analyze_instruction_int_cast(IrAnalyze *ira, IrInstSrcIntCa } if (instr_is_comptime(target) || dest_type->id == ZigTypeIdComptimeInt) { + ZigValue *val = ir_resolve_const(ira, target, UndefBad); + if (val == nullptr) + return ira->codegen->invalid_inst_gen; + return ir_implicit_cast2(ira, &instruction->target->base, target, dest_type); } @@ -26718,13 +26726,11 @@ static IrInstGen *ir_analyze_instruction_float_cast(IrAnalyze *ira, IrInstSrcFlo } if (instr_is_comptime(target) || dest_type->id == ZigTypeIdComptimeFloat) { - return ir_implicit_cast2(ira, &instruction->target->base, target, dest_type); - } + ZigValue *val = ir_resolve_const(ira, target, UndefBad); + if (val == nullptr) + return ira->codegen->invalid_inst_gen; - if (target->value->type->id != ZigTypeIdFloat) { - ir_add_error(ira, &instruction->target->base, buf_sprintf("expected float type, found '%s'", - buf_ptr(&target->value->type->name))); - return ira->codegen->invalid_inst_gen; + return ir_analyze_widen_or_shorten(ira, &instruction->target->base, target, dest_type); } return ir_analyze_widen_or_shorten(ira, &instruction->base.base, target, dest_type); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 611094c0509bba51ea33887fe5f37fff8b7267d9..df2e759bf1c4ed47ecced3b68c84ba947d0fe8ab 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,22 @@ const tests = @import("tests.zig"); const std = @import("std"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.addTest("int/float conversion to comptime_int/float", + \\export fn foo() void { + \\ var a: f32 = 2; + \\ _ = @floatToInt(comptime_int, a); + \\} + \\export fn bar() void { + \\ var a: u32 = 2; + \\ _ = @intToFloat(comptime_float, a); + \\} + , &[_][]const u8{ + "tmp.zig:3:35: error: unable to evaluate constant expression", + "tmp.zig:3:9: note: referenced here", + "tmp.zig:7:37: error: unable to evaluate constant expression", + "tmp.zig:7:9: note: referenced here", + }); + cases.add("extern variable has no type", \\extern var foo; \\pub export fn entry() void { @@ -90,19 +106,13 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ var a: u32 = 2; \\ _ = @floatToInt(u32, a); \\} - \\export fn qux() void { - \\ var a: u32 = 2; - \\ _ = @intCast(comptime_int, a); - \\} , &[_][]const u8{ - "tmp.zig:3:32: error: expected type 'comptime_int', found 'u32'", + "tmp.zig:3:32: error: unable to evaluate constant expression", "tmp.zig:3:9: note: referenced here", "tmp.zig:7:21: error: expected float type, found 'u32'", "tmp.zig:7:9: note: referenced here", "tmp.zig:11:26: error: expected float type, found 'u32'", "tmp.zig:11:9: note: referenced here", - "tmp.zig:15:32: error: expected type 'comptime_int', found 'u32'", - "tmp.zig:15:9: note: referenced here", }); cases.addTest("invalid float casts", @@ -118,19 +128,13 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ var a: f32 = 2; \\ _ = @intToFloat(f32, a); \\} - \\export fn qux() void { - \\ var a: f32 = 2; - \\ _ = @floatCast(comptime_float, a); - \\} , &[_][]const u8{ - "tmp.zig:3:36: error: expected type 'comptime_float', found 'f32'", + "tmp.zig:3:36: error: unable to evaluate constant expression", "tmp.zig:3:9: note: referenced here", "tmp.zig:7:21: error: expected integer type, found 'f32'", "tmp.zig:7:9: note: referenced here", "tmp.zig:11:26: error: expected int type, found 'f32'", "tmp.zig:11:9: note: referenced here", - "tmp.zig:15:36: error: expected type 'comptime_float', found 'f32'", - "tmp.zig:15:9: note: referenced here", }); cases.addTest("invalid assignments", diff --git a/test/stage1/behavior/cast.zig b/test/stage1/behavior/cast.zig index 77cdacc307a860a98d45c29f281b8d33b84000b3..4b678cd2db2a4a8463eb3cf34cb68c0e0a04c8a5 100644 --- a/test/stage1/behavior/cast.zig +++ b/test/stage1/behavior/cast.zig @@ -384,6 +384,19 @@ test "@intCast i32 to u7" { expect(z == 0xff); } +test "@floatCast cast down" { + { + var double: f64 = 0.001534; + var single = @floatCast(f32, double); + expect(single == 0.001534); + } + { + const double: f64 = 0.001534; + const single = @floatCast(f32, double); + expect(single == 0.001534); + } +} + test "implicit cast undefined to optional" { expect(MakeType(void).getNull() == null); expect(MakeType(void).getNonNull() != null); -- 2.54.0 From b45a2d72c83ef383af24a7af03604292c7089dae Mon Sep 17 00:00:00 2001 From: Paul Espinosa Date: Sat, 11 Jul 2020 18:08:00 +0700 Subject: [PATCH 236/295] Introduce Error Union and Use Writer This commit edits the "Hello, World!" introduction. It introduces Error Union Types. Also, it changes `outStream` to `writer` in the code example and description. --- doc/langref.html.in | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index d181844e90fc10f18444e8e9f461cd8c8131a1a0..51e7902c8cfdda640d59ed1184178afde91c5263 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -233,7 +233,7 @@ const std = @import("std"); pub fn main() !void { - const stdout = std.io.getStdOut().outStream(); + const stdout = std.io.getStdOut().writer(); try stdout.print("Hello, {}!\n", .{"world"}); } {#code_end#} @@ -269,16 +269,25 @@ pub fn main() !void {

A function is a block of any number of statements and expressions that, as a whole, perform a task. - Functions may or may not return data after they are done performing their task. + Functions may or may not return data after they are done performing their task. If a function + cannot perform its task, it might return an error. Zig makes all of this explicit.

In the hello.zig code sample, the main function is declared - with the {#syntax#}!void{#endsyntax#} return type. This return type tells the Zig compiler - and other people reading the code that the function will not return a value and it might fail. - The {#syntax#}!{#endsyntax#} (bang, exclamation mark) before the {#syntax#}void{#endsyntax#} - {#link|type|Primitive Types#} is what tells the Zig compiler an {#link|error|Errors#} might - occur. The {#syntax#}void{#endsyntax#} return type tells the Zig compiler the main - function will not return a value. + with the {#syntax#}!void{#endsyntax#} return type. This return type is known as an {#link|Error Union Type#}. + This syntax tells the Zig compiler that the function will either return an + error or a value. An error union type combines an {#link|Error Set Type#} and a {#link|Primitive Type|Primitive Types#}. + The full form of an error union type is + <error set type>{#syntax#}!{#endsyntax#}<primitive type>. In the code + sample, the error set type is not explicitly written on the left side of the {#syntax#}!{#endsyntax#} operator. + When written this way, the error set type is a special kind of error union type that has an + {#link|inferred error set type|Inferred Error Sets#}. The {#syntax#}void{#endsyntax#} after the {#syntax#}!{#endsyntax#} operator + tells the compiler that the function will not return a value under normal circumstances (i.e. no errors occur). +

+

+ Note to experienced programmers: Zig also has the boolean {#link|operator|Operators#} {#syntax#}!a{#endsyntax#} + where {#syntax#}a{#endsyntax#} is a value of type {#syntax#}bool{#endsyntax#}. Error union types contain the + name of the type in the syntax: {#syntax#}!{#endsyntax#}<primitive type>.

In Zig, a function's block of statements and expressions are surrounded by { and @@ -286,9 +295,9 @@ pub fn main() !void { the task of outputting Hello, world! to standard output.

- First, a constant identifier, stdout, is initialized to represent the standard output - stream. Then, the program tries to print the Hello, world! message to the standard output - stream. + First, a constant identifier, stdout, is initialized to represent standard output's + writer. Then, the program tries to print the Hello, world! + message to standard output.

Functions sometimes need information to perform their task. In Zig, information is passed @@ -310,14 +319,14 @@ pub fn main() !void { more statements or expressions left to execute in the main function, so the program exits.

- In Zig, the standard output stream's print function is allowed to fail because - it is actually a function defined for a generic output stream. Consider a generic output stream that - represents writing data to a file. When the disk is full, a write to the file will fail. However, - we typically do not expect writing text to the standard output stream to fail. To avoid having - to handle the failure case of printing to a standard output, you can use alternate functions: the + In Zig, the standard output writer's print function is allowed to fail because + it is actually a function defined as part of a generic Writer. Consider a generic Writer that + represents writing data to a file. When the disk is full, a write to the file will fail. + However, we typically do not expect writing text to the standard output to fail. To avoid having + to handle the failure case of printing to standard output, you can use alternate functions: the std.log function for proper logging or the std.debug.print function. - This documentation will use the latter option to print to standard error (stderr) and silently - return on failure. The next code sample, hello_again.zig demonstrates the use of + This documentation will use the latter option to print to standard error (stderr) and silently return + on failure. The next code sample, hello_again.zig demonstrates the use of std.debug.print.

{#code_begin|exe|hello_again#} -- 2.54.0 From c2fb4bfff3b1a2bf4e7072cec04d67e8152900c1 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 13:54:16 +0300 Subject: [PATCH 237/295] add 'anytype' to self-hosted parser --- lib/std/zig/ast.zig | 12 +++++----- lib/std/zig/parse.zig | 15 ++++++------ lib/std/zig/render.zig | 48 ++++++++++++++++++++++----------------- lib/std/zig/tokenizer.zig | 5 +++- 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 72d8f3560e36a657daba2e7855f77d0658218e41..e95a8855af765ed2bfa09121bae944b3b5f8cbc1 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -434,7 +434,7 @@ pub const Node = struct { Suspend, // Type expressions - VarType, + AnyType, ErrorType, FnProto, AnyFrameType, @@ -2732,19 +2732,19 @@ pub const Node = struct { } }; - pub const VarType = struct { - base: Node = Node{ .id = .VarType }, + pub const AnyType = struct { + base: Node = Node{ .id = .AnyType }, token: TokenIndex, - pub fn iterate(self: *const VarType, index: usize) ?*Node { + pub fn iterate(self: *const AnyType, index: usize) ?*Node { return null; } - pub fn firstToken(self: *const VarType) TokenIndex { + pub fn firstToken(self: *const AnyType) TokenIndex { return self.token; } - pub fn lastToken(self: *const VarType) TokenIndex { + pub fn lastToken(self: *const AnyType) TokenIndex { return self.token; } }; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 5a34c0ff91479ace5650d098ff22987f2f01602d..4f8eff3c68d5178a7e021bafe94a59d9521c163e 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -488,7 +488,7 @@ const Parser = struct { return p.parseUse(); } - /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_var / TypeExpr) + /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (Keyword_anytype / TypeExpr) fn parseFnProto(p: *Parser) !?*Node { // TODO: Remove once extern/async fn rewriting is var is_async = false; @@ -618,9 +618,9 @@ const Parser = struct { var align_expr: ?*Node = null; var type_expr: ?*Node = null; if (p.eatToken(.Colon)) |_| { - if (p.eatToken(.Keyword_var)) |var_tok| { - const node = try p.arena.allocator.create(Node.VarType); - node.* = .{ .token = var_tok }; + if (p.eatToken(.Keyword_anytype) orelse p.eatToken(.Keyword_var)) |anytype_tok| { + const node = try p.arena.allocator.create(Node.AnyType); + node.* = .{ .token = anytype_tok }; type_expr = &node.base; } else { type_expr = try p.expectNode(parseTypeExpr, .{ @@ -2022,7 +2022,7 @@ const Parser = struct { } /// ParamType - /// <- KEYWORD_var + /// <- Keyword_anytype /// / DOT3 /// / TypeExpr fn parseParamType(p: *Parser) !?Node.FnProto.ParamDecl.ParamType { @@ -3058,8 +3058,9 @@ const Parser = struct { } fn parseVarType(p: *Parser) !?*Node { - const token = p.eatToken(.Keyword_var) orelse return null; - const node = try p.arena.allocator.create(Node.VarType); + const token = p.eatToken(.Keyword_anytype) orelse + p.eatToken(.Keyword_var) orelse return null; // TODO remove in next release cycle + const node = try p.arena.allocator.create(Node.AnyType); node.* = .{ .token = token, }; diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 1a30e46ee076114cb63697c09b564aa9a7d62144..3150d6a3f4470e44b235f92296e7002091634217 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -12,7 +12,7 @@ pub const Error = error{ }; /// Returns whether anything changed -pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf(stream).Error || Error)!bool { +pub fn render(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree) (@TypeOf(stream).Error || Error)!bool { // cannot render an invalid tree std.debug.assert(tree.errors.len == 0); @@ -64,7 +64,7 @@ pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf( fn renderRoot( allocator: *mem.Allocator, - stream: var, + stream: anytype, tree: *ast.Tree, ) (@TypeOf(stream).Error || Error)!void { // render all the line comments at the beginning of the file @@ -191,13 +191,13 @@ fn renderRoot( } } -fn renderExtraNewline(tree: *ast.Tree, stream: var, start_col: *usize, node: *ast.Node) @TypeOf(stream).Error!void { +fn renderExtraNewline(tree: *ast.Tree, stream: anytype, start_col: *usize, node: *ast.Node) @TypeOf(stream).Error!void { return renderExtraNewlineToken(tree, stream, start_col, node.firstToken()); } fn renderExtraNewlineToken( tree: *ast.Tree, - stream: var, + stream: anytype, start_col: *usize, first_token: ast.TokenIndex, ) @TypeOf(stream).Error!void { @@ -218,11 +218,11 @@ fn renderExtraNewlineToken( } } -fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node) (@TypeOf(stream).Error || Error)!void { +fn renderTopLevelDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node) (@TypeOf(stream).Error || Error)!void { try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Newline); } -fn renderContainerDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node, space: Space) (@TypeOf(stream).Error || Error)!void { +fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node, space: Space) (@TypeOf(stream).Error || Error)!void { switch (decl.id) { .FnProto => { const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); @@ -358,7 +358,7 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, fn renderExpression( allocator: *mem.Allocator, - stream: var, + stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, @@ -1179,9 +1179,15 @@ fn renderExpression( const error_type = @fieldParentPtr(ast.Node.ErrorType, "base", base); return renderToken(tree, stream, error_type.token, indent, start_col, space); }, - .VarType => { - const var_type = @fieldParentPtr(ast.Node.VarType, "base", base); - return renderToken(tree, stream, var_type.token, indent, start_col, space); + .AnyType => { + const any_type = @fieldParentPtr(ast.Node.AnyType, "base", base); + if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) { + // TODO remove in next release cycle + try stream.writeAll("anytype"); + if (space == .Comma) try stream.writeAll(",\n"); + return; + } + return renderToken(tree, stream, any_type.token, indent, start_col, space); }, .ContainerDecl => { const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base); @@ -2053,7 +2059,7 @@ fn renderExpression( fn renderAsmOutput( allocator: *mem.Allocator, - stream: var, + stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, @@ -2081,7 +2087,7 @@ fn renderAsmOutput( fn renderAsmInput( allocator: *mem.Allocator, - stream: var, + stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, @@ -2099,7 +2105,7 @@ fn renderAsmInput( fn renderVarDecl( allocator: *mem.Allocator, - stream: var, + stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, @@ -2171,7 +2177,7 @@ fn renderVarDecl( fn renderParamDecl( allocator: *mem.Allocator, - stream: var, + stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, @@ -2198,7 +2204,7 @@ fn renderParamDecl( fn renderStatement( allocator: *mem.Allocator, - stream: var, + stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, @@ -2236,7 +2242,7 @@ const Space = enum { fn renderTokenOffset( tree: *ast.Tree, - stream: var, + stream: anytype, token_index: ast.TokenIndex, indent: usize, start_col: *usize, @@ -2434,7 +2440,7 @@ fn renderTokenOffset( fn renderToken( tree: *ast.Tree, - stream: var, + stream: anytype, token_index: ast.TokenIndex, indent: usize, start_col: *usize, @@ -2445,8 +2451,8 @@ fn renderToken( fn renderDocComments( tree: *ast.Tree, - stream: var, - node: var, + stream: anytype, + node: anytype, indent: usize, start_col: *usize, ) (@TypeOf(stream).Error || Error)!void { @@ -2456,7 +2462,7 @@ fn renderDocComments( fn renderDocCommentsToken( tree: *ast.Tree, - stream: var, + stream: anytype, comment: *ast.Node.DocComment, first_token: ast.TokenIndex, indent: usize, @@ -2532,7 +2538,7 @@ const FindByteOutStream = struct { } }; -fn copyFixingWhitespace(stream: var, slice: []const u8) @TypeOf(stream).Error!void { +fn copyFixingWhitespace(stream: anytype, slice: []const u8) @TypeOf(stream).Error!void { for (slice) |byte| switch (byte) { '\t' => try stream.writeAll(" "), '\r' => {}, diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 3bf0d350cf8ad5e33dcdf544386c314e2c9e0138..7f9c6f628895885463d629b40148140218b5c02c 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -15,6 +15,7 @@ pub const Token = struct { .{ "allowzero", .Keyword_allowzero }, .{ "and", .Keyword_and }, .{ "anyframe", .Keyword_anyframe }, + .{ "anytype", .Keyword_anytype }, .{ "asm", .Keyword_asm }, .{ "async", .Keyword_async }, .{ "await", .Keyword_await }, @@ -140,6 +141,8 @@ pub const Token = struct { Keyword_align, Keyword_allowzero, Keyword_and, + Keyword_anyframe, + Keyword_anytype, Keyword_asm, Keyword_async, Keyword_await, @@ -168,7 +171,6 @@ pub const Token = struct { Keyword_or, Keyword_orelse, Keyword_packed, - Keyword_anyframe, Keyword_pub, Keyword_resume, Keyword_return, @@ -263,6 +265,7 @@ pub const Token = struct { .Keyword_allowzero => "allowzero", .Keyword_and => "and", .Keyword_anyframe => "anyframe", + .Keyword_anytype => "anytype", .Keyword_asm => "asm", .Keyword_async => "async", .Keyword_await => "await", -- 2.54.0 From 8110639c7964fcb23c2b715f97ab6caa27506b93 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 14:08:20 +0300 Subject: [PATCH 238/295] add 'anytype' to stage1 and langref --- doc/langref.html.in | 87 +++++++++++++++++++++++---------------------- src/all_types.hpp | 8 ++--- src/analyze.cpp | 16 ++++----- src/ast_render.cpp | 16 ++++----- src/codegen.cpp | 4 +-- src/ir.cpp | 59 ++++++++++++++++-------------- src/parser.cpp | 26 +++++++------- src/tokenizer.cpp | 2 ++ src/tokenizer.hpp | 1 + 9 files changed, 114 insertions(+), 105 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index f64170817f3e46d7ba8d24ec6b1d0b0cd9f41301..015865ee3bfdfc73bf664794462b75d2b7871e2d 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1785,7 +1785,7 @@ test "fully anonymous list literal" { dump(.{ @as(u32, 1234), @as(f64, 12.34), true, "hi"}); } -fn dump(args: var) void { +fn dump(args: anytype) void { assert(args.@"0" == 1234); assert(args.@"1" == 12.34); assert(args.@"2"); @@ -2717,7 +2717,7 @@ test "fully anonymous struct" { }); } -fn dump(args: var) void { +fn dump(args: anytype) void { assert(args.int == 1234); assert(args.float == 12.34); assert(args.b); @@ -4181,14 +4181,14 @@ test "pass struct to function" { {#header_close#} {#header_open|Function Parameter Type Inference#}

- Function parameters can be declared with {#syntax#}var{#endsyntax#} in place of the type. + Function parameters can be declared with {#syntax#}anytype{#endsyntax#} in place of the type. In this case the parameter types will be inferred when the function is called. Use {#link|@TypeOf#} and {#link|@typeInfo#} to get information about the inferred type.

{#code_begin|test#} const assert = @import("std").debug.assert; -fn addFortyTwo(x: var) @TypeOf(x) { +fn addFortyTwo(x: anytype) @TypeOf(x) { return x + 42; } @@ -5974,7 +5974,7 @@ pub fn main() void { {#code_begin|syntax#} /// Calls print and then flushes the buffer. -pub fn printf(self: *OutStream, comptime format: []const u8, args: var) anyerror!void { +pub fn printf(self: *OutStream, comptime format: []const u8, args: anytype) anyerror!void { const State = enum { Start, OpenBrace, @@ -6060,7 +6060,7 @@ pub fn printf(self: *OutStream, arg0: i32, arg1: []const u8) !void { on the type:

{#code_begin|syntax#} -pub fn printValue(self: *OutStream, value: var) !void { +pub fn printValue(self: *OutStream, value: anytype) !void { switch (@typeInfo(@TypeOf(value))) { .Int => { return self.printInt(T, value); @@ -6686,7 +6686,7 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 {

{#header_close#} {#header_open|@alignCast#} -
{#syntax#}@alignCast(comptime alignment: u29, ptr: var) var{#endsyntax#}
+
{#syntax#}@alignCast(comptime alignment: u29, ptr: anytype) anytype{#endsyntax#}

{#syntax#}ptr{#endsyntax#} can be {#syntax#}*T{#endsyntax#}, {#syntax#}fn(){#endsyntax#}, {#syntax#}?*T{#endsyntax#}, {#syntax#}?fn(){#endsyntax#}, or {#syntax#}[]T{#endsyntax#}. It returns the same type as {#syntax#}ptr{#endsyntax#} @@ -6723,7 +6723,7 @@ comptime { {#header_close#} {#header_open|@asyncCall#} -

{#syntax#}@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: var) anyframe->T{#endsyntax#}
+
{#syntax#}@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: anytype) anyframe->T{#endsyntax#}

{#syntax#}@asyncCall{#endsyntax#} performs an {#syntax#}async{#endsyntax#} call on a function pointer, which may or may not be an {#link|async function|Async Functions#}. @@ -6811,7 +6811,7 @@ fn func(y: *i32) void {

{#header_close#} {#header_open|@bitCast#} -
{#syntax#}@bitCast(comptime DestType: type, value: var) DestType{#endsyntax#}
+
{#syntax#}@bitCast(comptime DestType: type, value: anytype) DestType{#endsyntax#}

Converts a value of one type to another type.

@@ -6932,7 +6932,7 @@ fn func(y: *i32) void { {#header_close#} {#header_open|@call#} -
{#syntax#}@call(options: std.builtin.CallOptions, function: var, args: var) var{#endsyntax#}
+
{#syntax#}@call(options: std.builtin.CallOptions, function: anytype, args: anytype) anytype{#endsyntax#}

Calls a function, in the same way that invoking an expression with parentheses does:

@@ -7279,7 +7279,7 @@ test "main" { {#header_close#} {#header_open|@enumToInt#} -
{#syntax#}@enumToInt(enum_or_tagged_union: var) var{#endsyntax#}
+
{#syntax#}@enumToInt(enum_or_tagged_union: anytype) anytype{#endsyntax#}

Converts an enumeration value into its integer tag type. When a tagged union is passed, the tag value is used as the enumeration value. @@ -7314,7 +7314,7 @@ test "main" { {#header_close#} {#header_open|@errorToInt#} -

{#syntax#}@errorToInt(err: var) std.meta.IntType(false, @sizeOf(anyerror) * 8){#endsyntax#}
+
{#syntax#}@errorToInt(err: anytype) std.meta.IntType(false, @sizeOf(anyerror) * 8){#endsyntax#}

Supports the following types:

@@ -7334,7 +7334,7 @@ test "main" { {#header_close#} {#header_open|@errSetCast#} -
{#syntax#}@errSetCast(comptime T: DestType, value: var) DestType{#endsyntax#}
+
{#syntax#}@errSetCast(comptime T: DestType, value: anytype) DestType{#endsyntax#}

Converts an error value from one error set to another error set. Attempting to convert an error which is not in the destination error set results in safety-protected {#link|Undefined Behavior#}. @@ -7342,7 +7342,7 @@ test "main" { {#header_close#} {#header_open|@export#} -

{#syntax#}@export(target: var, comptime options: std.builtin.ExportOptions) void{#endsyntax#}
+
{#syntax#}@export(target: anytype, comptime options: std.builtin.ExportOptions) void{#endsyntax#}

Creates a symbol in the output object file.

@@ -7387,7 +7387,7 @@ export fn @"A function name that is a complete sentence."() void {} {#header_close#} {#header_open|@field#} -
{#syntax#}@field(lhs: var, comptime field_name: []const u8) (field){#endsyntax#}
+
{#syntax#}@field(lhs: anytype, comptime field_name: []const u8) (field){#endsyntax#}

Performs field access by a compile-time string.

{#code_begin|test#} @@ -7421,7 +7421,7 @@ test "field access by string" { {#header_close#} {#header_open|@floatCast#} -
{#syntax#}@floatCast(comptime DestType: type, value: var) DestType{#endsyntax#}
+
{#syntax#}@floatCast(comptime DestType: type, value: anytype) DestType{#endsyntax#}

Convert from one float type to another. This cast is safe, but may cause the numeric value to lose precision. @@ -7429,7 +7429,7 @@ test "field access by string" { {#header_close#} {#header_open|@floatToInt#} -

{#syntax#}@floatToInt(comptime DestType: type, float: var) DestType{#endsyntax#}
+
{#syntax#}@floatToInt(comptime DestType: type, float: anytype) DestType{#endsyntax#}

Converts the integer part of a floating point number to the destination type.

@@ -7455,7 +7455,7 @@ test "field access by string" { {#header_close#} {#header_open|@Frame#} -
{#syntax#}@Frame(func: var) type{#endsyntax#}
+
{#syntax#}@Frame(func: anytype) type{#endsyntax#}

This function returns the frame type of a function. This works for {#link|Async Functions#} as well as any function without a specific calling convention. @@ -7581,7 +7581,7 @@ test "@hasDecl" { {#header_close#} {#header_open|@intCast#} -

{#syntax#}@intCast(comptime DestType: type, int: var) DestType{#endsyntax#}
+
{#syntax#}@intCast(comptime DestType: type, int: anytype) DestType{#endsyntax#}

Converts an integer to another integer while keeping the same numerical value. Attempting to convert a number which is out of range of the destination type results in @@ -7622,7 +7622,7 @@ test "@hasDecl" { {#header_close#} {#header_open|@intToFloat#} -

{#syntax#}@intToFloat(comptime DestType: type, int: var) DestType{#endsyntax#}
+
{#syntax#}@intToFloat(comptime DestType: type, int: anytype) DestType{#endsyntax#}

Converts an integer to the closest floating point representation. To convert the other way, use {#link|@floatToInt#}. This cast is always safe.

@@ -7773,7 +7773,7 @@ test "@wasmMemoryGrow" { {#header_close#} {#header_open|@ptrCast#} -
{#syntax#}@ptrCast(comptime DestType: type, value: var) DestType{#endsyntax#}
+
{#syntax#}@ptrCast(comptime DestType: type, value: anytype) DestType{#endsyntax#}

Converts a pointer of one type to a pointer of another type.

@@ -7784,7 +7784,7 @@ test "@wasmMemoryGrow" { {#header_close#} {#header_open|@ptrToInt#} -
{#syntax#}@ptrToInt(value: var) usize{#endsyntax#}
+
{#syntax#}@ptrToInt(value: anytype) usize{#endsyntax#}

Converts {#syntax#}value{#endsyntax#} to a {#syntax#}usize{#endsyntax#} which is the address of the pointer. {#syntax#}value{#endsyntax#} can be one of these types:

@@ -8042,7 +8042,7 @@ test "@setRuntimeSafety" { {#header_close#} {#header_open|@splat#} -
{#syntax#}@splat(comptime len: u32, scalar: var) std.meta.Vector(len, @TypeOf(scalar)){#endsyntax#}
+
{#syntax#}@splat(comptime len: u32, scalar: anytype) std.meta.Vector(len, @TypeOf(scalar)){#endsyntax#}

Produces a vector of length {#syntax#}len{#endsyntax#} where each element is the value {#syntax#}scalar{#endsyntax#}: @@ -8088,7 +8088,7 @@ fn doTheTest() void { {#code_end#} {#header_close#} {#header_open|@sqrt#} -

{#syntax#}@sqrt(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@sqrt(value: anytype) @TypeOf(value){#endsyntax#}

Performs the square root of a floating point number. Uses a dedicated hardware instruction when available. @@ -8099,7 +8099,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@sin#} -
{#syntax#}@sin(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@sin(value: anytype) @TypeOf(value){#endsyntax#}

Sine trigometric function on a floating point number. Uses a dedicated hardware instruction when available. @@ -8110,7 +8110,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@cos#} -
{#syntax#}@cos(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@cos(value: anytype) @TypeOf(value){#endsyntax#}

Cosine trigometric function on a floating point number. Uses a dedicated hardware instruction when available. @@ -8121,7 +8121,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@exp#} -
{#syntax#}@exp(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@exp(value: anytype) @TypeOf(value){#endsyntax#}

Base-e exponential function on a floating point number. Uses a dedicated hardware instruction when available. @@ -8132,7 +8132,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@exp2#} -
{#syntax#}@exp2(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@exp2(value: anytype) @TypeOf(value){#endsyntax#}

Base-2 exponential function on a floating point number. Uses a dedicated hardware instruction when available. @@ -8143,7 +8143,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@log#} -
{#syntax#}@log(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@log(value: anytype) @TypeOf(value){#endsyntax#}

Returns the natural logarithm of a floating point number. Uses a dedicated hardware instruction when available. @@ -8154,7 +8154,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@log2#} -
{#syntax#}@log2(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@log2(value: anytype) @TypeOf(value){#endsyntax#}

Returns the logarithm to the base 2 of a floating point number. Uses a dedicated hardware instruction when available. @@ -8165,7 +8165,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@log10#} -
{#syntax#}@log10(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@log10(value: anytype) @TypeOf(value){#endsyntax#}

Returns the logarithm to the base 10 of a floating point number. Uses a dedicated hardware instruction when available. @@ -8176,7 +8176,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@fabs#} -
{#syntax#}@fabs(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@fabs(value: anytype) @TypeOf(value){#endsyntax#}

Returns the absolute value of a floating point number. Uses a dedicated hardware instruction when available. @@ -8187,7 +8187,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@floor#} -
{#syntax#}@floor(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@floor(value: anytype) @TypeOf(value){#endsyntax#}

Returns the largest integral value not greater than the given floating point number. Uses a dedicated hardware instruction when available. @@ -8198,7 +8198,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@ceil#} -
{#syntax#}@ceil(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@ceil(value: anytype) @TypeOf(value){#endsyntax#}

Returns the largest integral value not less than the given floating point number. Uses a dedicated hardware instruction when available. @@ -8209,7 +8209,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@trunc#} -
{#syntax#}@trunc(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@trunc(value: anytype) @TypeOf(value){#endsyntax#}

Rounds the given floating point number to an integer, towards zero. Uses a dedicated hardware instruction when available. @@ -8220,7 +8220,7 @@ fn doTheTest() void {

{#header_close#} {#header_open|@round#} -
{#syntax#}@round(value: var) @TypeOf(value){#endsyntax#}
+
{#syntax#}@round(value: anytype) @TypeOf(value){#endsyntax#}

Rounds the given floating point number to an integer, away from zero. Uses a dedicated hardware instruction when available. @@ -8241,7 +8241,7 @@ fn doTheTest() void { {#header_close#} {#header_open|@tagName#} -

{#syntax#}@tagName(value: var) []const u8{#endsyntax#}
+
{#syntax#}@tagName(value: anytype) []const u8{#endsyntax#}

Converts an enum value or union value to a slice of bytes representing the name.

If the enum is non-exhaustive and the tag value does not map to a name, it invokes safety-checked {#link|Undefined Behavior#}.

@@ -8292,7 +8292,7 @@ fn List(comptime T: type) type { {#header_close#} {#header_open|@truncate#} -
{#syntax#}@truncate(comptime T: type, integer: var) T{#endsyntax#}
+
{#syntax#}@truncate(comptime T: type, integer: anytype) T{#endsyntax#}

This function truncates bits from an integer type, resulting in a smaller or same-sized integer type. @@ -10214,7 +10214,7 @@ TopLevelDecl / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl / KEYWORD_usingnamespace Expr SEMICOLON -FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_var / TypeExpr) +FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_anytype / TypeExpr) VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON @@ -10386,7 +10386,7 @@ LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN ParamDecl <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType ParamType - <- KEYWORD_var + <- KEYWORD_anytype / DOT3 / TypeExpr @@ -10624,6 +10624,7 @@ KEYWORD_align <- 'align' end_of_word KEYWORD_allowzero <- 'allowzero' end_of_word KEYWORD_and <- 'and' end_of_word KEYWORD_anyframe <- 'anyframe' end_of_word +KEYWORD_anytype <- 'anytype' end_of_word KEYWORD_asm <- 'asm' end_of_word KEYWORD_async <- 'async' end_of_word KEYWORD_await <- 'await' end_of_word @@ -10669,14 +10670,14 @@ KEYWORD_var <- 'var' end_of_word KEYWORD_volatile <- 'volatile' end_of_word KEYWORD_while <- 'while' end_of_word -keyword <- KEYWORD_align / KEYWORD_and / KEYWORD_allowzero / KEYWORD_asm - / KEYWORD_async / KEYWORD_await / KEYWORD_break +keyword <- KEYWORD_align / KEYWORD_and / KEYWORD_anyframe / KEYWORD_anytype + / KEYWORD_allowzero / KEYWORD_asm / KEYWORD_async / KEYWORD_await / KEYWORD_break / KEYWORD_catch / KEYWORD_comptime / KEYWORD_const / KEYWORD_continue / KEYWORD_defer / KEYWORD_else / KEYWORD_enum / KEYWORD_errdefer / KEYWORD_error / KEYWORD_export / KEYWORD_extern / KEYWORD_false / KEYWORD_fn / KEYWORD_for / KEYWORD_if / KEYWORD_inline / KEYWORD_noalias / KEYWORD_null / KEYWORD_or - / KEYWORD_orelse / KEYWORD_packed / KEYWORD_anyframe / KEYWORD_pub + / KEYWORD_orelse / KEYWORD_packed / KEYWORD_pub / KEYWORD_resume / KEYWORD_return / KEYWORD_linksection / KEYWORD_struct / KEYWORD_suspend / KEYWORD_switch / KEYWORD_test / KEYWORD_threadlocal / KEYWORD_true / KEYWORD_try diff --git a/src/all_types.hpp b/src/all_types.hpp index 4465bf674cac123c1e86ba33cf1e2762d141f88d..a73efe2c82b48146415fc649c36494d6a648060e 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -692,7 +692,7 @@ enum NodeType { NodeTypeSuspend, NodeTypeAnyFrameType, NodeTypeEnumLiteral, - NodeTypeVarFieldType, + NodeTypeAnyTypeField, }; enum FnInline { @@ -705,7 +705,7 @@ struct AstNodeFnProto { Buf *name; ZigList params; AstNode *return_type; - Token *return_var_token; + Token *return_anytype_token; AstNode *fn_def_node; // populated if this is an extern declaration Buf *lib_name; @@ -734,7 +734,7 @@ struct AstNodeFnDef { struct AstNodeParamDecl { Buf *name; AstNode *type; - Token *var_token; + Token *anytype_token; Buf doc_comments; bool is_noalias; bool is_comptime; @@ -2145,7 +2145,7 @@ struct CodeGen { ZigType *entry_num_lit_float; ZigType *entry_undef; ZigType *entry_null; - ZigType *entry_var; + ZigType *entry_anytype; ZigType *entry_global_error_set; ZigType *entry_enum_literal; ZigType *entry_any_frame; diff --git a/src/analyze.cpp b/src/analyze.cpp index afe0fe6849130ff4e9c5592910cdf8f4d6b12e59..5eba515e687656d5dfa5526a420755587bd84346 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1129,7 +1129,7 @@ ZigValue *analyze_const_value(CodeGen *g, Scope *scope, AstNode *node, ZigType * ZigValue *result = g->pass1_arena->create(); ZigValue *result_ptr = g->pass1_arena->create(); result->special = ConstValSpecialUndef; - result->type = (type_entry == nullptr) ? g->builtin_types.entry_var : type_entry; + result->type = (type_entry == nullptr) ? g->builtin_types.entry_anytype : type_entry; result_ptr->special = ConstValSpecialStatic; result_ptr->type = get_pointer_to_type(g, result->type, false); result_ptr->data.x_ptr.mut = ConstPtrMutComptimeVar; @@ -1230,7 +1230,7 @@ Error type_val_resolve_zero_bits(CodeGen *g, ZigValue *type_val, ZigType *parent Error type_val_resolve_is_opaque_type(CodeGen *g, ZigValue *type_val, bool *is_opaque_type) { if (type_val->special != ConstValSpecialLazy) { assert(type_val->special == ConstValSpecialStatic); - if (type_val->data.x_type == g->builtin_types.entry_var) { + if (type_val->data.x_type == g->builtin_types.entry_anytype) { *is_opaque_type = false; return ErrorNone; } @@ -1853,10 +1853,10 @@ static ZigType *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_sc buf_sprintf("var args only allowed in functions with C calling convention")); return g->builtin_types.entry_invalid; } - } else if (param_node->data.param_decl.var_token != nullptr) { + } else if (param_node->data.param_decl.anytype_token != nullptr) { if (!calling_convention_allows_zig_types(fn_type_id.cc)) { add_node_error(g, param_node, - buf_sprintf("parameter of type 'var' not allowed in function with calling convention '%s'", + buf_sprintf("parameter of type 'anytype' not allowed in function with calling convention '%s'", calling_convention_name(fn_type_id.cc))); return g->builtin_types.entry_invalid; } @@ -1942,10 +1942,10 @@ static ZigType *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_sc fn_entry->align_bytes = fn_type_id.alignment; } - if (fn_proto->return_var_token != nullptr) { + if (fn_proto->return_anytype_token != nullptr) { if (!calling_convention_allows_zig_types(fn_type_id.cc)) { add_node_error(g, fn_proto->return_type, - buf_sprintf("return type 'var' not allowed in function with calling convention '%s'", + buf_sprintf("return type 'anytype' not allowed in function with calling convention '%s'", calling_convention_name(fn_type_id.cc))); return g->builtin_types.entry_invalid; } @@ -3802,7 +3802,7 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node) { case NodeTypeEnumLiteral: case NodeTypeAnyFrameType: case NodeTypeErrorSetField: - case NodeTypeVarFieldType: + case NodeTypeAnyTypeField: zig_unreachable(); } } @@ -5868,7 +5868,7 @@ ZigValue *get_the_one_possible_value(CodeGen *g, ZigType *type_entry) { ReqCompTime type_requires_comptime(CodeGen *g, ZigType *ty) { Error err; - if (ty == g->builtin_types.entry_var) { + if (ty == g->builtin_types.entry_anytype) { return ReqCompTimeYes; } switch (ty->id) { diff --git a/src/ast_render.cpp b/src/ast_render.cpp index 49fad80a4055f262852918fd43f3c216c1cd2e50..ad308bf416a900d791892d94dbb3d09bb6fa0039 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -270,8 +270,8 @@ static const char *node_type_str(NodeType node_type) { return "EnumLiteral"; case NodeTypeErrorSetField: return "ErrorSetField"; - case NodeTypeVarFieldType: - return "VarFieldType"; + case NodeTypeAnyTypeField: + return "AnyTypeField"; } zig_unreachable(); } @@ -466,8 +466,8 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { } if (param_decl->data.param_decl.is_var_args) { fprintf(ar->f, "..."); - } else if (param_decl->data.param_decl.var_token != nullptr) { - fprintf(ar->f, "var"); + } else if (param_decl->data.param_decl.anytype_token != nullptr) { + fprintf(ar->f, "anytype"); } else { render_node_grouped(ar, param_decl->data.param_decl.type); } @@ -496,8 +496,8 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { fprintf(ar->f, ")"); } - if (node->data.fn_proto.return_var_token != nullptr) { - fprintf(ar->f, "var"); + if (node->data.fn_proto.return_anytype_token != nullptr) { + fprintf(ar->f, "anytype"); } else { AstNode *return_type_node = node->data.fn_proto.return_type; assert(return_type_node != nullptr); @@ -1216,8 +1216,8 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { fprintf(ar->f, ".%s", buf_ptr(&node->data.enum_literal.identifier->data.str_lit.str)); break; } - case NodeTypeVarFieldType: { - fprintf(ar->f, "var"); + case NodeTypeAnyTypeField: { + fprintf(ar->f, "anytype"); break; } case NodeTypeParamDecl: diff --git a/src/codegen.cpp b/src/codegen.cpp index 2f72861bc2a9dd4e66136f4006ffdc749e012b1c..3473a2b0ac3bdac3dde1ac9af22801cb374c793c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -8448,8 +8448,8 @@ static void define_builtin_types(CodeGen *g) { } { ZigType *entry = new_type_table_entry(ZigTypeIdOpaque); - buf_init_from_str(&entry->name, "(var)"); - g->builtin_types.entry_var = entry; + buf_init_from_str(&entry->name, "(anytype)"); + g->builtin_types.entry_anytype = entry; } for (size_t i = 0; i < array_length(c_int_type_infos); i += 1) { diff --git a/src/ir.cpp b/src/ir.cpp index ca0fd47c4924a6dd1cc3ed1ce6b7edfec458fa95..6447db8c715d70df70b561314de7984903b8efa6 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -9942,7 +9942,7 @@ static IrInstSrc *ir_gen_fn_proto(IrBuilderSrc *irb, Scope *parent_scope, AstNod is_var_args = true; break; } - if (param_node->data.param_decl.var_token == nullptr) { + if (param_node->data.param_decl.anytype_token == nullptr) { AstNode *type_node = param_node->data.param_decl.type; IrInstSrc *type_value = ir_gen_node(irb, type_node, parent_scope); if (type_value == irb->codegen->invalid_inst_src) @@ -9968,7 +9968,7 @@ static IrInstSrc *ir_gen_fn_proto(IrBuilderSrc *irb, Scope *parent_scope, AstNod } IrInstSrc *return_type; - if (node->data.fn_proto.return_var_token == nullptr) { + if (node->data.fn_proto.return_anytype_token == nullptr) { if (node->data.fn_proto.return_type == nullptr) { return_type = ir_build_const_type(irb, parent_scope, node, irb->codegen->builtin_types.entry_void); } else { @@ -10226,9 +10226,9 @@ static IrInstSrc *ir_gen_node_raw(IrBuilderSrc *irb, AstNode *node, Scope *scope add_node_error(irb->codegen, node, buf_sprintf("inferred array size invalid here")); return irb->codegen->invalid_inst_src; - case NodeTypeVarFieldType: + case NodeTypeAnyTypeField: return ir_lval_wrap(irb, scope, - ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_var), lval, result_loc); + ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_anytype), lval, result_loc); } zig_unreachable(); } @@ -10296,7 +10296,7 @@ static IrInstSrc *ir_gen_node_extra(IrBuilderSrc *irb, AstNode *node, Scope *sco case NodeTypeSuspend: case NodeTypeEnumLiteral: case NodeTypeInferredArrayType: - case NodeTypeVarFieldType: + case NodeTypeAnyTypeField: case NodeTypePrefixOpExpr: add_node_error(irb->codegen, node, buf_sprintf("invalid left-hand side to assignment")); @@ -10518,7 +10518,7 @@ ZigValue *const_ptr_pointee(IrAnalyze *ira, CodeGen *codegen, ZigValue *const_va if (val == nullptr) return nullptr; assert(const_val->type->id == ZigTypeIdPointer); ZigType *expected_type = const_val->type->data.pointer.child_type; - if (expected_type == codegen->builtin_types.entry_var) { + if (expected_type == codegen->builtin_types.entry_anytype) { return val; } switch (type_has_one_possible_value(codegen, expected_type)) { @@ -15040,7 +15040,7 @@ static IrInstGen *ir_analyze_cast(IrAnalyze *ira, IrInst *source_instr, } // This means the wanted type is anything. - if (wanted_type == ira->codegen->builtin_types.entry_var) { + if (wanted_type == ira->codegen->builtin_types.entry_anytype) { return value; } @@ -15635,7 +15635,7 @@ static IrInstGen *ir_implicit_cast(IrAnalyze *ira, IrInstGen *value, ZigType *ex static ZigType *get_ptr_elem_type(CodeGen *g, IrInstGen *ptr) { ir_assert_gen(ptr->value->type->id == ZigTypeIdPointer, ptr); ZigType *elem_type = ptr->value->type->data.pointer.child_type; - if (elem_type != g->builtin_types.entry_var) + if (elem_type != g->builtin_types.entry_anytype) return elem_type; if (ir_resolve_lazy(g, ptr->base.source_node, ptr->value)) @@ -15687,7 +15687,7 @@ static IrInstGen *ir_get_deref(IrAnalyze *ira, IrInst* source_instruction, IrIns } if (ptr->value->data.x_ptr.mut != ConstPtrMutRuntimeVar) { ZigValue *pointee = const_ptr_pointee_unchecked(ira->codegen, ptr->value); - if (child_type == ira->codegen->builtin_types.entry_var) { + if (child_type == ira->codegen->builtin_types.entry_anytype) { child_type = pointee->type; } if (pointee->special != ConstValSpecialRuntime) { @@ -19087,7 +19087,7 @@ static Error ir_result_has_type(IrAnalyze *ira, ResultLoc *result_loc, bool *out ZigType *dest_type = ir_resolve_type(ira, result_cast->base.source_instruction->child); if (type_is_invalid(dest_type)) return ErrorSemanticAnalyzeFail; - *out = (dest_type != ira->codegen->builtin_types.entry_var); + *out = (dest_type != ira->codegen->builtin_types.entry_anytype); return ErrorNone; } case ResultLocIdVar: @@ -19293,7 +19293,7 @@ static IrInstGen *ir_resolve_result_raw(IrAnalyze *ira, IrInst *suspend_source_i if (type_is_invalid(dest_type)) return ira->codegen->invalid_inst_gen; - if (dest_type == ira->codegen->builtin_types.entry_var) { + if (dest_type == ira->codegen->builtin_types.entry_anytype) { return ir_resolve_no_result_loc(ira, suspend_source_instr, result_loc, value_type); } @@ -19439,7 +19439,7 @@ static IrInstGen *ir_resolve_result_raw(IrAnalyze *ira, IrInst *suspend_source_i return ira->codegen->invalid_inst_gen; } - if (child_type != ira->codegen->builtin_types.entry_var) { + if (child_type != ira->codegen->builtin_types.entry_anytype) { if (type_size(ira->codegen, child_type) != type_size(ira->codegen, value_type)) { // pointer cast won't work; we need a temporary location. result_bit_cast->parent->written = parent_was_written; @@ -19600,9 +19600,9 @@ static IrInstGen *ir_analyze_instruction_resolve_result(IrAnalyze *ira, IrInstSr if (type_is_invalid(implicit_elem_type)) return ira->codegen->invalid_inst_gen; } else { - implicit_elem_type = ira->codegen->builtin_types.entry_var; + implicit_elem_type = ira->codegen->builtin_types.entry_anytype; } - if (implicit_elem_type == ira->codegen->builtin_types.entry_var) { + if (implicit_elem_type == ira->codegen->builtin_types.entry_anytype) { Buf *bare_name = buf_alloc(); Buf *name = get_anon_type_name(ira->codegen, nullptr, container_string(ContainerKindStruct), instruction->base.base.scope, instruction->base.base.source_node, bare_name); @@ -19759,7 +19759,7 @@ static bool ir_analyze_fn_call_inline_arg(IrAnalyze *ira, AstNode *fn_proto_node assert(param_decl_node->type == NodeTypeParamDecl); IrInstGen *casted_arg; - if (param_decl_node->data.param_decl.var_token == nullptr) { + if (param_decl_node->data.param_decl.anytype_token == nullptr) { AstNode *param_type_node = param_decl_node->data.param_decl.type; ZigType *param_type = ir_analyze_type_expr(ira, *exec_scope, param_type_node); if (type_is_invalid(param_type)) @@ -19799,7 +19799,7 @@ static bool ir_analyze_fn_call_generic_arg(IrAnalyze *ira, AstNode *fn_proto_nod arg_part_of_generic_id = true; casted_arg = arg; } else { - if (param_decl_node->data.param_decl.var_token == nullptr) { + if (param_decl_node->data.param_decl.anytype_token == nullptr) { AstNode *param_type_node = param_decl_node->data.param_decl.type; ZigType *param_type = ir_analyze_type_expr(ira, *child_scope, param_type_node); if (type_is_invalid(param_type)) @@ -20011,7 +20011,7 @@ static IrInstGen *ir_analyze_store_ptr(IrAnalyze *ira, IrInst* source_instr, } if (ptr->value->type->data.pointer.inferred_struct_field != nullptr && - child_type == ira->codegen->builtin_types.entry_var) + child_type == ira->codegen->builtin_types.entry_anytype) { child_type = ptr->value->type->data.pointer.inferred_struct_field->inferred_struct_type; } @@ -20202,6 +20202,11 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, } AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type; + if (return_type_node == nullptr) { + ir_add_error(ira, &fn_ref->base, + buf_sprintf("TODO implement inferred return types https://github.com/ziglang/zig/issues/447")); + return ira->codegen->invalid_inst_gen; + } ZigType *specified_return_type = ir_analyze_type_expr(ira, exec_scope, return_type_node); if (type_is_invalid(specified_return_type)) return ira->codegen->invalid_inst_gen; @@ -20364,7 +20369,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, inst_fn_type_id.alignment = align_bytes; } - if (fn_proto_node->data.fn_proto.return_var_token == nullptr) { + if (fn_proto_node->data.fn_proto.return_anytype_token == nullptr) { AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type; ZigType *specified_return_type = ir_analyze_type_expr(ira, impl_fn->child_scope, return_type_node); if (type_is_invalid(specified_return_type)) @@ -20463,7 +20468,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, if (type_is_invalid(dummy_result->value->type)) return ira->codegen->invalid_inst_gen; ZigType *res_child_type = result_loc->value->type->data.pointer.child_type; - if (res_child_type == ira->codegen->builtin_types.entry_var) { + if (res_child_type == ira->codegen->builtin_types.entry_anytype) { res_child_type = impl_fn_type_id->return_type; } if (!handle_is_ptr(ira->codegen, res_child_type)) { @@ -20606,7 +20611,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, if (type_is_invalid(dummy_result->value->type)) return ira->codegen->invalid_inst_gen; ZigType *res_child_type = result_loc->value->type->data.pointer.child_type; - if (res_child_type == ira->codegen->builtin_types.entry_var) { + if (res_child_type == ira->codegen->builtin_types.entry_anytype) { res_child_type = return_type; } if (!handle_is_ptr(ira->codegen, res_child_type)) { @@ -22337,7 +22342,7 @@ static IrInstGen *ir_analyze_inferred_field_ptr(IrAnalyze *ira, Buf *field_name, inferred_struct_field->inferred_struct_type = container_type; inferred_struct_field->field_name = field_name; - ZigType *elem_type = ira->codegen->builtin_types.entry_var; + ZigType *elem_type = ira->codegen->builtin_types.entry_anytype; ZigType *field_ptr_type = get_pointer_to_type_extra2(ira->codegen, elem_type, container_ptr_type->data.pointer.is_const, container_ptr_type->data.pointer.is_volatile, PtrLenSingle, 0, 0, 0, false, VECTOR_INDEX_NONE, inferred_struct_field, nullptr); @@ -25115,7 +25120,7 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, ZigType *ptr_type_ent fields[5]->special = ConstValSpecialStatic; fields[5]->type = ira->codegen->builtin_types.entry_bool; fields[5]->data.x_bool = attrs_type->data.pointer.allow_zero; - // sentinel: var + // sentinel: anytype ensure_field_index(result->type, "sentinel", 6); fields[6]->special = ConstValSpecialStatic; if (attrs_type->data.pointer.child_type->id != ZigTypeIdOpaque) { @@ -25243,7 +25248,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy fields[1]->special = ConstValSpecialStatic; fields[1]->type = ira->codegen->builtin_types.entry_type; fields[1]->data.x_type = type_entry->data.array.child_type; - // sentinel: var + // sentinel: anytype fields[2]->special = ConstValSpecialStatic; fields[2]->type = get_optional_type(ira->codegen, type_entry->data.array.child_type); fields[2]->data.x_optional = type_entry->data.array.sentinel; @@ -25598,7 +25603,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy inner_fields[2]->type = ira->codegen->builtin_types.entry_type; inner_fields[2]->data.x_type = struct_field->type_entry; - // default_value: var + // default_value: anytype inner_fields[3]->special = ConstValSpecialStatic; inner_fields[3]->type = get_optional_type2(ira->codegen, struct_field->type_entry); if (inner_fields[3]->type == nullptr) return ErrorSemanticAnalyzeFail; @@ -25736,7 +25741,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 1); result->data.x_struct.fields = fields; ZigFn *fn = type_entry->data.frame.fn; - // function: var + // function: anytype ensure_field_index(result->type, "function", 0); fields[0] = create_const_fn(ira->codegen, fn); break; @@ -29996,7 +30001,7 @@ static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgTy if (arg_index >= fn_type_id->param_count) { if (instruction->allow_var) { // TODO remove this with var args - return ir_const_type(ira, &instruction->base.base, ira->codegen->builtin_types.entry_var); + return ir_const_type(ira, &instruction->base.base, ira->codegen->builtin_types.entry_anytype); } ir_add_error(ira, &arg_index_inst->base, buf_sprintf("arg index %" ZIG_PRI_u64 " out of bounds; '%s' has %" ZIG_PRI_usize " arguments", @@ -30010,7 +30015,7 @@ static IrInstGen *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstSrcArgTy ir_assert(fn_type->data.fn.is_generic, &instruction->base.base); if (instruction->allow_var) { - return ir_const_type(ira, &instruction->base.base, ira->codegen->builtin_types.entry_var); + return ir_const_type(ira, &instruction->base.base, ira->codegen->builtin_types.entry_anytype); } else { ir_add_error(ira, &arg_index_inst->base, buf_sprintf("@ArgType could not resolve the type of arg %" ZIG_PRI_u64 " because '%s' is generic", diff --git a/src/parser.cpp b/src/parser.cpp index c6c58236725cd72b2122841b2cc38ade7eb77504..fcd1133b0d8fd46cf494cc50b059ee8854e9f7ef 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -786,7 +786,7 @@ static AstNode *ast_parse_top_level_decl(ParseContext *pc, VisibMod visib_mod, B return nullptr; } -// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_var / TypeExpr) +// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (KEYWORD_anytype / TypeExpr) static AstNode *ast_parse_fn_proto(ParseContext *pc) { Token *first = eat_token_if(pc, TokenIdKeywordFn); if (first == nullptr) { @@ -801,10 +801,10 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc) { AstNode *align_expr = ast_parse_byte_align(pc); AstNode *section_expr = ast_parse_link_section(pc); AstNode *callconv_expr = ast_parse_callconv(pc); - Token *var = eat_token_if(pc, TokenIdKeywordVar); + Token *anytype = eat_token_if(pc, TokenIdKeywordAnyType); Token *exmark = nullptr; AstNode *return_type = nullptr; - if (var == nullptr) { + if (anytype == nullptr) { exmark = eat_token_if(pc, TokenIdBang); return_type = ast_expect(pc, ast_parse_type_expr); } @@ -816,7 +816,7 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc) { res->data.fn_proto.align_expr = align_expr; res->data.fn_proto.section_expr = section_expr; res->data.fn_proto.callconv_expr = callconv_expr; - res->data.fn_proto.return_var_token = var; + res->data.fn_proto.return_anytype_token = anytype; res->data.fn_proto.auto_err_set = exmark != nullptr; res->data.fn_proto.return_type = return_type; @@ -870,9 +870,9 @@ static AstNode *ast_parse_container_field(ParseContext *pc) { AstNode *type_expr = nullptr; if (eat_token_if(pc, TokenIdColon) != nullptr) { - Token *var_tok = eat_token_if(pc, TokenIdKeywordVar); - if (var_tok != nullptr) { - type_expr = ast_create_node(pc, NodeTypeVarFieldType, var_tok); + Token *anytype_tok = eat_token_if(pc, TokenIdKeywordAnyType); + if (anytype_tok != nullptr) { + type_expr = ast_create_node(pc, NodeTypeAnyTypeField, anytype_tok); } else { type_expr = ast_expect(pc, ast_parse_type_expr); } @@ -2191,14 +2191,14 @@ static AstNode *ast_parse_param_decl(ParseContext *pc) { } // ParamType -// <- KEYWORD_var +// <- KEYWORD_anytype // / DOT3 // / TypeExpr static AstNode *ast_parse_param_type(ParseContext *pc) { - Token *var_token = eat_token_if(pc, TokenIdKeywordVar); - if (var_token != nullptr) { - AstNode *res = ast_create_node(pc, NodeTypeParamDecl, var_token); - res->data.param_decl.var_token = var_token; + Token *anytype_token = eat_token_if(pc, TokenIdKeywordAnyType); + if (anytype_token != nullptr) { + AstNode *res = ast_create_node(pc, NodeTypeParamDecl, anytype_token); + res->data.param_decl.anytype_token = anytype_token; return res; } @@ -3207,7 +3207,7 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont visit_field(&node->data.suspend.block, visit, context); break; case NodeTypeEnumLiteral: - case NodeTypeVarFieldType: + case NodeTypeAnyTypeField: break; } } diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index f09a146f2b521e4079dfe6f159fb7b25096dd369..487a125d6281c114cc10a1269f4e5df5551537c4 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -106,6 +106,7 @@ static const struct ZigKeyword zig_keywords[] = { {"allowzero", TokenIdKeywordAllowZero}, {"and", TokenIdKeywordAnd}, {"anyframe", TokenIdKeywordAnyFrame}, + {"anytype", TokenIdKeywordAnyType}, {"asm", TokenIdKeywordAsm}, {"async", TokenIdKeywordAsync}, {"await", TokenIdKeywordAwait}, @@ -1569,6 +1570,7 @@ const char * token_name(TokenId id) { case TokenIdKeywordAlign: return "align"; case TokenIdKeywordAnd: return "and"; case TokenIdKeywordAnyFrame: return "anyframe"; + case TokenIdKeywordAnyType: return "anytype"; case TokenIdKeywordAsm: return "asm"; case TokenIdKeywordBreak: return "break"; case TokenIdKeywordCatch: return "catch"; diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index 552ded4ef8860ef796464bc8433181e60b60526e..d8af21ee006eb2070990680e8c8540f6acf42008 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -54,6 +54,7 @@ enum TokenId { TokenIdKeywordAllowZero, TokenIdKeywordAnd, TokenIdKeywordAnyFrame, + TokenIdKeywordAnyType, TokenIdKeywordAsm, TokenIdKeywordAsync, TokenIdKeywordAwait, -- 2.54.0 From 2e6688ae27389dcb0d72f2a297b44eca95a02516 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 19:51:20 +0300 Subject: [PATCH 239/295] Add test for `@typeInfo` declarations showing up in declaration order Both the stage1 and std lib HashMap implementations now preserve insertion order --- test/stage1/behavior/type_info.zig | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index 2685a3552e3e750edb7d9e5b5b0cd7db3a8292ed..9da1da6e875b9a4d6c251eaaecce0a9da50c7a62 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -409,3 +409,19 @@ test "type info: value is correctly copied" { expect(@typeInfo([]u32).Pointer.size == .Slice); } } + +test "Declarations are returned in declaration order" { + const S = struct { + const a = 1; + const b = 2; + const c = 3; + const d = 4; + const e = 5; + }; + const d = @typeInfo(S).Struct.decls; + expect(std.mem.eql(u8, d[0].name, "a")); + expect(std.mem.eql(u8, d[1].name, "b")); + expect(std.mem.eql(u8, d[2].name, "c")); + expect(std.mem.eql(u8, d[3].name, "d")); + expect(std.mem.eql(u8, d[4].name, "e")); +} -- 2.54.0 From 9907116478efbdae5cac3de3879a1a8e42aecde8 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 20:11:20 +0300 Subject: [PATCH 240/295] use typeInfo instead of hardcoded tables in std.Target --- lib/std/target.zig | 39 +++--- lib/std/target/aarch64.zig | 45 ------- lib/std/target/amdgpu.zig | 45 ------- lib/std/target/arm.zig | 89 ------------- lib/std/target/avr.zig | 263 ------------------------------------- lib/std/target/bpf.zig | 11 -- lib/std/target/hexagon.zig | 13 -- lib/std/target/mips.zig | 25 ---- lib/std/target/msp430.zig | 9 -- lib/std/target/nvptx.zig | 21 --- lib/std/target/powerpc.zig | 44 ------- lib/std/target/riscv.zig | 10 -- lib/std/target/sparc.zig | 46 ------- lib/std/target/systemz.zig | 19 --- lib/std/target/wasm.zig | 9 -- lib/std/target/x86.zig | 85 ------------ 16 files changed, 24 insertions(+), 749 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 110b7a088f50a8a643a9fad2e7232837e12b6239..f20bec421ead104c05a4c0dfe2a872d874693333 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -903,25 +903,34 @@ pub const Target = struct { /// All processors Zig is aware of, sorted lexicographically by name. pub fn allCpuModels(arch: Arch) []const *const Cpu.Model { return switch (arch) { - .arm, .armeb, .thumb, .thumbeb => arm.all_cpus, - .aarch64, .aarch64_be, .aarch64_32 => aarch64.all_cpus, - .avr => avr.all_cpus, - .bpfel, .bpfeb => bpf.all_cpus, - .hexagon => hexagon.all_cpus, - .mips, .mipsel, .mips64, .mips64el => mips.all_cpus, - .msp430 => msp430.all_cpus, - .powerpc, .powerpc64, .powerpc64le => powerpc.all_cpus, - .amdgcn => amdgpu.all_cpus, - .riscv32, .riscv64 => riscv.all_cpus, - .sparc, .sparcv9, .sparcel => sparc.all_cpus, - .s390x => systemz.all_cpus, - .i386, .x86_64 => x86.all_cpus, - .nvptx, .nvptx64 => nvptx.all_cpus, - .wasm32, .wasm64 => wasm.all_cpus, + .arm, .armeb, .thumb, .thumbeb => comptime allCpusFromDecls(arm.cpu), + .aarch64, .aarch64_be, .aarch64_32 => comptime allCpusFromDecls(aarch64.cpu), + .avr => comptime allCpusFromDecls(avr.cpu), + .bpfel, .bpfeb => comptime allCpusFromDecls(bpf.cpu), + .hexagon => comptime allCpusFromDecls(hexagon.cpu), + .mips, .mipsel, .mips64, .mips64el => comptime allCpusFromDecls(mips.cpu), + .msp430 => comptime allCpusFromDecls(msp430.cpu), + .powerpc, .powerpc64, .powerpc64le => comptime allCpusFromDecls(powerpc.cpu), + .amdgcn => comptime allCpusFromDecls(amdgpu.cpu), + .riscv32, .riscv64 => comptime allCpusFromDecls(riscv.cpu), + .sparc, .sparcv9, .sparcel => comptime allCpusFromDecls(sparc.cpu), + .s390x => comptime allCpusFromDecls(systemz.cpu), + .i386, .x86_64 => comptime allCpusFromDecls(x86.cpu), + .nvptx, .nvptx64 => comptime allCpusFromDecls(nvptx.cpu), + .wasm32, .wasm64 => comptime allCpusFromDecls(wasm.cpu), else => &[0]*const Model{}, }; } + + fn allCpusFromDecls(comptime cpus: type) []const *const Cpu.Model { + const decls = std.meta.declarations(cpus); + var array: [decls.len]*const Cpu.Model = undefined; + for (decls) |decl, i| { + array[i] = &@field(cpus, decl.name); + } + return &array; + } }; pub const Model = struct { diff --git a/lib/std/target/aarch64.zig b/lib/std/target/aarch64.zig index 5c49d4acfcf9b0eeae43e1a29be578aad7deb8a8..9af95dfadad0201d3baf3f59c19f894fa184376a 100644 --- a/lib/std/target/aarch64.zig +++ b/lib/std/target/aarch64.zig @@ -1505,48 +1505,3 @@ pub const cpu = struct { }), }; }; - -/// All aarch64 CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.apple_a10, - &cpu.apple_a11, - &cpu.apple_a12, - &cpu.apple_a13, - &cpu.apple_a7, - &cpu.apple_a8, - &cpu.apple_a9, - &cpu.apple_latest, - &cpu.apple_s4, - &cpu.apple_s5, - &cpu.cortex_a35, - &cpu.cortex_a53, - &cpu.cortex_a55, - &cpu.cortex_a57, - &cpu.cortex_a65, - &cpu.cortex_a65ae, - &cpu.cortex_a72, - &cpu.cortex_a73, - &cpu.cortex_a75, - &cpu.cortex_a76, - &cpu.cortex_a76ae, - &cpu.cyclone, - &cpu.exynos_m1, - &cpu.exynos_m2, - &cpu.exynos_m3, - &cpu.exynos_m4, - &cpu.exynos_m5, - &cpu.falkor, - &cpu.generic, - &cpu.kryo, - &cpu.neoverse_e1, - &cpu.neoverse_n1, - &cpu.saphira, - &cpu.thunderx, - &cpu.thunderx2t99, - &cpu.thunderxt81, - &cpu.thunderxt83, - &cpu.thunderxt88, - &cpu.tsv110, -}; diff --git a/lib/std/target/amdgpu.zig b/lib/std/target/amdgpu.zig index 962e3073cf8b13194454c89ee6009cc0b84a3e41..4b3f83bbc317ddfa6f2b0cb84fe1f6cabeb32f05 100644 --- a/lib/std/target/amdgpu.zig +++ b/lib/std/target/amdgpu.zig @@ -1276,48 +1276,3 @@ pub const cpu = struct { }), }; }; - -/// All amdgpu CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.bonaire, - &cpu.carrizo, - &cpu.fiji, - &cpu.generic, - &cpu.generic_hsa, - &cpu.gfx1010, - &cpu.gfx1011, - &cpu.gfx1012, - &cpu.gfx600, - &cpu.gfx601, - &cpu.gfx700, - &cpu.gfx701, - &cpu.gfx702, - &cpu.gfx703, - &cpu.gfx704, - &cpu.gfx801, - &cpu.gfx802, - &cpu.gfx803, - &cpu.gfx810, - &cpu.gfx900, - &cpu.gfx902, - &cpu.gfx904, - &cpu.gfx906, - &cpu.gfx908, - &cpu.gfx909, - &cpu.hainan, - &cpu.hawaii, - &cpu.iceland, - &cpu.kabini, - &cpu.kaveri, - &cpu.mullins, - &cpu.oland, - &cpu.pitcairn, - &cpu.polaris10, - &cpu.polaris11, - &cpu.stoney, - &cpu.tahiti, - &cpu.tonga, - &cpu.verde, -}; diff --git a/lib/std/target/arm.zig b/lib/std/target/arm.zig index aab8e9d068a9f249e80e71a26c618e35166e249f..90b060c03ff4dc2e7794028cb0bc0a78f9228e85 100644 --- a/lib/std/target/arm.zig +++ b/lib/std/target/arm.zig @@ -2145,92 +2145,3 @@ pub const cpu = struct { }), }; }; - -/// All arm CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.arm1020e, - &cpu.arm1020t, - &cpu.arm1022e, - &cpu.arm10e, - &cpu.arm10tdmi, - &cpu.arm1136j_s, - &cpu.arm1136jf_s, - &cpu.arm1156t2_s, - &cpu.arm1156t2f_s, - &cpu.arm1176j_s, - &cpu.arm1176jz_s, - &cpu.arm1176jzf_s, - &cpu.arm710t, - &cpu.arm720t, - &cpu.arm7tdmi, - &cpu.arm7tdmi_s, - &cpu.arm8, - &cpu.arm810, - &cpu.arm9, - &cpu.arm920, - &cpu.arm920t, - &cpu.arm922t, - &cpu.arm926ej_s, - &cpu.arm940t, - &cpu.arm946e_s, - &cpu.arm966e_s, - &cpu.arm968e_s, - &cpu.arm9e, - &cpu.arm9tdmi, - &cpu.cortex_a12, - &cpu.cortex_a15, - &cpu.cortex_a17, - &cpu.cortex_a32, - &cpu.cortex_a35, - &cpu.cortex_a5, - &cpu.cortex_a53, - &cpu.cortex_a55, - &cpu.cortex_a57, - &cpu.cortex_a7, - &cpu.cortex_a72, - &cpu.cortex_a73, - &cpu.cortex_a75, - &cpu.cortex_a76, - &cpu.cortex_a76ae, - &cpu.cortex_a8, - &cpu.cortex_a9, - &cpu.cortex_m0, - &cpu.cortex_m0plus, - &cpu.cortex_m1, - &cpu.cortex_m23, - &cpu.cortex_m3, - &cpu.cortex_m33, - &cpu.cortex_m35p, - &cpu.cortex_m4, - &cpu.cortex_m7, - &cpu.cortex_r4, - &cpu.cortex_r4f, - &cpu.cortex_r5, - &cpu.cortex_r52, - &cpu.cortex_r7, - &cpu.cortex_r8, - &cpu.cyclone, - &cpu.ep9312, - &cpu.exynos_m1, - &cpu.exynos_m2, - &cpu.exynos_m3, - &cpu.exynos_m4, - &cpu.exynos_m5, - &cpu.generic, - &cpu.iwmmxt, - &cpu.krait, - &cpu.kryo, - &cpu.mpcore, - &cpu.mpcorenovfp, - &cpu.neoverse_n1, - &cpu.sc000, - &cpu.sc300, - &cpu.strongarm, - &cpu.strongarm110, - &cpu.strongarm1100, - &cpu.strongarm1110, - &cpu.swift, - &cpu.xscale, -}; diff --git a/lib/std/target/avr.zig b/lib/std/target/avr.zig index 4d0da9b2c38292fcfa31278445c88062ade289ec..af4f5ba5be9792ecb4af02114590b5ba295686a5 100644 --- a/lib/std/target/avr.zig +++ b/lib/std/target/avr.zig @@ -2116,266 +2116,3 @@ pub const cpu = struct { }), }; }; - -/// All avr CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.at43usb320, - &cpu.at43usb355, - &cpu.at76c711, - &cpu.at86rf401, - &cpu.at90c8534, - &cpu.at90can128, - &cpu.at90can32, - &cpu.at90can64, - &cpu.at90pwm1, - &cpu.at90pwm161, - &cpu.at90pwm2, - &cpu.at90pwm216, - &cpu.at90pwm2b, - &cpu.at90pwm3, - &cpu.at90pwm316, - &cpu.at90pwm3b, - &cpu.at90pwm81, - &cpu.at90s1200, - &cpu.at90s2313, - &cpu.at90s2323, - &cpu.at90s2333, - &cpu.at90s2343, - &cpu.at90s4414, - &cpu.at90s4433, - &cpu.at90s4434, - &cpu.at90s8515, - &cpu.at90s8535, - &cpu.at90scr100, - &cpu.at90usb1286, - &cpu.at90usb1287, - &cpu.at90usb162, - &cpu.at90usb646, - &cpu.at90usb647, - &cpu.at90usb82, - &cpu.at94k, - &cpu.ata5272, - &cpu.ata5505, - &cpu.ata5790, - &cpu.ata5795, - &cpu.ata6285, - &cpu.ata6286, - &cpu.ata6289, - &cpu.atmega103, - &cpu.atmega128, - &cpu.atmega1280, - &cpu.atmega1281, - &cpu.atmega1284, - &cpu.atmega1284p, - &cpu.atmega1284rfr2, - &cpu.atmega128a, - &cpu.atmega128rfa1, - &cpu.atmega128rfr2, - &cpu.atmega16, - &cpu.atmega161, - &cpu.atmega162, - &cpu.atmega163, - &cpu.atmega164a, - &cpu.atmega164p, - &cpu.atmega164pa, - &cpu.atmega165, - &cpu.atmega165a, - &cpu.atmega165p, - &cpu.atmega165pa, - &cpu.atmega168, - &cpu.atmega168a, - &cpu.atmega168p, - &cpu.atmega168pa, - &cpu.atmega169, - &cpu.atmega169a, - &cpu.atmega169p, - &cpu.atmega169pa, - &cpu.atmega16a, - &cpu.atmega16hva, - &cpu.atmega16hva2, - &cpu.atmega16hvb, - &cpu.atmega16hvbrevb, - &cpu.atmega16m1, - &cpu.atmega16u2, - &cpu.atmega16u4, - &cpu.atmega2560, - &cpu.atmega2561, - &cpu.atmega2564rfr2, - &cpu.atmega256rfr2, - &cpu.atmega32, - &cpu.atmega323, - &cpu.atmega324a, - &cpu.atmega324p, - &cpu.atmega324pa, - &cpu.atmega325, - &cpu.atmega3250, - &cpu.atmega3250a, - &cpu.atmega3250p, - &cpu.atmega3250pa, - &cpu.atmega325a, - &cpu.atmega325p, - &cpu.atmega325pa, - &cpu.atmega328, - &cpu.atmega328p, - &cpu.atmega329, - &cpu.atmega3290, - &cpu.atmega3290a, - &cpu.atmega3290p, - &cpu.atmega3290pa, - &cpu.atmega329a, - &cpu.atmega329p, - &cpu.atmega329pa, - &cpu.atmega32a, - &cpu.atmega32c1, - &cpu.atmega32hvb, - &cpu.atmega32hvbrevb, - &cpu.atmega32m1, - &cpu.atmega32u2, - &cpu.atmega32u4, - &cpu.atmega32u6, - &cpu.atmega406, - &cpu.atmega48, - &cpu.atmega48a, - &cpu.atmega48p, - &cpu.atmega48pa, - &cpu.atmega64, - &cpu.atmega640, - &cpu.atmega644, - &cpu.atmega644a, - &cpu.atmega644p, - &cpu.atmega644pa, - &cpu.atmega644rfr2, - &cpu.atmega645, - &cpu.atmega6450, - &cpu.atmega6450a, - &cpu.atmega6450p, - &cpu.atmega645a, - &cpu.atmega645p, - &cpu.atmega649, - &cpu.atmega6490, - &cpu.atmega6490a, - &cpu.atmega6490p, - &cpu.atmega649a, - &cpu.atmega649p, - &cpu.atmega64a, - &cpu.atmega64c1, - &cpu.atmega64hve, - &cpu.atmega64m1, - &cpu.atmega64rfr2, - &cpu.atmega8, - &cpu.atmega8515, - &cpu.atmega8535, - &cpu.atmega88, - &cpu.atmega88a, - &cpu.atmega88p, - &cpu.atmega88pa, - &cpu.atmega8a, - &cpu.atmega8hva, - &cpu.atmega8u2, - &cpu.attiny10, - &cpu.attiny102, - &cpu.attiny104, - &cpu.attiny11, - &cpu.attiny12, - &cpu.attiny13, - &cpu.attiny13a, - &cpu.attiny15, - &cpu.attiny1634, - &cpu.attiny167, - &cpu.attiny20, - &cpu.attiny22, - &cpu.attiny2313, - &cpu.attiny2313a, - &cpu.attiny24, - &cpu.attiny24a, - &cpu.attiny25, - &cpu.attiny26, - &cpu.attiny261, - &cpu.attiny261a, - &cpu.attiny28, - &cpu.attiny4, - &cpu.attiny40, - &cpu.attiny4313, - &cpu.attiny43u, - &cpu.attiny44, - &cpu.attiny44a, - &cpu.attiny45, - &cpu.attiny461, - &cpu.attiny461a, - &cpu.attiny48, - &cpu.attiny5, - &cpu.attiny828, - &cpu.attiny84, - &cpu.attiny84a, - &cpu.attiny85, - &cpu.attiny861, - &cpu.attiny861a, - &cpu.attiny87, - &cpu.attiny88, - &cpu.attiny9, - &cpu.atxmega128a1, - &cpu.atxmega128a1u, - &cpu.atxmega128a3, - &cpu.atxmega128a3u, - &cpu.atxmega128a4u, - &cpu.atxmega128b1, - &cpu.atxmega128b3, - &cpu.atxmega128c3, - &cpu.atxmega128d3, - &cpu.atxmega128d4, - &cpu.atxmega16a4, - &cpu.atxmega16a4u, - &cpu.atxmega16c4, - &cpu.atxmega16d4, - &cpu.atxmega16e5, - &cpu.atxmega192a3, - &cpu.atxmega192a3u, - &cpu.atxmega192c3, - &cpu.atxmega192d3, - &cpu.atxmega256a3, - &cpu.atxmega256a3b, - &cpu.atxmega256a3bu, - &cpu.atxmega256a3u, - &cpu.atxmega256c3, - &cpu.atxmega256d3, - &cpu.atxmega32a4, - &cpu.atxmega32a4u, - &cpu.atxmega32c4, - &cpu.atxmega32d4, - &cpu.atxmega32e5, - &cpu.atxmega32x1, - &cpu.atxmega384c3, - &cpu.atxmega384d3, - &cpu.atxmega64a1, - &cpu.atxmega64a1u, - &cpu.atxmega64a3, - &cpu.atxmega64a3u, - &cpu.atxmega64a4u, - &cpu.atxmega64b1, - &cpu.atxmega64b3, - &cpu.atxmega64c3, - &cpu.atxmega64d3, - &cpu.atxmega64d4, - &cpu.atxmega8e5, - &cpu.avr1, - &cpu.avr2, - &cpu.avr25, - &cpu.avr3, - &cpu.avr31, - &cpu.avr35, - &cpu.avr4, - &cpu.avr5, - &cpu.avr51, - &cpu.avr6, - &cpu.avrtiny, - &cpu.avrxmega1, - &cpu.avrxmega2, - &cpu.avrxmega3, - &cpu.avrxmega4, - &cpu.avrxmega5, - &cpu.avrxmega6, - &cpu.avrxmega7, - &cpu.m3000, -}; diff --git a/lib/std/target/bpf.zig b/lib/std/target/bpf.zig index 6b548ac031d5dbb6431cbeec2922d145cf9ddbe3..ddb12d3d8515a6916a5ca67f9933cc0a5afdfb39 100644 --- a/lib/std/target/bpf.zig +++ b/lib/std/target/bpf.zig @@ -64,14 +64,3 @@ pub const cpu = struct { .features = featureSet(&[_]Feature{}), }; }; - -/// All bpf CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.generic, - &cpu.probe, - &cpu.v1, - &cpu.v2, - &cpu.v3, -}; diff --git a/lib/std/target/hexagon.zig b/lib/std/target/hexagon.zig index b0558908e35a6670ce05f7927af5de0dc9d03bac..f429099d882f3da38b4fdda834c78c5ae71fd0b6 100644 --- a/lib/std/target/hexagon.zig +++ b/lib/std/target/hexagon.zig @@ -298,16 +298,3 @@ pub const cpu = struct { }), }; }; - -/// All hexagon CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.generic, - &cpu.hexagonv5, - &cpu.hexagonv55, - &cpu.hexagonv60, - &cpu.hexagonv62, - &cpu.hexagonv65, - &cpu.hexagonv66, -}; diff --git a/lib/std/target/mips.zig b/lib/std/target/mips.zig index 21211ae20eb5430a5203cffa3de44614d043007d..fc95b2dee8e7811c72bbc7437c5a8967a8cea4b5 100644 --- a/lib/std/target/mips.zig +++ b/lib/std/target/mips.zig @@ -524,28 +524,3 @@ pub const cpu = struct { }), }; }; - -/// All mips CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.generic, - &cpu.mips1, - &cpu.mips2, - &cpu.mips3, - &cpu.mips32, - &cpu.mips32r2, - &cpu.mips32r3, - &cpu.mips32r5, - &cpu.mips32r6, - &cpu.mips4, - &cpu.mips5, - &cpu.mips64, - &cpu.mips64r2, - &cpu.mips64r3, - &cpu.mips64r5, - &cpu.mips64r6, - &cpu.octeon, - &cpu.@"octeon+", - &cpu.p5600, -}; diff --git a/lib/std/target/msp430.zig b/lib/std/target/msp430.zig index e1b858341f5085e7cb47f1d6a7999fdbfab9826c..947137b3e28cd1e7e3a399b68f82f3a83a4b3843 100644 --- a/lib/std/target/msp430.zig +++ b/lib/std/target/msp430.zig @@ -62,12 +62,3 @@ pub const cpu = struct { }), }; }; - -/// All msp430 CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.generic, - &cpu.msp430, - &cpu.msp430x, -}; diff --git a/lib/std/target/nvptx.zig b/lib/std/target/nvptx.zig index 6a79aea1dae37c96a0b7a45aa3cff85064300b33..d719a6bb7196decc107967f0826fde42286ae1ed 100644 --- a/lib/std/target/nvptx.zig +++ b/lib/std/target/nvptx.zig @@ -287,24 +287,3 @@ pub const cpu = struct { }), }; }; - -/// All nvptx CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.sm_20, - &cpu.sm_21, - &cpu.sm_30, - &cpu.sm_32, - &cpu.sm_35, - &cpu.sm_37, - &cpu.sm_50, - &cpu.sm_52, - &cpu.sm_53, - &cpu.sm_60, - &cpu.sm_61, - &cpu.sm_62, - &cpu.sm_70, - &cpu.sm_72, - &cpu.sm_75, -}; diff --git a/lib/std/target/powerpc.zig b/lib/std/target/powerpc.zig index c06b82f02a30f6f0b1a4fbfbda0f18e8d6791ec1..ffea7344fcbf24721024742411e07b00e17966c9 100644 --- a/lib/std/target/powerpc.zig +++ b/lib/std/target/powerpc.zig @@ -944,47 +944,3 @@ pub const cpu = struct { }), }; }; - -/// All powerpc CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.@"440", - &cpu.@"450", - &cpu.@"601", - &cpu.@"602", - &cpu.@"603", - &cpu.@"603e", - &cpu.@"603ev", - &cpu.@"604", - &cpu.@"604e", - &cpu.@"620", - &cpu.@"7400", - &cpu.@"7450", - &cpu.@"750", - &cpu.@"970", - &cpu.a2, - &cpu.a2q, - &cpu.e500, - &cpu.e500mc, - &cpu.e5500, - &cpu.future, - &cpu.g3, - &cpu.g4, - &cpu.@"g4+", - &cpu.g5, - &cpu.generic, - &cpu.ppc, - &cpu.ppc32, - &cpu.ppc64, - &cpu.ppc64le, - &cpu.pwr3, - &cpu.pwr4, - &cpu.pwr5, - &cpu.pwr5x, - &cpu.pwr6, - &cpu.pwr6x, - &cpu.pwr7, - &cpu.pwr8, - &cpu.pwr9, -}; diff --git a/lib/std/target/riscv.zig b/lib/std/target/riscv.zig index ff8921eaf2ff33e2e65af607878edc7b64232581..dbdb1070245140f847f48eb3d86102653d4166e6 100644 --- a/lib/std/target/riscv.zig +++ b/lib/std/target/riscv.zig @@ -303,13 +303,3 @@ pub const cpu = struct { }), }; }; - -/// All riscv CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.baseline_rv32, - &cpu.baseline_rv64, - &cpu.generic_rv32, - &cpu.generic_rv64, -}; diff --git a/lib/std/target/sparc.zig b/lib/std/target/sparc.zig index 3ec6cc7c2041bc8deb9834b06c939920557f99e7..e1cbc845fca2dacdcdaeb3800e4dd7af68901696 100644 --- a/lib/std/target/sparc.zig +++ b/lib/std/target/sparc.zig @@ -448,49 +448,3 @@ pub const cpu = struct { }), }; }; - -/// All sparc CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.at697e, - &cpu.at697f, - &cpu.f934, - &cpu.generic, - &cpu.gr712rc, - &cpu.gr740, - &cpu.hypersparc, - &cpu.leon2, - &cpu.leon3, - &cpu.leon4, - &cpu.ma2080, - &cpu.ma2085, - &cpu.ma2100, - &cpu.ma2150, - &cpu.ma2155, - &cpu.ma2450, - &cpu.ma2455, - &cpu.ma2480, - &cpu.ma2485, - &cpu.ma2x5x, - &cpu.ma2x8x, - &cpu.myriad2, - &cpu.myriad2_1, - &cpu.myriad2_2, - &cpu.myriad2_3, - &cpu.niagara, - &cpu.niagara2, - &cpu.niagara3, - &cpu.niagara4, - &cpu.sparclet, - &cpu.sparclite, - &cpu.sparclite86x, - &cpu.supersparc, - &cpu.tsc701, - &cpu.ultrasparc, - &cpu.ultrasparc3, - &cpu.ut699, - &cpu.v7, - &cpu.v8, - &cpu.v9, -}; diff --git a/lib/std/target/systemz.zig b/lib/std/target/systemz.zig index 453ce8887f08b75aaeb05570e31560ff64fa12dd..f065a8b1694753a203633b7937d3fa2d781be5af 100644 --- a/lib/std/target/systemz.zig +++ b/lib/std/target/systemz.zig @@ -532,22 +532,3 @@ pub const cpu = struct { }), }; }; - -/// All systemz CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.arch10, - &cpu.arch11, - &cpu.arch12, - &cpu.arch13, - &cpu.arch8, - &cpu.arch9, - &cpu.generic, - &cpu.z10, - &cpu.z13, - &cpu.z14, - &cpu.z15, - &cpu.z196, - &cpu.zEC12, -}; diff --git a/lib/std/target/wasm.zig b/lib/std/target/wasm.zig index 066282f3c6cc6385ab5cf82b21999560cca3b2b0..72b2b6d4312a76ecb96d727d1eab812ab2954612 100644 --- a/lib/std/target/wasm.zig +++ b/lib/std/target/wasm.zig @@ -104,12 +104,3 @@ pub const cpu = struct { .features = featureSet(&[_]Feature{}), }; }; - -/// All wasm CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.bleeding_edge, - &cpu.generic, - &cpu.mvp, -}; diff --git a/lib/std/target/x86.zig b/lib/std/target/x86.zig index 5eccd61c7e46e3ddbbe2dcbcfb53993912bc5711..bfcd1abc1a33d59499bee93c3d83d901b3766ba1 100644 --- a/lib/std/target/x86.zig +++ b/lib/std/target/x86.zig @@ -2943,88 +2943,3 @@ pub const cpu = struct { }), }; }; - -/// All x86 CPUs, sorted alphabetically by name. -/// TODO: Replace this with usage of `std.meta.declList`. It does work, but stage1 -/// compiler has inefficient memory and CPU usage, affecting build times. -pub const all_cpus = &[_]*const CpuModel{ - &cpu.amdfam10, - &cpu.athlon, - &cpu.athlon_4, - &cpu.athlon_fx, - &cpu.athlon_mp, - &cpu.athlon_tbird, - &cpu.athlon_xp, - &cpu.athlon64, - &cpu.athlon64_sse3, - &cpu.atom, - &cpu.barcelona, - &cpu.bdver1, - &cpu.bdver2, - &cpu.bdver3, - &cpu.bdver4, - &cpu.bonnell, - &cpu.broadwell, - &cpu.btver1, - &cpu.btver2, - &cpu.c3, - &cpu.c3_2, - &cpu.cannonlake, - &cpu.cascadelake, - &cpu.cooperlake, - &cpu.core_avx_i, - &cpu.core_avx2, - &cpu.core2, - &cpu.corei7, - &cpu.corei7_avx, - &cpu.generic, - &cpu.geode, - &cpu.goldmont, - &cpu.goldmont_plus, - &cpu.haswell, - &cpu._i386, - &cpu._i486, - &cpu._i586, - &cpu._i686, - &cpu.icelake_client, - &cpu.icelake_server, - &cpu.ivybridge, - &cpu.k6, - &cpu.k6_2, - &cpu.k6_3, - &cpu.k8, - &cpu.k8_sse3, - &cpu.knl, - &cpu.knm, - &cpu.lakemont, - &cpu.nehalem, - &cpu.nocona, - &cpu.opteron, - &cpu.opteron_sse3, - &cpu.penryn, - &cpu.pentium, - &cpu.pentium_m, - &cpu.pentium_mmx, - &cpu.pentium2, - &cpu.pentium3, - &cpu.pentium3m, - &cpu.pentium4, - &cpu.pentium4m, - &cpu.pentiumpro, - &cpu.prescott, - &cpu.sandybridge, - &cpu.silvermont, - &cpu.skx, - &cpu.skylake, - &cpu.skylake_avx512, - &cpu.slm, - &cpu.tigerlake, - &cpu.tremont, - &cpu.westmere, - &cpu.winchip_c6, - &cpu.winchip2, - &cpu.x86_64, - &cpu.yonah, - &cpu.znver1, - &cpu.znver2, -}; -- 2.54.0 From e85fe13e44b1e2957b9d90e19c171fdfa8cb5505 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 14:09:04 +0300 Subject: [PATCH 241/295] run zig fmt on std lib and self hosted --- build.zig | 8 +- lib/std/array_list.zig | 2 +- lib/std/array_list_sentineled.zig | 4 +- lib/std/atomic/queue.zig | 4 +- lib/std/build.zig | 4 +- lib/std/build/emit_raw.zig | 2 +- lib/std/builtin.zig | 10 +- lib/std/c.zig | 2 +- lib/std/c/ast.zig | 14 +- lib/std/cache_hash.zig | 2 +- lib/std/comptime_string_map.zig | 6 +- lib/std/crypto/benchmark.zig | 12 +- lib/std/crypto/test.zig | 2 +- lib/std/debug.zig | 24 ++-- lib/std/debug/leb128.zig | 14 +- lib/std/dwarf.zig | 18 +-- lib/std/elf.zig | 4 +- lib/std/event/group.zig | 2 +- lib/std/fmt.zig | 70 +++++----- lib/std/fs/wasi.zig | 2 +- lib/std/hash/auto_hash.zig | 16 +-- lib/std/hash/benchmark.zig | 4 +- lib/std/hash/cityhash.zig | 2 +- lib/std/hash/murmur.zig | 2 +- lib/std/heap.zig | 31 +++-- lib/std/heap/logging_allocator.zig | 4 +- lib/std/http/headers.zig | 2 +- lib/std/io/bit_reader.zig | 2 +- lib/std/io/bit_writer.zig | 4 +- lib/std/io/buffered_reader.zig | 2 +- lib/std/io/buffered_writer.zig | 2 +- lib/std/io/counting_writer.zig | 2 +- lib/std/io/fixed_buffer_stream.zig | 2 +- lib/std/io/multi_writer.zig | 2 +- lib/std/io/peek_stream.zig | 2 +- lib/std/io/serialization.zig | 14 +- lib/std/io/writer.zig | 2 +- lib/std/json.zig | 16 +-- lib/std/json/write_stream.zig | 6 +- lib/std/log.zig | 18 +-- lib/std/math.zig | 36 ++--- lib/std/math/acos.zig | 2 +- lib/std/math/acosh.zig | 2 +- lib/std/math/asin.zig | 2 +- lib/std/math/asinh.zig | 2 +- lib/std/math/atan.zig | 2 +- lib/std/math/atanh.zig | 2 +- lib/std/math/big/int.zig | 20 +-- lib/std/math/big/rational.zig | 4 +- lib/std/math/cbrt.zig | 2 +- lib/std/math/ceil.zig | 2 +- lib/std/math/complex/abs.zig | 2 +- lib/std/math/complex/acos.zig | 2 +- lib/std/math/complex/acosh.zig | 2 +- lib/std/math/complex/arg.zig | 2 +- lib/std/math/complex/asin.zig | 2 +- lib/std/math/complex/asinh.zig | 2 +- lib/std/math/complex/atan.zig | 2 +- lib/std/math/complex/atanh.zig | 2 +- lib/std/math/complex/conj.zig | 2 +- lib/std/math/complex/cos.zig | 2 +- lib/std/math/complex/cosh.zig | 2 +- lib/std/math/complex/exp.zig | 2 +- lib/std/math/complex/ldexp.zig | 2 +- lib/std/math/complex/log.zig | 2 +- lib/std/math/complex/proj.zig | 2 +- lib/std/math/complex/sin.zig | 2 +- lib/std/math/complex/sinh.zig | 2 +- lib/std/math/complex/sqrt.zig | 2 +- lib/std/math/complex/tan.zig | 2 +- lib/std/math/complex/tanh.zig | 2 +- lib/std/math/cos.zig | 2 +- lib/std/math/cosh.zig | 2 +- lib/std/math/exp.zig | 2 +- lib/std/math/exp2.zig | 2 +- lib/std/math/expm1.zig | 2 +- lib/std/math/expo2.zig | 2 +- lib/std/math/fabs.zig | 2 +- lib/std/math/floor.zig | 2 +- lib/std/math/frexp.zig | 2 +- lib/std/math/ilogb.zig | 2 +- lib/std/math/isfinite.zig | 2 +- lib/std/math/isinf.zig | 6 +- lib/std/math/isnan.zig | 4 +- lib/std/math/isnormal.zig | 2 +- lib/std/math/ln.zig | 2 +- lib/std/math/log10.zig | 2 +- lib/std/math/log1p.zig | 2 +- lib/std/math/log2.zig | 2 +- lib/std/math/modf.zig | 2 +- lib/std/math/round.zig | 2 +- lib/std/math/scalbn.zig | 2 +- lib/std/math/signbit.zig | 2 +- lib/std/math/sin.zig | 2 +- lib/std/math/sinh.zig | 2 +- lib/std/math/sqrt.zig | 2 +- lib/std/math/tan.zig | 2 +- lib/std/math/tanh.zig | 2 +- lib/std/math/trunc.zig | 2 +- lib/std/mem.zig | 179 +++++++++++++------------ lib/std/meta.zig | 10 +- lib/std/meta/trait.zig | 8 +- lib/std/net.zig | 6 +- lib/std/os.zig | 2 +- lib/std/os/uefi.zig | 2 +- lib/std/progress.zig | 4 +- lib/std/segmented_list.zig | 4 +- lib/std/sort.zig | 38 +++--- lib/std/special/build_runner.zig | 4 +- lib/std/special/test_runner.zig | 4 +- lib/std/target.zig | 19 +-- lib/std/testing.zig | 4 +- lib/std/thread.zig | 2 +- lib/std/zig/ast.zig | 16 +-- lib/std/zig/parse.zig | 2 +- lib/std/zig/string_literal.zig | 2 +- lib/std/zig/system.zig | 8 +- src-self-hosted/Module.zig | 10 +- src-self-hosted/codegen.zig | 21 +-- src-self-hosted/dep_tokenizer.zig | 26 ++-- src-self-hosted/ir.zig | 2 +- src-self-hosted/libc_installation.zig | 4 +- src-self-hosted/link.zig | 8 +- src-self-hosted/liveness.zig | 2 +- src-self-hosted/main.zig | 2 +- src-self-hosted/print_targets.zig | 2 +- src-self-hosted/translate_c.zig | 18 +-- src-self-hosted/type.zig | 3 +- src-self-hosted/value.zig | 2 +- src-self-hosted/zir.zig | 13 +- test/stage1/behavior/async_fn.zig | 10 +- test/stage1/behavior/bitcast.zig | 4 +- test/stage1/behavior/bugs/2114.zig | 2 +- test/stage1/behavior/bugs/3742.zig | 2 +- test/stage1/behavior/bugs/4328.zig | 8 +- test/stage1/behavior/bugs/4769_a.zig | 2 +- test/stage1/behavior/bugs/4769_b.zig | 2 +- test/stage1/behavior/byval_arg_var.zig | 4 +- test/stage1/behavior/call.zig | 2 +- test/stage1/behavior/enum.zig | 2 +- test/stage1/behavior/error.zig | 2 +- test/stage1/behavior/eval.zig | 9 +- test/stage1/behavior/fn.zig | 6 +- test/stage1/behavior/generics.zig | 8 +- test/stage1/behavior/optional.zig | 16 ++- test/stage1/behavior/struct.zig | 10 +- test/stage1/behavior/tuple.zig | 4 +- test/stage1/behavior/type_info.zig | 2 +- test/stage1/behavior/union.zig | 2 +- test/stage1/behavior/var_args.zig | 16 +-- test/stage1/behavior/vector.zig | 8 +- 151 files changed, 537 insertions(+), 530 deletions(-) diff --git a/build.zig b/build.zig index f9cd1b0dea68a475d70d4a3e3a3d2d48b6ce83c3..e4ed04ea16cf2c1bb1a59b0545c9f1d731fe3ca1 100644 --- a/build.zig +++ b/build.zig @@ -153,7 +153,7 @@ pub fn build(b: *Builder) !void { test_step.dependOn(docs_step); } -fn dependOnLib(b: *Builder, lib_exe_obj: var, dep: LibraryDep) void { +fn dependOnLib(b: *Builder, lib_exe_obj: anytype, dep: LibraryDep) void { for (dep.libdirs.items) |lib_dir| { lib_exe_obj.addLibPath(lib_dir); } @@ -193,7 +193,7 @@ fn fileExists(filename: []const u8) !bool { return true; } -fn addCppLib(b: *Builder, lib_exe_obj: var, cmake_binary_dir: []const u8, lib_name: []const u8) void { +fn addCppLib(b: *Builder, lib_exe_obj: anytype, cmake_binary_dir: []const u8, lib_name: []const u8) void { lib_exe_obj.addObjectFile(fs.path.join(b.allocator, &[_][]const u8{ cmake_binary_dir, "zig_cpp", @@ -275,7 +275,7 @@ fn findLLVM(b: *Builder, llvm_config_exe: []const u8) !LibraryDep { return result; } -fn configureStage2(b: *Builder, exe: var, ctx: Context) !void { +fn configureStage2(b: *Builder, exe: anytype, ctx: Context) !void { exe.addIncludeDir("src"); exe.addIncludeDir(ctx.cmake_binary_dir); addCppLib(b, exe, ctx.cmake_binary_dir, "zig_cpp"); @@ -340,7 +340,7 @@ fn configureStage2(b: *Builder, exe: var, ctx: Context) !void { fn addCxxKnownPath( b: *Builder, ctx: Context, - exe: var, + exe: anytype, objname: []const u8, errtxt: ?[]const u8, ) !void { diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index d667bc4d1753ee0ac16c7e77994e5008af2dee78..4d8cdc200c03fbb401e53e8da8495e484d35aee0 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -53,7 +53,7 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { /// Deprecated: use `items` field directly. /// Return contents as a slice. Only valid while the list /// doesn't change size. - pub fn span(self: var) @TypeOf(self.items) { + pub fn span(self: anytype) @TypeOf(self.items) { return self.items; } diff --git a/lib/std/array_list_sentineled.zig b/lib/std/array_list_sentineled.zig index b83cc4ad62b46c5cf3cfa120221ac4a13667c5ce..828be7462f73c718b98afec82360822ef4842dae 100644 --- a/lib/std/array_list_sentineled.zig +++ b/lib/std/array_list_sentineled.zig @@ -69,7 +69,7 @@ pub fn ArrayListSentineled(comptime T: type, comptime sentinel: T) type { } /// Only works when `T` is `u8`. - pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: var) !Self { + pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: anytype) !Self { const size = std.math.cast(usize, std.fmt.count(format, args)) catch |err| switch (err) { error.Overflow => return error.OutOfMemory, }; @@ -82,7 +82,7 @@ pub fn ArrayListSentineled(comptime T: type, comptime sentinel: T) type { self.list.deinit(); } - pub fn span(self: var) @TypeOf(self.list.items[0..:sentinel]) { + pub fn span(self: anytype) @TypeOf(self.list.items[0..:sentinel]) { return self.list.items[0..self.len() :sentinel]; } diff --git a/lib/std/atomic/queue.zig b/lib/std/atomic/queue.zig index d6d0b707540580392cc788c095099db00883e971..880af37ef4cb791e5b3a9bcb26cc7c99c8c5b56c 100644 --- a/lib/std/atomic/queue.zig +++ b/lib/std/atomic/queue.zig @@ -123,10 +123,10 @@ pub fn Queue(comptime T: type) type { /// Dumps the contents of the queue to `stream`. /// Up to 4 elements from the head are dumped and the tail of the queue is /// dumped as well. - pub fn dumpToStream(self: *Self, stream: var) !void { + pub fn dumpToStream(self: *Self, stream: anytype) !void { const S = struct { fn dumpRecursive( - s: var, + s: anytype, optional_node: ?*Node, indent: usize, comptime depth: comptime_int, diff --git a/lib/std/build.zig b/lib/std/build.zig index 2d5ec4bd9184586d3c71339ac81bd634fec1c621..19de76b00db38aef3f210c84b68739346b33edf0 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -312,7 +312,7 @@ pub const Builder = struct { return write_file_step; } - pub fn addLog(self: *Builder, comptime format: []const u8, args: var) *LogStep { + pub fn addLog(self: *Builder, comptime format: []const u8, args: anytype) *LogStep { const data = self.fmt(format, args); const log_step = self.allocator.create(LogStep) catch unreachable; log_step.* = LogStep.init(self, data); @@ -883,7 +883,7 @@ pub const Builder = struct { return fs.path.resolve(self.allocator, &[_][]const u8{ self.build_root, rel_path }) catch unreachable; } - pub fn fmt(self: *Builder, comptime format: []const u8, args: var) []u8 { + pub fn fmt(self: *Builder, comptime format: []const u8, args: anytype) []u8 { return fmt_lib.allocPrint(self.allocator, format, args) catch unreachable; } diff --git a/lib/std/build/emit_raw.zig b/lib/std/build/emit_raw.zig index 746b0ac91b9417665f93f0c64783244badd9b476..058a4a64ff7dd358e0fcdbc521344df276825f09 100644 --- a/lib/std/build/emit_raw.zig +++ b/lib/std/build/emit_raw.zig @@ -126,7 +126,7 @@ const BinaryElfOutput = struct { return segment.p_offset <= section.elfOffset and (segment.p_offset + segment.p_filesz) >= (section.elfOffset + section.fileSize); } - fn sectionValidForOutput(shdr: var) bool { + fn sectionValidForOutput(shdr: anytype) bool { return shdr.sh_size > 0 and shdr.sh_type != elf.SHT_NOBITS and ((shdr.sh_flags & elf.SHF_ALLOC) == elf.SHF_ALLOC); } diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 0c7e534bed5d20432a0237068e047794945899c8..5eafc4e409c19d6ce620eb6c5b5370ae557959fd 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -198,7 +198,7 @@ pub const TypeInfo = union(enum) { /// The type of the sentinel is the element type of the pointer, which is /// the value of the `child` field in this struct. However there is no way /// to refer to that type here, so we use `var`. - sentinel: var, + sentinel: anytype, /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. @@ -220,7 +220,7 @@ pub const TypeInfo = union(enum) { /// The type of the sentinel is the element type of the array, which is /// the value of the `child` field in this struct. However there is no way /// to refer to that type here, so we use `var`. - sentinel: var, + sentinel: anytype, }; /// This data structure is used by the Zig language code generation and @@ -237,7 +237,7 @@ pub const TypeInfo = union(enum) { name: []const u8, offset: ?comptime_int, field_type: type, - default_value: var, + default_value: anytype, }; /// This data structure is used by the Zig language code generation and @@ -328,7 +328,7 @@ pub const TypeInfo = union(enum) { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. pub const Frame = struct { - function: var, + function: anytype, }; /// This data structure is used by the Zig language code generation and @@ -452,7 +452,7 @@ pub const Version = struct { self: Version, comptime fmt: []const u8, options: std.fmt.FormatOptions, - out_stream: var, + out_stream: anytype, ) !void { if (fmt.len == 0) { if (self.patch == 0) { diff --git a/lib/std/c.zig b/lib/std/c.zig index 28d4157d6a2f9de57a7488b3911265ecd18d6806..e483b5b50f153e56b6f55edb31ddaf41e4197beb 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -27,7 +27,7 @@ pub usingnamespace switch (std.Target.current.os.tag) { else => struct {}, }; -pub fn getErrno(rc: var) u16 { +pub fn getErrno(rc: anytype) u16 { if (rc == -1) { return @intCast(u16, _errno().*); } else { diff --git a/lib/std/c/ast.zig b/lib/std/c/ast.zig index bb8c01f138dbf62663adde2fc3bd2eb694dfb0d8..467050d57d08ac953d9bad026aaeb5fb781280e2 100644 --- a/lib/std/c/ast.zig +++ b/lib/std/c/ast.zig @@ -64,7 +64,7 @@ pub const Error = union(enum) { NothingDeclared: SimpleError("declaration doesn't declare anything"), QualifierIgnored: SingleTokenError("qualifier '{}' ignored"), - pub fn render(self: *const Error, tree: *Tree, stream: var) !void { + pub fn render(self: *const Error, tree: *Tree, stream: anytype) !void { switch (self.*) { .InvalidToken => |*x| return x.render(tree, stream), .ExpectedToken => |*x| return x.render(tree, stream), @@ -114,7 +114,7 @@ pub const Error = union(enum) { token: TokenIndex, expected_id: @TagType(Token.Id), - pub fn render(self: *const ExpectedToken, tree: *Tree, stream: var) !void { + pub fn render(self: *const ExpectedToken, tree: *Tree, stream: anytype) !void { const found_token = tree.tokens.at(self.token); if (found_token.id == .Invalid) { return stream.print("expected '{}', found invalid bytes", .{self.expected_id.symbol()}); @@ -129,7 +129,7 @@ pub const Error = union(enum) { token: TokenIndex, type_spec: *Node.TypeSpec, - pub fn render(self: *const ExpectedToken, tree: *Tree, stream: var) !void { + pub fn render(self: *const ExpectedToken, tree: *Tree, stream: anytype) !void { try stream.write("invalid type specifier '"); try type_spec.spec.print(tree, stream); const token_name = tree.tokens.at(self.token).id.symbol(); @@ -141,7 +141,7 @@ pub const Error = union(enum) { kw: TokenIndex, name: TokenIndex, - pub fn render(self: *const ExpectedToken, tree: *Tree, stream: var) !void { + pub fn render(self: *const ExpectedToken, tree: *Tree, stream: anytype) !void { return stream.print("must use '{}' tag to refer to type '{}'", .{ tree.slice(kw), tree.slice(name) }); } }; @@ -150,7 +150,7 @@ pub const Error = union(enum) { return struct { token: TokenIndex, - pub fn render(self: *const @This(), tree: *Tree, stream: var) !void { + pub fn render(self: *const @This(), tree: *Tree, stream: anytype) !void { const actual_token = tree.tokens.at(self.token); return stream.print(msg, .{actual_token.id.symbol()}); } @@ -163,7 +163,7 @@ pub const Error = union(enum) { token: TokenIndex, - pub fn render(self: *const ThisError, tokens: *Tree.TokenList, stream: var) !void { + pub fn render(self: *const ThisError, tokens: *Tree.TokenList, stream: anytype) !void { return stream.write(msg); } }; @@ -317,7 +317,7 @@ pub const Node = struct { sym_type: *Type, }, - pub fn print(self: *@This(), self: *const @This(), tree: *Tree, stream: var) !void { + pub fn print(self: *@This(), self: *const @This(), tree: *Tree, stream: anytype) !void { switch (self.spec) { .None => unreachable, .Void => |index| try stream.write(tree.slice(index)), diff --git a/lib/std/cache_hash.zig b/lib/std/cache_hash.zig index 257f407826a9efbf96d2a2001316436c50597a9e..acaa5edc8d77ea07e2f22bf50380d3f64d044fc2 100644 --- a/lib/std/cache_hash.zig +++ b/lib/std/cache_hash.zig @@ -70,7 +70,7 @@ pub const CacheHash = struct { /// Convert the input value into bytes and record it as a dependency of the /// process being cached - pub fn add(self: *CacheHash, val: var) void { + pub fn add(self: *CacheHash, val: anytype) void { assert(self.manifest_file == null); const valPtr = switch (@typeInfo(@TypeOf(val))) { diff --git a/lib/std/comptime_string_map.zig b/lib/std/comptime_string_map.zig index 3021f6bc1e36044154dc1c3a360d995569f902fb..8cc5cac130a0e86957f86f276be6bddc38d73424 100644 --- a/lib/std/comptime_string_map.zig +++ b/lib/std/comptime_string_map.zig @@ -8,7 +8,7 @@ const mem = std.mem; /// `kvs` expects a list literal containing list literals or an array/slice of structs /// where `.@"0"` is the `[]const u8` key and `.@"1"` is the associated value of type `V`. /// TODO: https://github.com/ziglang/zig/issues/4335 -pub fn ComptimeStringMap(comptime V: type, comptime kvs: var) type { +pub fn ComptimeStringMap(comptime V: type, comptime kvs: anytype) type { const precomputed = comptime blk: { @setEvalBranchQuota(2000); const KV = struct { @@ -126,7 +126,7 @@ test "ComptimeStringMap slice of structs" { testMap(map); } -fn testMap(comptime map: var) void { +fn testMap(comptime map: anytype) void { std.testing.expectEqual(TestEnum.A, map.get("have").?); std.testing.expectEqual(TestEnum.B, map.get("nothing").?); std.testing.expect(null == map.get("missing")); @@ -165,7 +165,7 @@ test "ComptimeStringMap void value type, list literal of list literals" { testSet(map); } -fn testSet(comptime map: var) void { +fn testSet(comptime map: anytype) void { std.testing.expectEqual({}, map.get("have").?); std.testing.expectEqual({}, map.get("nothing").?); std.testing.expect(null == map.get("missing")); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 8c5d75f80a426b7d9d697dc2cfa802ba596b8b24..f0f40bd23125988a4264cf3c732f81ec0b48a7c0 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -29,7 +29,7 @@ const hashes = [_]Crypto{ Crypto{ .ty = crypto.Blake3, .name = "blake3" }, }; -pub fn benchmarkHash(comptime Hash: var, comptime bytes: comptime_int) !u64 { +pub fn benchmarkHash(comptime Hash: anytype, comptime bytes: comptime_int) !u64 { var h = Hash.init(); var block: [Hash.digest_length]u8 = undefined; @@ -56,7 +56,7 @@ const macs = [_]Crypto{ Crypto{ .ty = crypto.HmacSha256, .name = "hmac-sha256" }, }; -pub fn benchmarkMac(comptime Mac: var, comptime bytes: comptime_int) !u64 { +pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 { std.debug.assert(32 >= Mac.mac_length and 32 >= Mac.minimum_key_length); var in: [1 * MiB]u8 = undefined; @@ -81,7 +81,7 @@ pub fn benchmarkMac(comptime Mac: var, comptime bytes: comptime_int) !u64 { const exchanges = [_]Crypto{Crypto{ .ty = crypto.X25519, .name = "x25519" }}; -pub fn benchmarkKeyExchange(comptime DhKeyExchange: var, comptime exchange_count: comptime_int) !u64 { +pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 { std.debug.assert(DhKeyExchange.minimum_key_length >= DhKeyExchange.secret_length); var in: [DhKeyExchange.minimum_key_length]u8 = undefined; @@ -166,21 +166,21 @@ pub fn main() !void { inline for (hashes) |H| { if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) { const throughput = try benchmarkHash(H.ty, mode(32 * MiB)); - try stdout.print("{:>11}: {:5} MiB/s\n", .{H.name, throughput / (1 * MiB)}); + try stdout.print("{:>11}: {:5} MiB/s\n", .{ H.name, throughput / (1 * MiB) }); } } inline for (macs) |M| { if (filter == null or std.mem.indexOf(u8, M.name, filter.?) != null) { const throughput = try benchmarkMac(M.ty, mode(128 * MiB)); - try stdout.print("{:>11}: {:5} MiB/s\n", .{M.name, throughput / (1 * MiB)}); + try stdout.print("{:>11}: {:5} MiB/s\n", .{ M.name, throughput / (1 * MiB) }); } } inline for (exchanges) |E| { if (filter == null or std.mem.indexOf(u8, E.name, filter.?) != null) { const throughput = try benchmarkKeyExchange(E.ty, mode(1000)); - try stdout.print("{:>11}: {:5} exchanges/s\n", .{E.name, throughput}); + try stdout.print("{:>11}: {:5} exchanges/s\n", .{ E.name, throughput }); } } } diff --git a/lib/std/crypto/test.zig b/lib/std/crypto/test.zig index 1ff326cf39784e06023594244754a3e3f99cb236..61260c7e39a55faadc44f5c55a3e060969434454 100644 --- a/lib/std/crypto/test.zig +++ b/lib/std/crypto/test.zig @@ -4,7 +4,7 @@ const mem = std.mem; const fmt = std.fmt; // Hash using the specified hasher `H` asserting `expected == H(input)`. -pub fn assertEqualHash(comptime Hasher: var, comptime expected: []const u8, input: []const u8) void { +pub fn assertEqualHash(comptime Hasher: anytype, comptime expected: []const u8, input: []const u8) void { var h: [expected.len / 2]u8 = undefined; Hasher.hash(input, h[0..]); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index e6d0c17da4b783fc9a56d9b44c04ed2d63e90351..3346598ab7de5ea9c0dff6717e1af0e51531c6e7 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -58,7 +58,7 @@ pub const warn = print; /// Print to stderr, unbuffered, and silently returning on failure. Intended /// for use in "printf debugging." Use `std.log` functions for proper logging. -pub fn print(comptime fmt: []const u8, args: var) void { +pub fn print(comptime fmt: []const u8, args: anytype) void { const held = stderr_mutex.acquire(); defer held.release(); const stderr = io.getStdErr().writer(); @@ -223,7 +223,7 @@ pub fn assert(ok: bool) void { if (!ok) unreachable; // assertion failure } -pub fn panic(comptime format: []const u8, args: var) noreturn { +pub fn panic(comptime format: []const u8, args: anytype) noreturn { @setCold(true); // TODO: remove conditional once wasi / LLVM defines __builtin_return_address const first_trace_addr = if (builtin.os.tag == .wasi) null else @returnAddress(); @@ -241,7 +241,7 @@ var panic_mutex = std.Mutex.init(); /// This is used to catch and handle panics triggered by the panic handler. threadlocal var panic_stage: usize = 0; -pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, comptime format: []const u8, args: var) noreturn { +pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, comptime format: []const u8, args: anytype) noreturn { @setCold(true); if (enable_segfault_handler) { @@ -306,7 +306,7 @@ const RESET = "\x1b[0m"; pub fn writeStackTrace( stack_trace: builtin.StackTrace, - out_stream: var, + out_stream: anytype, allocator: *mem.Allocator, debug_info: *DebugInfo, tty_config: TTY.Config, @@ -384,7 +384,7 @@ pub const StackIterator = struct { }; pub fn writeCurrentStackTrace( - out_stream: var, + out_stream: anytype, debug_info: *DebugInfo, tty_config: TTY.Config, start_addr: ?usize, @@ -399,7 +399,7 @@ pub fn writeCurrentStackTrace( } pub fn writeCurrentStackTraceWindows( - out_stream: var, + out_stream: anytype, debug_info: *DebugInfo, tty_config: TTY.Config, start_addr: ?usize, @@ -435,7 +435,7 @@ pub const TTY = struct { // TODO give this a payload of file handle windows_api, - fn setColor(conf: Config, out_stream: var, color: Color) void { + fn setColor(conf: Config, out_stream: anytype, color: Color) void { nosuspend switch (conf) { .no_color => return, .escape_codes => switch (color) { @@ -555,7 +555,7 @@ fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const Mach } /// TODO resources https://github.com/ziglang/zig/issues/4353 -pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: usize, tty_config: TTY.Config) !void { +pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: TTY.Config) !void { const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => { return printLineInfo( @@ -586,13 +586,13 @@ pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: us } fn printLineInfo( - out_stream: var, + out_stream: anytype, line_info: ?LineInfo, address: usize, symbol_name: []const u8, compile_unit_name: []const u8, tty_config: TTY.Config, - comptime printLineFromFile: var, + comptime printLineFromFile: anytype, ) !void { nosuspend { tty_config.setColor(out_stream, .White); @@ -820,7 +820,7 @@ fn readCoffDebugInfo(allocator: *mem.Allocator, coff_file: File) !ModuleDebugInf } } -fn readSparseBitVector(stream: var, allocator: *mem.Allocator) ![]usize { +fn readSparseBitVector(stream: anytype, allocator: *mem.Allocator) ![]usize { const num_words = try stream.readIntLittle(u32); var word_i: usize = 0; var list = ArrayList(usize).init(allocator); @@ -1004,7 +1004,7 @@ fn readMachODebugInfo(allocator: *mem.Allocator, macho_file: File) !ModuleDebugI }; } -fn printLineFromFileAnyOs(out_stream: var, line_info: LineInfo) !void { +fn printLineFromFileAnyOs(out_stream: anytype, line_info: LineInfo) !void { // Need this to always block even in async I/O mode, because this could potentially // be called from e.g. the event loop code crashing. var f = try fs.cwd().openFile(line_info.file_name, .{ .intended_io_mode = .blocking }); diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig index 16a23da1232f750507628f5d2bc3e985cb79c345..8149554246003451cdd640c48c15ca4cdc3d49a4 100644 --- a/lib/std/debug/leb128.zig +++ b/lib/std/debug/leb128.zig @@ -3,7 +3,7 @@ const testing = std.testing; /// Read a single unsigned LEB128 value from the given reader as type T, /// or error.Overflow if the value cannot fit. -pub fn readULEB128(comptime T: type, reader: var) !T { +pub fn readULEB128(comptime T: type, reader: anytype) !T { const U = if (T.bit_count < 8) u8 else T; const ShiftT = std.math.Log2Int(U); @@ -33,7 +33,7 @@ pub fn readULEB128(comptime T: type, reader: var) !T { } /// Write a single unsigned integer as unsigned LEB128 to the given writer. -pub fn writeULEB128(writer: var, uint_value: var) !void { +pub fn writeULEB128(writer: anytype, uint_value: anytype) !void { const T = @TypeOf(uint_value); const U = if (T.bit_count < 8) u8 else T; var value = @intCast(U, uint_value); @@ -61,7 +61,7 @@ pub fn readULEB128Mem(comptime T: type, ptr: *[]const u8) !T { /// Write a single unsigned LEB128 integer to the given memory as unsigned LEB128, /// returning the number of bytes written. -pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { +pub fn writeULEB128Mem(ptr: []u8, uint_value: anytype) !usize { const T = @TypeOf(uint_value); const max_group = (T.bit_count + 6) / 7; var buf = std.io.fixedBufferStream(ptr); @@ -71,7 +71,7 @@ pub fn writeULEB128Mem(ptr: []u8, uint_value: var) !usize { /// Read a single signed LEB128 value from the given reader as type T, /// or error.Overflow if the value cannot fit. -pub fn readILEB128(comptime T: type, reader: var) !T { +pub fn readILEB128(comptime T: type, reader: anytype) !T { const S = if (T.bit_count < 8) i8 else T; const U = std.meta.Int(false, S.bit_count); const ShiftU = std.math.Log2Int(U); @@ -120,7 +120,7 @@ pub fn readILEB128(comptime T: type, reader: var) !T { } /// Write a single signed integer as signed LEB128 to the given writer. -pub fn writeILEB128(writer: var, int_value: var) !void { +pub fn writeILEB128(writer: anytype, int_value: anytype) !void { const T = @TypeOf(int_value); const S = if (T.bit_count < 8) i8 else T; const U = std.meta.Int(false, S.bit_count); @@ -152,7 +152,7 @@ pub fn readILEB128Mem(comptime T: type, ptr: *[]const u8) !T { /// Write a single signed LEB128 integer to the given memory as unsigned LEB128, /// returning the number of bytes written. -pub fn writeILEB128Mem(ptr: []u8, int_value: var) !usize { +pub fn writeILEB128Mem(ptr: []u8, int_value: anytype) !usize { const T = @TypeOf(int_value); var buf = std.io.fixedBufferStream(ptr); try writeILEB128(buf.writer(), int_value); @@ -295,7 +295,7 @@ test "deserialize unsigned LEB128" { try test_read_uleb128_seq(u64, 4, "\x81\x01\x3f\x80\x7f\x80\x80\x80\x00"); } -fn test_write_leb128(value: var) !void { +fn test_write_leb128(value: anytype) !void { const T = @TypeOf(value); const writeStream = if (T.is_signed) writeILEB128 else writeULEB128; diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index 24792c7ca0468bd2450afc1f87c3b5a6fa753140..14004422477662d4d61251a5540666f21ba908b7 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -236,7 +236,7 @@ const LineNumberProgram = struct { } }; -fn readUnitLength(in_stream: var, endian: builtin.Endian, is_64: *bool) !u64 { +fn readUnitLength(in_stream: anytype, endian: builtin.Endian, is_64: *bool) !u64 { const first_32_bits = try in_stream.readInt(u32, endian); is_64.* = (first_32_bits == 0xffffffff); if (is_64.*) { @@ -249,7 +249,7 @@ fn readUnitLength(in_stream: var, endian: builtin.Endian, is_64: *bool) !u64 { } // TODO the nosuspends here are workarounds -fn readAllocBytes(allocator: *mem.Allocator, in_stream: var, size: usize) ![]u8 { +fn readAllocBytes(allocator: *mem.Allocator, in_stream: anytype, size: usize) ![]u8 { const buf = try allocator.alloc(u8, size); errdefer allocator.free(buf); if ((try nosuspend in_stream.read(buf)) < size) return error.EndOfFile; @@ -257,25 +257,25 @@ fn readAllocBytes(allocator: *mem.Allocator, in_stream: var, size: usize) ![]u8 } // TODO the nosuspends here are workarounds -fn readAddress(in_stream: var, endian: builtin.Endian, is_64: bool) !u64 { +fn readAddress(in_stream: anytype, endian: builtin.Endian, is_64: bool) !u64 { return nosuspend if (is_64) try in_stream.readInt(u64, endian) else @as(u64, try in_stream.readInt(u32, endian)); } -fn parseFormValueBlockLen(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue { +fn parseFormValueBlockLen(allocator: *mem.Allocator, in_stream: anytype, size: usize) !FormValue { const buf = try readAllocBytes(allocator, in_stream, size); return FormValue{ .Block = buf }; } // TODO the nosuspends here are workarounds -fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: var, endian: builtin.Endian, size: usize) !FormValue { +fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: anytype, endian: builtin.Endian, size: usize) !FormValue { const block_len = try nosuspend in_stream.readVarInt(usize, endian, size); return parseFormValueBlockLen(allocator, in_stream, block_len); } -fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: bool, endian: builtin.Endian, comptime size: i32) !FormValue { +fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: anytype, signed: bool, endian: builtin.Endian, comptime size: i32) !FormValue { // TODO: Please forgive me, I've worked around zig not properly spilling some intermediate values here. // `nosuspend` should be removed from all the function calls once it is fixed. return FormValue{ @@ -302,7 +302,7 @@ fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: boo } // TODO the nosuspends here are workarounds -fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, endian: builtin.Endian, size: i32) !FormValue { +fn parseFormValueRef(allocator: *mem.Allocator, in_stream: anytype, endian: builtin.Endian, size: i32) !FormValue { return FormValue{ .Ref = switch (size) { 1 => try nosuspend in_stream.readInt(u8, endian), @@ -316,7 +316,7 @@ fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, endian: builtin. } // TODO the nosuspends here are workarounds -fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, endian: builtin.Endian, is_64: bool) anyerror!FormValue { +fn parseFormValue(allocator: *mem.Allocator, in_stream: anytype, form_id: u64, endian: builtin.Endian, is_64: bool) anyerror!FormValue { return switch (form_id) { FORM_addr => FormValue{ .Address = try readAddress(in_stream, endian, @sizeOf(usize) == 8) }, FORM_block1 => parseFormValueBlock(allocator, in_stream, endian, 1), @@ -670,7 +670,7 @@ pub const DwarfInfo = struct { } } - fn parseDie(di: *DwarfInfo, in_stream: var, abbrev_table: *const AbbrevTable, is_64: bool) !?Die { + fn parseDie(di: *DwarfInfo, in_stream: anytype, abbrev_table: *const AbbrevTable, is_64: bool) !?Die { const abbrev_code = try leb.readULEB128(u64, in_stream); if (abbrev_code == 0) return null; const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo; diff --git a/lib/std/elf.zig b/lib/std/elf.zig index b6609d8b311fe11a5bc7b44c60d8e956547535ae..98508df190f66c02ab9a6d4c562ed33c71b5b8dc 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -517,7 +517,7 @@ pub fn readAllHeaders(allocator: *mem.Allocator, file: File) !AllHeaders { return hdrs; } -pub fn int(is_64: bool, need_bswap: bool, int_32: var, int_64: var) @TypeOf(int_64) { +pub fn int(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { if (is_64) { if (need_bswap) { return @byteSwap(@TypeOf(int_64), int_64); @@ -529,7 +529,7 @@ pub fn int(is_64: bool, need_bswap: bool, int_32: var, int_64: var) @TypeOf(int_ } } -pub fn int32(need_bswap: bool, int_32: var, comptime Int64: var) Int64 { +pub fn int32(need_bswap: bool, int_32: anytype, comptime Int64: anytype) Int64 { if (need_bswap) { return @byteSwap(@TypeOf(int_32), int_32); } else { diff --git a/lib/std/event/group.zig b/lib/std/event/group.zig index 61130b32cbac2d4aff7fd34f2119d93d75258911..0dc6550218267e99724ef08bdb052ccea7b7c357 100644 --- a/lib/std/event/group.zig +++ b/lib/std/event/group.zig @@ -65,7 +65,7 @@ pub fn Group(comptime ReturnType: type) type { /// allocated by the group and freed by `wait`. /// `func` must be async and have return type `ReturnType`. /// Thread-safe. - pub fn call(self: *Self, comptime func: var, args: var) error{OutOfMemory}!void { + pub fn call(self: *Self, comptime func: anytype, args: anytype) error{OutOfMemory}!void { var frame = try self.allocator.create(@TypeOf(@call(.{ .modifier = .async_kw }, func, args))); errdefer self.allocator.destroy(frame); const node = try self.allocator.create(AllocStack.Node); diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 2674ba485a75c0cb4527561db747966a89ab0b8b..7415b1b520f4a4bdc4a11f825b62c8794c24114b 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -76,9 +76,9 @@ fn peekIsAlign(comptime fmt: []const u8) bool { /// /// A user type may be a `struct`, `vector`, `union` or `enum` type. pub fn format( - writer: var, + writer: anytype, comptime fmt: []const u8, - args: var, + args: anytype, ) !void { const ArgSetType = u32; if (@typeInfo(@TypeOf(args)) != .Struct) { @@ -311,10 +311,10 @@ pub fn format( } pub fn formatType( - value: var, + value: anytype, comptime fmt: []const u8, options: FormatOptions, - writer: var, + writer: anytype, max_depth: usize, ) @TypeOf(writer).Error!void { if (comptime std.mem.eql(u8, fmt, "*")) { @@ -490,10 +490,10 @@ pub fn formatType( } fn formatValue( - value: var, + value: anytype, comptime fmt: []const u8, options: FormatOptions, - writer: var, + writer: anytype, ) !void { if (comptime std.mem.eql(u8, fmt, "B")) { return formatBytes(value, options, 1000, writer); @@ -511,10 +511,10 @@ fn formatValue( } pub fn formatIntValue( - value: var, + value: anytype, comptime fmt: []const u8, options: FormatOptions, - writer: var, + writer: anytype, ) !void { comptime var radix = 10; comptime var uppercase = false; @@ -551,10 +551,10 @@ pub fn formatIntValue( } fn formatFloatValue( - value: var, + value: anytype, comptime fmt: []const u8, options: FormatOptions, - writer: var, + writer: anytype, ) !void { if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { return formatFloatScientific(value, options, writer); @@ -569,7 +569,7 @@ pub fn formatText( bytes: []const u8, comptime fmt: []const u8, options: FormatOptions, - writer: var, + writer: anytype, ) !void { if (comptime std.mem.eql(u8, fmt, "s") or (fmt.len == 0)) { return formatBuf(bytes, options, writer); @@ -586,7 +586,7 @@ pub fn formatText( pub fn formatAsciiChar( c: u8, options: FormatOptions, - writer: var, + writer: anytype, ) !void { return writer.writeAll(@as(*const [1]u8, &c)); } @@ -594,7 +594,7 @@ pub fn formatAsciiChar( pub fn formatBuf( buf: []const u8, options: FormatOptions, - writer: var, + writer: anytype, ) !void { const width = options.width orelse buf.len; var padding = if (width > buf.len) (width - buf.len) else 0; @@ -626,9 +626,9 @@ pub fn formatBuf( // It should be the case that every full precision, printed value can be re-parsed back to the // same type unambiguously. pub fn formatFloatScientific( - value: var, + value: anytype, options: FormatOptions, - writer: var, + writer: anytype, ) !void { var x = @floatCast(f64, value); @@ -719,9 +719,9 @@ pub fn formatFloatScientific( // Print a float of the format x.yyyyy where the number of y is specified by the precision argument. // By default floats are printed at full precision (no rounding). pub fn formatFloatDecimal( - value: var, + value: anytype, options: FormatOptions, - writer: var, + writer: anytype, ) !void { var x = @as(f64, value); @@ -860,10 +860,10 @@ pub fn formatFloatDecimal( } pub fn formatBytes( - value: var, + value: anytype, options: FormatOptions, comptime radix: usize, - writer: var, + writer: anytype, ) !void { if (value == 0) { return writer.writeAll("0B"); @@ -901,11 +901,11 @@ pub fn formatBytes( } pub fn formatInt( - value: var, + value: anytype, base: u8, uppercase: bool, options: FormatOptions, - writer: var, + writer: anytype, ) !void { const int_value = if (@TypeOf(value) == comptime_int) blk: { const Int = math.IntFittingRange(value, value); @@ -921,11 +921,11 @@ pub fn formatInt( } fn formatIntSigned( - value: var, + value: anytype, base: u8, uppercase: bool, options: FormatOptions, - writer: var, + writer: anytype, ) !void { const new_options = FormatOptions{ .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, @@ -948,11 +948,11 @@ fn formatIntSigned( } fn formatIntUnsigned( - value: var, + value: anytype, base: u8, uppercase: bool, options: FormatOptions, - writer: var, + writer: anytype, ) !void { assert(base >= 2); var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined; @@ -990,7 +990,7 @@ fn formatIntUnsigned( } } -pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize { +pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) usize { var fbs = std.io.fixedBufferStream(out_buf); formatInt(value, base, uppercase, options, fbs.writer()) catch unreachable; return fbs.pos; @@ -1050,7 +1050,7 @@ fn parseWithSign( .Pos => math.add, .Neg => math.sub, }; - + var x: T = 0; for (buf) |c| { @@ -1132,14 +1132,14 @@ pub const BufPrintError = error{ /// As much as possible was written to the buffer, but it was too small to fit all the printed bytes. NoSpaceLeft, }; -pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: var) BufPrintError![]u8 { +pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: anytype) BufPrintError![]u8 { var fbs = std.io.fixedBufferStream(buf); try format(fbs.writer(), fmt, args); return fbs.getWritten(); } // Count the characters needed for format. Useful for preallocating memory -pub fn count(comptime fmt: []const u8, args: var) u64 { +pub fn count(comptime fmt: []const u8, args: anytype) u64 { var counting_writer = std.io.countingWriter(std.io.null_writer); format(counting_writer.writer(), fmt, args) catch |err| switch (err) {}; return counting_writer.bytes_written; @@ -1147,7 +1147,7 @@ pub fn count(comptime fmt: []const u8, args: var) u64 { pub const AllocPrintError = error{OutOfMemory}; -pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: var) AllocPrintError![]u8 { +pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: anytype) AllocPrintError![]u8 { const size = math.cast(usize, count(fmt, args)) catch |err| switch (err) { // Output too long. Can't possibly allocate enough memory to display it. error.Overflow => return error.OutOfMemory, @@ -1158,7 +1158,7 @@ pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: var }; } -pub fn allocPrint0(allocator: *mem.Allocator, comptime fmt: []const u8, args: var) AllocPrintError![:0]u8 { +pub fn allocPrint0(allocator: *mem.Allocator, comptime fmt: []const u8, args: anytype) AllocPrintError![:0]u8 { const result = try allocPrint(allocator, fmt ++ "\x00", args); return result[0 .. result.len - 1 :0]; } @@ -1184,7 +1184,7 @@ test "bufPrintInt" { std.testing.expectEqualSlices(u8, "-42", bufPrintIntToSlice(buf, @as(i32, -42), 10, false, FormatOptions{ .width = 3 })); } -fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) []u8 { +fn bufPrintIntToSlice(buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) []u8 { return buf[0..formatIntBuf(buf, value, base, uppercase, options)]; } @@ -1452,7 +1452,7 @@ test "custom" { self: SelfType, comptime fmt: []const u8, options: FormatOptions, - writer: var, + writer: anytype, ) !void { if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { return std.fmt.format(writer, "({d:.3},{d:.3})", .{ self.x, self.y }); @@ -1573,7 +1573,7 @@ test "bytes.hex" { try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); } -fn testFmt(expected: []const u8, comptime template: []const u8, args: var) !void { +fn testFmt(expected: []const u8, comptime template: []const u8, args: anytype) !void { var buf: [100]u8 = undefined; const result = try bufPrint(buf[0..], template, args); if (mem.eql(u8, result, expected)) return; @@ -1669,7 +1669,7 @@ test "formatType max_depth" { self: SelfType, comptime fmt: []const u8, options: FormatOptions, - writer: var, + writer: anytype, ) !void { if (fmt.len == 0) { return std.fmt.format(writer, "({d:.3},{d:.3})", .{ self.x, self.y }); diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index db4317064dc1878405e05e2a6a179640f21dab0a..4a897c78f70d758221a9c1e4afbe908228650f22 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -29,7 +29,7 @@ pub const PreopenType = union(PreopenTypeTag) { } } - pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: var) !void { + pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype) !void { try out_stream.print("PreopenType{{ ", .{}); switch (self) { PreopenType.Dir => |path| try out_stream.print(".Dir = '{}'", .{path}), diff --git a/lib/std/hash/auto_hash.zig b/lib/std/hash/auto_hash.zig index a33b23354bbfabab58986b8eb85a49f5215a1332..a3e1a390c2714744b8c0b5c2f2de5dfbfbddee46 100644 --- a/lib/std/hash/auto_hash.zig +++ b/lib/std/hash/auto_hash.zig @@ -21,7 +21,7 @@ pub const HashStrategy = enum { }; /// Helper function to hash a pointer and mutate the strategy if needed. -pub fn hashPointer(hasher: var, key: var, comptime strat: HashStrategy) void { +pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { const info = @typeInfo(@TypeOf(key)); switch (info.Pointer.size) { @@ -53,7 +53,7 @@ pub fn hashPointer(hasher: var, key: var, comptime strat: HashStrategy) void { } /// Helper function to hash a set of contiguous objects, from an array or slice. -pub fn hashArray(hasher: var, key: var, comptime strat: HashStrategy) void { +pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { switch (strat) { .Shallow => { // TODO detect via a trait when Key has no padding bits to @@ -73,7 +73,7 @@ pub fn hashArray(hasher: var, key: var, comptime strat: HashStrategy) void { /// Provides generic hashing for any eligible type. /// Strategy is provided to determine if pointers should be followed or not. -pub fn hash(hasher: var, key: var, comptime strat: HashStrategy) void { +pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { const Key = @TypeOf(key); switch (@typeInfo(Key)) { .NoReturn, @@ -161,7 +161,7 @@ pub fn hash(hasher: var, key: var, comptime strat: HashStrategy) void { /// Provides generic hashing for any eligible type. /// Only hashes `key` itself, pointers are not followed. /// Slices are rejected to avoid ambiguity on the user's intention. -pub fn autoHash(hasher: var, key: var) void { +pub fn autoHash(hasher: anytype, key: anytype) void { const Key = @TypeOf(key); if (comptime meta.trait.isSlice(Key)) { comptime assert(@hasDecl(std, "StringHashMap")); // detect when the following message needs updated @@ -181,28 +181,28 @@ pub fn autoHash(hasher: var, key: var) void { const testing = std.testing; const Wyhash = std.hash.Wyhash; -fn testHash(key: var) u64 { +fn testHash(key: anytype) u64 { // Any hash could be used here, for testing autoHash. var hasher = Wyhash.init(0); hash(&hasher, key, .Shallow); return hasher.final(); } -fn testHashShallow(key: var) u64 { +fn testHashShallow(key: anytype) u64 { // Any hash could be used here, for testing autoHash. var hasher = Wyhash.init(0); hash(&hasher, key, .Shallow); return hasher.final(); } -fn testHashDeep(key: var) u64 { +fn testHashDeep(key: anytype) u64 { // Any hash could be used here, for testing autoHash. var hasher = Wyhash.init(0); hash(&hasher, key, .Deep); return hasher.final(); } -fn testHashDeepRecursive(key: var) u64 { +fn testHashDeepRecursive(key: anytype) u64 { // Any hash could be used here, for testing autoHash. var hasher = Wyhash.init(0); hash(&hasher, key, .DeepRecursive); diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index 0eb3a25fe1bc8c9fcd1265c4a01496da2e1e9f13..5f8a15831c3fefd7cbf704d5da29155e8b971b82 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -88,7 +88,7 @@ const Result = struct { const block_size: usize = 8 * 8192; -pub fn benchmarkHash(comptime H: var, bytes: usize) !Result { +pub fn benchmarkHash(comptime H: anytype, bytes: usize) !Result { var h = blk: { if (H.init_u8s) |init| { break :blk H.ty.init(init); @@ -119,7 +119,7 @@ pub fn benchmarkHash(comptime H: var, bytes: usize) !Result { }; } -pub fn benchmarkHashSmallKeys(comptime H: var, key_size: usize, bytes: usize) !Result { +pub fn benchmarkHashSmallKeys(comptime H: anytype, key_size: usize, bytes: usize) !Result { const key_count = bytes / key_size; var block: [block_size]u8 = undefined; prng.random.bytes(block[0..]); diff --git a/lib/std/hash/cityhash.zig b/lib/std/hash/cityhash.zig index a717303090ff521dafac1d96ccf5ee28a98299c1..73b94acbd2b14a36c62a8beeb4da61e0d8044c6d 100644 --- a/lib/std/hash/cityhash.zig +++ b/lib/std/hash/cityhash.zig @@ -354,7 +354,7 @@ pub const CityHash64 = struct { } }; -fn SMHasherTest(comptime hash_fn: var, comptime hashbits: u32) u32 { +fn SMHasherTest(comptime hash_fn: anytype, comptime hashbits: u32) u32 { const hashbytes = hashbits / 8; var key: [256]u8 = undefined; var hashes: [hashbytes * 256]u8 = undefined; diff --git a/lib/std/hash/murmur.zig b/lib/std/hash/murmur.zig index 96efc8b9c1eb26e984c95789678ad80bed5fee6c..effa13ad690912f7e5993ba39af4516be9129f68 100644 --- a/lib/std/hash/murmur.zig +++ b/lib/std/hash/murmur.zig @@ -279,7 +279,7 @@ pub const Murmur3_32 = struct { } }; -fn SMHasherTest(comptime hash_fn: var, comptime hashbits: u32) u32 { +fn SMHasherTest(comptime hash_fn: anytype, comptime hashbits: u32) u32 { const hashbytes = hashbits / 8; var key: [256]u8 = undefined; var hashes: [hashbytes * 256]u8 = undefined; diff --git a/lib/std/heap.zig b/lib/std/heap.zig index ea9e95c67569c164d78c6bca6952e1c59de9f3e4..ba96257557b0e9fb74d11b25261de7663d60cc61 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -15,15 +15,20 @@ pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator; const Allocator = mem.Allocator; -usingnamespace if (comptime @hasDecl(c, "malloc_size")) struct { - pub const supports_malloc_size = true; - pub const malloc_size = c.malloc_size; -} else if (comptime @hasDecl(c, "malloc_usable_size")) struct { - pub const supports_malloc_size = true; - pub const malloc_size = c.malloc_usable_size; -} else struct { - pub const supports_malloc_size = false; -}; +usingnamespace if (comptime @hasDecl(c, "malloc_size")) + struct { + pub const supports_malloc_size = true; + pub const malloc_size = c.malloc_size; + } +else if (comptime @hasDecl(c, "malloc_usable_size")) + struct { + pub const supports_malloc_size = true; + pub const malloc_size = c.malloc_usable_size; + } +else + struct { + pub const supports_malloc_size = false; + }; pub const c_allocator = &c_allocator_state; var c_allocator_state = Allocator{ @@ -151,8 +156,7 @@ const PageAllocator = struct { } const maxDropLen = alignment - std.math.min(alignment, mem.page_size); - const allocLen = if (maxDropLen <= alignedLen - n) alignedLen - else mem.alignForward(alignedLen + maxDropLen, mem.page_size); + const allocLen = if (maxDropLen <= alignedLen - n) alignedLen else mem.alignForward(alignedLen + maxDropLen, mem.page_size); const slice = os.mmap( null, allocLen, @@ -331,8 +335,7 @@ const WasmPageAllocator = struct { fn alloc(allocator: *Allocator, len: usize, alignment: u29, len_align: u29) error{OutOfMemory}![]u8 { const page_count = nPages(len); const page_idx = try allocPages(page_count, alignment); - return @intToPtr([*]u8, page_idx * mem.page_size) - [0..alignPageAllocLen(page_count * mem.page_size, len, len_align)]; + return @intToPtr([*]u8, page_idx * mem.page_size)[0..alignPageAllocLen(page_count * mem.page_size, len, len_align)]; } fn allocPages(page_count: usize, alignment: u29) !usize { { @@ -452,7 +455,7 @@ pub const HeapAllocator = switch (builtin.os.tag) { fn resize(allocator: *Allocator, buf: []u8, new_size: usize, len_align: u29) error{OutOfMemory}!usize { const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); if (new_size == 0) { - os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void ,getRecordPtr(buf).*)); + os.windows.HeapFree(self.heap_handle.?, 0, @intToPtr(*c_void, getRecordPtr(buf).*)); return 0; } diff --git a/lib/std/heap/logging_allocator.zig b/lib/std/heap/logging_allocator.zig index b521515a79f3239ac319d0ac2b522cf3b4a32657..d3055c75ee0e8d30657078555b15a908a66edd21 100644 --- a/lib/std/heap/logging_allocator.zig +++ b/lib/std/heap/logging_allocator.zig @@ -40,7 +40,7 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type { if (new_len == 0) { self.out_stream.print("free : {}\n", .{buf.len}) catch {}; } else if (new_len <= buf.len) { - self.out_stream.print("shrink: {} to {}\n", .{buf.len, new_len}) catch {}; + self.out_stream.print("shrink: {} to {}\n", .{ buf.len, new_len }) catch {}; } else { self.out_stream.print("expand: {} to {}", .{ buf.len, new_len }) catch {}; } @@ -60,7 +60,7 @@ pub fn LoggingAllocator(comptime OutStreamType: type) type { pub fn loggingAllocator( parent_allocator: *Allocator, - out_stream: var, + out_stream: anytype, ) LoggingAllocator(@TypeOf(out_stream)) { return LoggingAllocator(@TypeOf(out_stream)).init(parent_allocator, out_stream); } diff --git a/lib/std/http/headers.zig b/lib/std/http/headers.zig index 9310dac34806c105c489aa1ffb5103c6c99c84f5..f5465d4151e3994c3133f4fa5cc9ab180e550706 100644 --- a/lib/std/http/headers.zig +++ b/lib/std/http/headers.zig @@ -348,7 +348,7 @@ pub const Headers = struct { self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, - out_stream: var, + out_stream: anytype, ) !void { for (self.toSlice()) |entry| { try out_stream.writeAll(entry.name); diff --git a/lib/std/io/bit_reader.zig b/lib/std/io/bit_reader.zig index d5e8ce934f0f316d83378899e9d8934e30b43572..fbdf7fbe78c4234d2e8fea1ff860ead19ac8695e 100644 --- a/lib/std/io/bit_reader.zig +++ b/lib/std/io/bit_reader.zig @@ -170,7 +170,7 @@ pub fn BitReader(endian: builtin.Endian, comptime ReaderType: type) type { pub fn bitReader( comptime endian: builtin.Endian, - underlying_stream: var, + underlying_stream: anytype, ) BitReader(endian, @TypeOf(underlying_stream)) { return BitReader(endian, @TypeOf(underlying_stream)).init(underlying_stream); } diff --git a/lib/std/io/bit_writer.zig b/lib/std/io/bit_writer.zig index bdf9156136dd979ee0394282611657432179f80e..7c1d3e5dba94493f06ba9187e56d59ae81a9fc50 100644 --- a/lib/std/io/bit_writer.zig +++ b/lib/std/io/bit_writer.zig @@ -34,7 +34,7 @@ pub fn BitWriter(endian: builtin.Endian, comptime WriterType: type) type { /// Write the specified number of bits to the stream from the least significant bits of /// the specified unsigned int value. Bits will only be written to the stream when there /// are enough to fill a byte. - pub fn writeBits(self: *Self, value: var, bits: usize) Error!void { + pub fn writeBits(self: *Self, value: anytype, bits: usize) Error!void { if (bits == 0) return; const U = @TypeOf(value); @@ -145,7 +145,7 @@ pub fn BitWriter(endian: builtin.Endian, comptime WriterType: type) type { pub fn bitWriter( comptime endian: builtin.Endian, - underlying_stream: var, + underlying_stream: anytype, ) BitWriter(endian, @TypeOf(underlying_stream)) { return BitWriter(endian, @TypeOf(underlying_stream)).init(underlying_stream); } diff --git a/lib/std/io/buffered_reader.zig b/lib/std/io/buffered_reader.zig index f33dc127d22dee5e7be2d5698b32168a791fb331..73d74b465f5d08cdaefdde74e5e546a55ed8eaeb 100644 --- a/lib/std/io/buffered_reader.zig +++ b/lib/std/io/buffered_reader.zig @@ -48,7 +48,7 @@ pub fn BufferedReader(comptime buffer_size: usize, comptime ReaderType: type) ty }; } -pub fn bufferedReader(underlying_stream: var) BufferedReader(4096, @TypeOf(underlying_stream)) { +pub fn bufferedReader(underlying_stream: anytype) BufferedReader(4096, @TypeOf(underlying_stream)) { return .{ .unbuffered_reader = underlying_stream }; } diff --git a/lib/std/io/buffered_writer.zig b/lib/std/io/buffered_writer.zig index 5cd102b5102e1b738d74a14a23751034badb3e7b..a970f899d63d8461c5abb59644f16f200d30c287 100644 --- a/lib/std/io/buffered_writer.zig +++ b/lib/std/io/buffered_writer.zig @@ -43,6 +43,6 @@ pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) ty }; } -pub fn bufferedWriter(underlying_stream: var) BufferedWriter(4096, @TypeOf(underlying_stream)) { +pub fn bufferedWriter(underlying_stream: anytype) BufferedWriter(4096, @TypeOf(underlying_stream)) { return .{ .unbuffered_writer = underlying_stream }; } diff --git a/lib/std/io/counting_writer.zig b/lib/std/io/counting_writer.zig index 90e4580eea36bc28aebcd4acee83a8078b27a2a3..c0cd53c7eed15cd264f113734a07494dca29f4e3 100644 --- a/lib/std/io/counting_writer.zig +++ b/lib/std/io/counting_writer.zig @@ -32,7 +32,7 @@ pub fn CountingWriter(comptime WriterType: type) type { }; } -pub fn countingWriter(child_stream: var) CountingWriter(@TypeOf(child_stream)) { +pub fn countingWriter(child_stream: anytype) CountingWriter(@TypeOf(child_stream)) { return .{ .bytes_written = 0, .child_stream = child_stream }; } diff --git a/lib/std/io/fixed_buffer_stream.zig b/lib/std/io/fixed_buffer_stream.zig index ee5fe48ca5ce5d4f217c4798f900d7bbe7a59929..32625f3b7afd3f0fae659af2d8926e4e9b73f4f3 100644 --- a/lib/std/io/fixed_buffer_stream.zig +++ b/lib/std/io/fixed_buffer_stream.zig @@ -127,7 +127,7 @@ pub fn FixedBufferStream(comptime Buffer: type) type { }; } -pub fn fixedBufferStream(buffer: var) FixedBufferStream(NonSentinelSpan(@TypeOf(buffer))) { +pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(NonSentinelSpan(@TypeOf(buffer))) { return .{ .buffer = mem.span(buffer), .pos = 0 }; } diff --git a/lib/std/io/multi_writer.zig b/lib/std/io/multi_writer.zig index 02ed75eaaa4c9520adb0c7677d7985f53261e437..e63940bff7f151f10f6d23e66763fc14352312aa 100644 --- a/lib/std/io/multi_writer.zig +++ b/lib/std/io/multi_writer.zig @@ -43,7 +43,7 @@ pub fn MultiWriter(comptime Writers: type) type { }; } -pub fn multiWriter(streams: var) MultiWriter(@TypeOf(streams)) { +pub fn multiWriter(streams: anytype) MultiWriter(@TypeOf(streams)) { return .{ .streams = streams }; } diff --git a/lib/std/io/peek_stream.zig b/lib/std/io/peek_stream.zig index 2bf6b83bc5ba913d6efbc57a04908885ef2c5390..08e940c6ecbbb0dce197c649b2d159c7df6a2966 100644 --- a/lib/std/io/peek_stream.zig +++ b/lib/std/io/peek_stream.zig @@ -80,7 +80,7 @@ pub fn PeekStream( pub fn peekStream( comptime lookahead: comptime_int, - underlying_stream: var, + underlying_stream: anytype, ) PeekStream(.{ .Static = lookahead }, @TypeOf(underlying_stream)) { return PeekStream(.{ .Static = lookahead }, @TypeOf(underlying_stream)).init(underlying_stream); } diff --git a/lib/std/io/serialization.zig b/lib/std/io/serialization.zig index 8c63b8b966bbebb4ba4bb9d35aecae7994d4bbbc..8fe0782c849e48320518483c199916914ac8a1c1 100644 --- a/lib/std/io/serialization.zig +++ b/lib/std/io/serialization.zig @@ -93,7 +93,7 @@ pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, } /// Deserializes data into the type pointed to by `ptr` - pub fn deserializeInto(self: *Self, ptr: var) !void { + pub fn deserializeInto(self: *Self, ptr: anytype) !void { const T = @TypeOf(ptr); comptime assert(trait.is(.Pointer)(T)); @@ -190,7 +190,7 @@ pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, pub fn deserializer( comptime endian: builtin.Endian, comptime packing: Packing, - in_stream: var, + in_stream: anytype, ) Deserializer(endian, packing, @TypeOf(in_stream)) { return Deserializer(endian, packing, @TypeOf(in_stream)).init(in_stream); } @@ -229,7 +229,7 @@ pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, co if (packing == .Bit) return self.out_stream.flushBits(); } - fn serializeInt(self: *Self, value: var) Error!void { + fn serializeInt(self: *Self, value: anytype) Error!void { const T = @TypeOf(value); comptime assert(trait.is(.Int)(T) or trait.is(.Float)(T)); @@ -261,7 +261,7 @@ pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, co } /// Serializes the passed value into the stream - pub fn serialize(self: *Self, value: var) Error!void { + pub fn serialize(self: *Self, value: anytype) Error!void { const T = comptime @TypeOf(value); if (comptime trait.isIndexable(T)) { @@ -346,7 +346,7 @@ pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, co pub fn serializer( comptime endian: builtin.Endian, comptime packing: Packing, - out_stream: var, + out_stream: anytype, ) Serializer(endian, packing, @TypeOf(out_stream)) { return Serializer(endian, packing, @TypeOf(out_stream)).init(out_stream); } @@ -462,7 +462,7 @@ test "Serializer/Deserializer Int: Inf/NaN" { try testIntSerializerDeserializerInfNaN(.Little, .Bit); } -fn testAlternateSerializer(self: var, _serializer: var) !void { +fn testAlternateSerializer(self: anytype, _serializer: anytype) !void { try _serializer.serialize(self.f_f16); } @@ -503,7 +503,7 @@ fn testSerializerDeserializer(comptime endian: builtin.Endian, comptime packing: f_f16: f16, f_unused_u32: u32, - pub fn deserialize(self: *@This(), _deserializer: var) !void { + pub fn deserialize(self: *@This(), _deserializer: anytype) !void { try _deserializer.deserializeInto(&self.f_f16); self.f_unused_u32 = 47; } diff --git a/lib/std/io/writer.zig b/lib/std/io/writer.zig index 659ba2703eb8e1c86e27f5af38caa1069ef36dce..a98e3b1acd78368ef4af2ea033ff5f0dfc4c5b81 100644 --- a/lib/std/io/writer.zig +++ b/lib/std/io/writer.zig @@ -24,7 +24,7 @@ pub fn Writer( } } - pub fn print(self: Self, comptime format: []const u8, args: var) Error!void { + pub fn print(self: Self, comptime format: []const u8, args: anytype) Error!void { return std.fmt.format(self, format, args); } diff --git a/lib/std/json.zig b/lib/std/json.zig index 65ebe550723b17fa04f69b0fe338512f6985c0bb..f1b91fc82939182b659f967216f6d8d9bb8e98c3 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -239,7 +239,7 @@ pub const StreamingParser = struct { NullLiteral3, // Only call this function to generate array/object final state. - pub fn fromInt(x: var) State { + pub fn fromInt(x: anytype) State { debug.assert(x == 0 or x == 1); const T = @TagType(State); return @intToEnum(State, @intCast(T, x)); @@ -1236,7 +1236,7 @@ pub const Value = union(enum) { pub fn jsonStringify( value: @This(), options: StringifyOptions, - out_stream: var, + out_stream: anytype, ) @TypeOf(out_stream).Error!void { switch (value) { .Null => try stringify(null, options, out_stream), @@ -2338,7 +2338,7 @@ pub const StringifyOptions = struct { pub fn outputIndent( whitespace: @This(), - out_stream: var, + out_stream: anytype, ) @TypeOf(out_stream).Error!void { var char: u8 = undefined; var n_chars: usize = undefined; @@ -2380,7 +2380,7 @@ pub const StringifyOptions = struct { fn outputUnicodeEscape( codepoint: u21, - out_stream: var, + out_stream: anytype, ) !void { if (codepoint <= 0xFFFF) { // If the character is in the Basic Multilingual Plane (U+0000 through U+FFFF), @@ -2402,9 +2402,9 @@ fn outputUnicodeEscape( } pub fn stringify( - value: var, + value: anytype, options: StringifyOptions, - out_stream: var, + out_stream: anytype, ) @TypeOf(out_stream).Error!void { const T = @TypeOf(value); switch (@typeInfo(T)) { @@ -2584,7 +2584,7 @@ pub fn stringify( unreachable; } -fn teststringify(expected: []const u8, value: var, options: StringifyOptions) !void { +fn teststringify(expected: []const u8, value: anytype, options: StringifyOptions) !void { const ValidationOutStream = struct { const Self = @This(); pub const OutStream = std.io.OutStream(*Self, Error, write); @@ -2758,7 +2758,7 @@ test "stringify struct with custom stringifier" { pub fn jsonStringify( value: Self, options: StringifyOptions, - out_stream: var, + out_stream: anytype, ) !void { try out_stream.writeAll("[\"something special\","); try stringify(42, options, out_stream); diff --git a/lib/std/json/write_stream.zig b/lib/std/json/write_stream.zig index dcfbf04bc1766ebd0ffae4a8040de83537e927d6..778173cc243c82af023f85df2304e2f5ce88f643 100644 --- a/lib/std/json/write_stream.zig +++ b/lib/std/json/write_stream.zig @@ -152,7 +152,7 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type { self: *Self, /// An integer, float, or `std.math.BigInt`. Emitted as a bare number if it fits losslessly /// in a IEEE 754 double float, otherwise emitted as a string to the full precision. - value: var, + value: anytype, ) !void { assert(self.state[self.state_index] == State.Value); switch (@typeInfo(@TypeOf(value))) { @@ -215,7 +215,7 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type { self.state_index -= 1; } - fn stringify(self: *Self, value: var) !void { + fn stringify(self: *Self, value: anytype) !void { try std.json.stringify(value, std.json.StringifyOptions{ .whitespace = self.whitespace, }, self.stream); @@ -224,7 +224,7 @@ pub fn WriteStream(comptime OutStream: type, comptime max_depth: usize) type { } pub fn writeStream( - out_stream: var, + out_stream: anytype, comptime max_depth: usize, ) WriteStream(@TypeOf(out_stream), max_depth) { return WriteStream(@TypeOf(out_stream), max_depth).init(out_stream); diff --git a/lib/std/log.zig b/lib/std/log.zig index 63aeaecf888d38c9d51efe51266e754ab9186fc9..000658003181d7bf39024a666c84b203e2887eee 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -101,7 +101,7 @@ fn log( comptime message_level: Level, comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { if (@enumToInt(message_level) <= @enumToInt(level)) { if (@hasDecl(root, "log")) { @@ -120,7 +120,7 @@ fn log( pub fn emerg( comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { @setCold(true); log(.emerg, scope, format, args); @@ -131,7 +131,7 @@ pub fn emerg( pub fn alert( comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { @setCold(true); log(.alert, scope, format, args); @@ -143,7 +143,7 @@ pub fn alert( pub fn crit( comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { @setCold(true); log(.crit, scope, format, args); @@ -154,7 +154,7 @@ pub fn crit( pub fn err( comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { @setCold(true); log(.err, scope, format, args); @@ -166,7 +166,7 @@ pub fn err( pub fn warn( comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { log(.warn, scope, format, args); } @@ -176,7 +176,7 @@ pub fn warn( pub fn notice( comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { log(.notice, scope, format, args); } @@ -186,7 +186,7 @@ pub fn notice( pub fn info( comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { log(.info, scope, format, args); } @@ -196,7 +196,7 @@ pub fn info( pub fn debug( comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { log(.debug, scope, format, args); } diff --git a/lib/std/math.zig b/lib/std/math.zig index 14ffd61c29ea15f20bda76128ff15d6fa52be388..111a618cef777b582e799244b38acafcaa3f6043 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -104,7 +104,7 @@ pub fn approxEq(comptime T: type, x: T, y: T, epsilon: T) bool { } // TODO: Hide the following in an internal module. -pub fn forceEval(value: var) void { +pub fn forceEval(value: anytype) void { const T = @TypeOf(value); switch (T) { f16 => { @@ -259,7 +259,7 @@ pub fn Min(comptime A: type, comptime B: type) type { /// Returns the smaller number. When one of the parameter's type's full range fits in the other, /// the return type is the smaller type. -pub fn min(x: var, y: var) Min(@TypeOf(x), @TypeOf(y)) { +pub fn min(x: anytype, y: anytype) Min(@TypeOf(x), @TypeOf(y)) { const Result = Min(@TypeOf(x), @TypeOf(y)); if (x < y) { // TODO Zig should allow this as an implicit cast because x is immutable and in this @@ -310,7 +310,7 @@ test "math.min" { } } -pub fn max(x: var, y: var) @TypeOf(x, y) { +pub fn max(x: anytype, y: anytype) @TypeOf(x, y) { return if (x > y) x else y; } @@ -318,7 +318,7 @@ test "math.max" { testing.expect(max(@as(i32, -1), @as(i32, 2)) == 2); } -pub fn clamp(val: var, lower: var, upper: var) @TypeOf(val, lower, upper) { +pub fn clamp(val: anytype, lower: anytype, upper: anytype) @TypeOf(val, lower, upper) { assert(lower <= upper); return max(lower, min(val, upper)); } @@ -354,7 +354,7 @@ pub fn sub(comptime T: type, a: T, b: T) (error{Overflow}!T) { return if (@subWithOverflow(T, a, b, &answer)) error.Overflow else answer; } -pub fn negate(x: var) !@TypeOf(x) { +pub fn negate(x: anytype) !@TypeOf(x) { return sub(@TypeOf(x), 0, x); } @@ -365,7 +365,7 @@ pub fn shlExact(comptime T: type, a: T, shift_amt: Log2Int(T)) !T { /// Shifts left. Overflowed bits are truncated. /// A negative shift amount results in a right shift. -pub fn shl(comptime T: type, a: T, shift_amt: var) T { +pub fn shl(comptime T: type, a: T, shift_amt: anytype) T { const abs_shift_amt = absCast(shift_amt); const casted_shift_amt = if (abs_shift_amt >= T.bit_count) return 0 else @intCast(Log2Int(T), abs_shift_amt); @@ -391,7 +391,7 @@ test "math.shl" { /// Shifts right. Overflowed bits are truncated. /// A negative shift amount results in a left shift. -pub fn shr(comptime T: type, a: T, shift_amt: var) T { +pub fn shr(comptime T: type, a: T, shift_amt: anytype) T { const abs_shift_amt = absCast(shift_amt); const casted_shift_amt = if (abs_shift_amt >= T.bit_count) return 0 else @intCast(Log2Int(T), abs_shift_amt); @@ -419,7 +419,7 @@ test "math.shr" { /// Rotates right. Only unsigned values can be rotated. /// Negative shift values results in shift modulo the bit count. -pub fn rotr(comptime T: type, x: T, r: var) T { +pub fn rotr(comptime T: type, x: T, r: anytype) T { if (T.is_signed) { @compileError("cannot rotate signed integer"); } else { @@ -438,7 +438,7 @@ test "math.rotr" { /// Rotates left. Only unsigned values can be rotated. /// Negative shift values results in shift modulo the bit count. -pub fn rotl(comptime T: type, x: T, r: var) T { +pub fn rotl(comptime T: type, x: T, r: anytype) T { if (T.is_signed) { @compileError("cannot rotate signed integer"); } else { @@ -541,7 +541,7 @@ fn testOverflow() void { testing.expect((shlExact(i32, 0b11, 4) catch unreachable) == 0b110000); } -pub fn absInt(x: var) !@TypeOf(x) { +pub fn absInt(x: anytype) !@TypeOf(x) { const T = @TypeOf(x); comptime assert(@typeInfo(T) == .Int); // must pass an integer to absInt comptime assert(T.is_signed); // must pass a signed integer to absInt @@ -689,7 +689,7 @@ fn testRem() void { /// Returns the absolute value of the integer parameter. /// Result is an unsigned integer. -pub fn absCast(x: var) switch (@typeInfo(@TypeOf(x))) { +pub fn absCast(x: anytype) switch (@typeInfo(@TypeOf(x))) { .ComptimeInt => comptime_int, .Int => |intInfo| std.meta.Int(false, intInfo.bits), else => @compileError("absCast only accepts integers"), @@ -724,7 +724,7 @@ test "math.absCast" { /// Returns the negation of the integer parameter. /// Result is a signed integer. -pub fn negateCast(x: var) !std.meta.Int(true, @TypeOf(x).bit_count) { +pub fn negateCast(x: anytype) !std.meta.Int(true, @TypeOf(x).bit_count) { if (@TypeOf(x).is_signed) return negate(x); const int = std.meta.Int(true, @TypeOf(x).bit_count); @@ -747,7 +747,7 @@ test "math.negateCast" { /// Cast an integer to a different integer type. If the value doesn't fit, /// return an error. -pub fn cast(comptime T: type, x: var) (error{Overflow}!T) { +pub fn cast(comptime T: type, x: anytype) (error{Overflow}!T) { comptime assert(@typeInfo(T) == .Int); // must pass an integer comptime assert(@typeInfo(@TypeOf(x)) == .Int); // must pass an integer if (maxInt(@TypeOf(x)) > maxInt(T) and x > maxInt(T)) { @@ -772,7 +772,7 @@ test "math.cast" { pub const AlignCastError = error{UnalignedMemory}; /// Align cast a pointer but return an error if it's the wrong alignment -pub fn alignCast(comptime alignment: u29, ptr: var) AlignCastError!@TypeOf(@alignCast(alignment, ptr)) { +pub fn alignCast(comptime alignment: u29, ptr: anytype) AlignCastError!@TypeOf(@alignCast(alignment, ptr)) { const addr = @ptrToInt(ptr); if (addr % alignment != 0) { return error.UnalignedMemory; @@ -780,7 +780,7 @@ pub fn alignCast(comptime alignment: u29, ptr: var) AlignCastError!@TypeOf(@alig return @alignCast(alignment, ptr); } -pub fn isPowerOfTwo(v: var) bool { +pub fn isPowerOfTwo(v: anytype) bool { assert(v != 0); return (v & (v - 1)) == 0; } @@ -897,7 +897,7 @@ test "std.math.log2_int_ceil" { testing.expect(log2_int_ceil(u32, 10) == 4); } -pub fn lossyCast(comptime T: type, value: var) T { +pub fn lossyCast(comptime T: type, value: anytype) T { switch (@typeInfo(@TypeOf(value))) { .Int => return @intToFloat(T, value), .Float => return @floatCast(T, value), @@ -1031,7 +1031,7 @@ pub const Order = enum { }; /// Given two numbers, this function returns the order they are with respect to each other. -pub fn order(a: var, b: var) Order { +pub fn order(a: anytype, b: anytype) Order { if (a == b) { return .eq; } else if (a < b) { @@ -1062,7 +1062,7 @@ pub const CompareOperator = enum { /// This function does the same thing as comparison operators, however the /// operator is a runtime-known enum value. Works on any operands that /// support comparison operators. -pub fn compare(a: var, op: CompareOperator, b: var) bool { +pub fn compare(a: anytype, op: CompareOperator, b: anytype) bool { return switch (op) { .lt => a < b, .lte => a <= b, diff --git a/lib/std/math/acos.zig b/lib/std/math/acos.zig index aec0d4706acefaa7dbb06adad22e5fa1213014cc..cdd86601fd80c20e27e01ecf5de9f85ccd980792 100644 --- a/lib/std/math/acos.zig +++ b/lib/std/math/acos.zig @@ -12,7 +12,7 @@ const expect = std.testing.expect; /// /// Special cases: /// - acos(x) = nan if x < -1 or x > 1 -pub fn acos(x: var) @TypeOf(x) { +pub fn acos(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => acos32(x), diff --git a/lib/std/math/acosh.zig b/lib/std/math/acosh.zig index 0f99335058f0702f8073aa031898685c8b549ed2..9a594f9cc4ab413a8aa132ef07e1b13f4f28cb6c 100644 --- a/lib/std/math/acosh.zig +++ b/lib/std/math/acosh.zig @@ -14,7 +14,7 @@ const expect = std.testing.expect; /// Special cases: /// - acosh(x) = snan if x < 1 /// - acosh(nan) = nan -pub fn acosh(x: var) @TypeOf(x) { +pub fn acosh(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => acosh32(x), diff --git a/lib/std/math/asin.zig b/lib/std/math/asin.zig index db57e2088f5189ec4e5ebfb31e8393321119c20a..4cff69fc1bf1b5e3834facb506a4f07b0ecb3a77 100644 --- a/lib/std/math/asin.zig +++ b/lib/std/math/asin.zig @@ -13,7 +13,7 @@ const expect = std.testing.expect; /// Special Cases: /// - asin(+-0) = +-0 /// - asin(x) = nan if x < -1 or x > 1 -pub fn asin(x: var) @TypeOf(x) { +pub fn asin(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => asin32(x), diff --git a/lib/std/math/asinh.zig b/lib/std/math/asinh.zig index ab1b6501392789f80e69df5a563448aa1155e27c..940b953d06b7b331aadda9c9a9f29c30c13e4df6 100644 --- a/lib/std/math/asinh.zig +++ b/lib/std/math/asinh.zig @@ -15,7 +15,7 @@ const maxInt = std.math.maxInt; /// - asinh(+-0) = +-0 /// - asinh(+-inf) = +-inf /// - asinh(nan) = nan -pub fn asinh(x: var) @TypeOf(x) { +pub fn asinh(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => asinh32(x), diff --git a/lib/std/math/atan.zig b/lib/std/math/atan.zig index eb9154b5fe64512d54a69741308ce502b669a5ec..9342b6ed594cbbfae3367d3965abf73436f75210 100644 --- a/lib/std/math/atan.zig +++ b/lib/std/math/atan.zig @@ -13,7 +13,7 @@ const expect = std.testing.expect; /// Special Cases: /// - atan(+-0) = +-0 /// - atan(+-inf) = +-pi/2 -pub fn atan(x: var) @TypeOf(x) { +pub fn atan(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => atan32(x), diff --git a/lib/std/math/atanh.zig b/lib/std/math/atanh.zig index e58a10fff5bd72e81feb2a75723b7dda9dd0cf04..de742bd4cdff2c283071ce6612b14e2704343e33 100644 --- a/lib/std/math/atanh.zig +++ b/lib/std/math/atanh.zig @@ -15,7 +15,7 @@ const maxInt = std.math.maxInt; /// - atanh(+-1) = +-inf with signal /// - atanh(x) = nan if |x| > 1 with signal /// - atanh(nan) = nan -pub fn atanh(x: var) @TypeOf(x) { +pub fn atanh(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => atanh_32(x), diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 85e14bc55ca91656955b82d051123be06f241d70..b6d7731f1aa4396a0da8013cc3c19eca3d0f57b0 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -12,7 +12,7 @@ const assert = std.debug.assert; /// Returns the number of limbs needed to store `scalar`, which must be a /// primitive integer value. -pub fn calcLimbLen(scalar: var) usize { +pub fn calcLimbLen(scalar: anytype) usize { const T = @TypeOf(scalar); switch (@typeInfo(T)) { .Int => |info| { @@ -110,7 +110,7 @@ pub const Mutable = struct { /// `value` is a primitive integer type. /// Asserts the value fits within the provided `limbs_buffer`. /// Note: `calcLimbLen` can be used to figure out how big an array to allocate for `limbs_buffer`. - pub fn init(limbs_buffer: []Limb, value: var) Mutable { + pub fn init(limbs_buffer: []Limb, value: anytype) Mutable { limbs_buffer[0] = 0; var self: Mutable = .{ .limbs = limbs_buffer, @@ -169,7 +169,7 @@ pub const Mutable = struct { /// Asserts the value fits within the limbs buffer. /// Note: `calcLimbLen` can be used to figure out how big the limbs buffer /// needs to be to store a specific value. - pub fn set(self: *Mutable, value: var) void { + pub fn set(self: *Mutable, value: anytype) void { const T = @TypeOf(value); switch (@typeInfo(T)) { @@ -281,7 +281,7 @@ pub const Mutable = struct { /// /// Asserts the result fits in `r`. An upper bound on the number of limbs needed by /// r is `math.max(a.limbs.len, calcLimbLen(scalar)) + 1`. - pub fn addScalar(r: *Mutable, a: Const, scalar: var) void { + pub fn addScalar(r: *Mutable, a: Const, scalar: anytype) void { var limbs: [calcLimbLen(scalar)]Limb = undefined; const operand = init(&limbs, scalar).toConst(); return add(r, a, operand); @@ -1058,7 +1058,7 @@ pub const Const = struct { self: Const, comptime fmt: []const u8, options: std.fmt.FormatOptions, - out_stream: var, + out_stream: anytype, ) !void { comptime var radix = 10; comptime var uppercase = false; @@ -1261,7 +1261,7 @@ pub const Const = struct { } /// Same as `order` but the right-hand operand is a primitive integer. - pub fn orderAgainstScalar(lhs: Const, scalar: var) math.Order { + pub fn orderAgainstScalar(lhs: Const, scalar: anytype) math.Order { var limbs: [calcLimbLen(scalar)]Limb = undefined; const rhs = Mutable.init(&limbs, scalar); return order(lhs, rhs.toConst()); @@ -1333,7 +1333,7 @@ pub const Managed = struct { /// Creates a new `Managed` with value `value`. /// /// This is identical to an `init`, followed by a `set`. - pub fn initSet(allocator: *Allocator, value: var) !Managed { + pub fn initSet(allocator: *Allocator, value: anytype) !Managed { var s = try Managed.init(allocator); try s.set(value); return s; @@ -1496,7 +1496,7 @@ pub const Managed = struct { } /// Sets an Managed to value. Value must be an primitive integer type. - pub fn set(self: *Managed, value: var) Allocator.Error!void { + pub fn set(self: *Managed, value: anytype) Allocator.Error!void { try self.ensureCapacity(calcLimbLen(value)); var m = self.toMutable(); m.set(value); @@ -1549,7 +1549,7 @@ pub const Managed = struct { self: Managed, comptime fmt: []const u8, options: std.fmt.FormatOptions, - out_stream: var, + out_stream: anytype, ) !void { return self.toConst().format(fmt, options, out_stream); } @@ -1607,7 +1607,7 @@ pub const Managed = struct { /// scalar is a primitive integer type. /// /// Returns an error if memory could not be allocated. - pub fn addScalar(r: *Managed, a: Const, scalar: var) Allocator.Error!void { + pub fn addScalar(r: *Managed, a: Const, scalar: anytype) Allocator.Error!void { try r.ensureCapacity(math.max(a.limbs.len, calcLimbLen(scalar)) + 1); var m = r.toMutable(); m.addScalar(a, scalar); diff --git a/lib/std/math/big/rational.zig b/lib/std/math/big/rational.zig index 3624a161393aaccc8c62a847a51ca58b992c3499..6f62a462b8739ebdd3b78b9a0639a38983889d3d 100644 --- a/lib/std/math/big/rational.zig +++ b/lib/std/math/big/rational.zig @@ -43,7 +43,7 @@ pub const Rational = struct { } /// Set a Rational from a primitive integer type. - pub fn setInt(self: *Rational, a: var) !void { + pub fn setInt(self: *Rational, a: anytype) !void { try self.p.set(a); try self.q.set(1); } @@ -280,7 +280,7 @@ pub const Rational = struct { } /// Set a rational from an integer ratio. - pub fn setRatio(self: *Rational, p: var, q: var) !void { + pub fn setRatio(self: *Rational, p: anytype, q: anytype) !void { try self.p.set(p); try self.q.set(q); diff --git a/lib/std/math/cbrt.zig b/lib/std/math/cbrt.zig index 2b219d5368d28be97aa915e377fd3d9d2b390abf..42163b96dcfb2c32fe4f8ad12aa521fec812e53f 100644 --- a/lib/std/math/cbrt.zig +++ b/lib/std/math/cbrt.zig @@ -14,7 +14,7 @@ const expect = std.testing.expect; /// - cbrt(+-0) = +-0 /// - cbrt(+-inf) = +-inf /// - cbrt(nan) = nan -pub fn cbrt(x: var) @TypeOf(x) { +pub fn cbrt(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => cbrt32(x), diff --git a/lib/std/math/ceil.zig b/lib/std/math/ceil.zig index e3b567931881fd4c7b560952b9c19456066f339f..39de46f361617812397c6c7a46e409d18d108833 100644 --- a/lib/std/math/ceil.zig +++ b/lib/std/math/ceil.zig @@ -15,7 +15,7 @@ const expect = std.testing.expect; /// - ceil(+-0) = +-0 /// - ceil(+-inf) = +-inf /// - ceil(nan) = nan -pub fn ceil(x: var) @TypeOf(x) { +pub fn ceil(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => ceil32(x), diff --git a/lib/std/math/complex/abs.zig b/lib/std/math/complex/abs.zig index 75b967f3d20b45dffcd16a7f6aeb9866e1090fd1..db31aef42ad124dded6233d91ff19734437e811f 100644 --- a/lib/std/math/complex/abs.zig +++ b/lib/std/math/complex/abs.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the absolute value (modulus) of z. -pub fn abs(z: var) @TypeOf(z.re) { +pub fn abs(z: anytype) @TypeOf(z.re) { const T = @TypeOf(z.re); return math.hypot(T, z.re, z.im); } diff --git a/lib/std/math/complex/acos.zig b/lib/std/math/complex/acos.zig index 24a645375cd21a92ced44e474a386ca4a65eeb40..072fd77f08910016593b91c36da6cb85109e9c21 100644 --- a/lib/std/math/complex/acos.zig +++ b/lib/std/math/complex/acos.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the arc-cosine of z. -pub fn acos(z: var) Complex(@TypeOf(z.re)) { +pub fn acos(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); const q = cmath.asin(z); return Complex(T).new(@as(T, math.pi) / 2 - q.re, -q.im); diff --git a/lib/std/math/complex/acosh.zig b/lib/std/math/complex/acosh.zig index 996334034a7af4d818e50a6e2a64c9a75fb89d78..59117a8b270b8c957a563b8b9af456c1b5008196 100644 --- a/lib/std/math/complex/acosh.zig +++ b/lib/std/math/complex/acosh.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the hyperbolic arc-cosine of z. -pub fn acosh(z: var) Complex(@TypeOf(z.re)) { +pub fn acosh(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); const q = cmath.acos(z); return Complex(T).new(-q.im, q.re); diff --git a/lib/std/math/complex/arg.zig b/lib/std/math/complex/arg.zig index f690e92143a87f5186f81162ddc0007e21781eb1..6cf959a081db87be0413f93c01bccb81e98650cc 100644 --- a/lib/std/math/complex/arg.zig +++ b/lib/std/math/complex/arg.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the angular component (in radians) of z. -pub fn arg(z: var) @TypeOf(z.re) { +pub fn arg(z: anytype) @TypeOf(z.re) { const T = @TypeOf(z.re); return math.atan2(T, z.im, z.re); } diff --git a/lib/std/math/complex/asin.zig b/lib/std/math/complex/asin.zig index 01fa33156a2a021a227c85f613d0e87b39723aca..9f7cd396aaf664fcdc31531f69303a3ec8394a1d 100644 --- a/lib/std/math/complex/asin.zig +++ b/lib/std/math/complex/asin.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; // Returns the arc-sine of z. -pub fn asin(z: var) Complex(@TypeOf(z.re)) { +pub fn asin(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); const x = z.re; const y = z.im; diff --git a/lib/std/math/complex/asinh.zig b/lib/std/math/complex/asinh.zig index 47d8244adb8fae6170ff0671d0f9b6090481ab84..0c3c2bd115556488a9fb4a4c87f867fe7d2aa876 100644 --- a/lib/std/math/complex/asinh.zig +++ b/lib/std/math/complex/asinh.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the hyperbolic arc-sine of z. -pub fn asinh(z: var) Complex(@TypeOf(z.re)) { +pub fn asinh(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); const q = Complex(T).new(-z.im, z.re); const r = cmath.asin(q); diff --git a/lib/std/math/complex/atan.zig b/lib/std/math/complex/atan.zig index 5ba6f7b0d2c2a4be3e632da65202af44c12d2479..98bde3e125a7e85a4ee93e7b3dd55cf64f25a5f9 100644 --- a/lib/std/math/complex/atan.zig +++ b/lib/std/math/complex/atan.zig @@ -12,7 +12,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the arc-tangent of z. -pub fn atan(z: var) @TypeOf(z) { +pub fn atan(z: anytype) @TypeOf(z) { const T = @TypeOf(z.re); return switch (T) { f32 => atan32(z), diff --git a/lib/std/math/complex/atanh.zig b/lib/std/math/complex/atanh.zig index 8b703062246d618d1cabc2a6305c74d4116be2e5..a07c2969e49b1ab6e717a9233e8cb9661705fdd3 100644 --- a/lib/std/math/complex/atanh.zig +++ b/lib/std/math/complex/atanh.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the hyperbolic arc-tangent of z. -pub fn atanh(z: var) Complex(@TypeOf(z.re)) { +pub fn atanh(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); const q = Complex(T).new(-z.im, z.re); const r = cmath.atan(q); diff --git a/lib/std/math/complex/conj.zig b/lib/std/math/complex/conj.zig index 1065d4bb73fe4c8ccdf3b978a37b7cecdec3b144..42a34e7dfcb09cf731497d7800887f3c304390c8 100644 --- a/lib/std/math/complex/conj.zig +++ b/lib/std/math/complex/conj.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the complex conjugate of z. -pub fn conj(z: var) Complex(@TypeOf(z.re)) { +pub fn conj(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); return Complex(T).new(z.re, -z.im); } diff --git a/lib/std/math/complex/cos.zig b/lib/std/math/complex/cos.zig index 1aefa73db5b1bb76ca2503cf1ce503a8ba9b1d6d..9daf89c7302f6472f8581018e5a24b4923a2b95f 100644 --- a/lib/std/math/complex/cos.zig +++ b/lib/std/math/complex/cos.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the cosine of z. -pub fn cos(z: var) Complex(@TypeOf(z.re)) { +pub fn cos(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); const p = Complex(T).new(-z.im, z.re); return cmath.cosh(p); diff --git a/lib/std/math/complex/cosh.zig b/lib/std/math/complex/cosh.zig index a9ac8936024c0b55fd3bee0ae746804dd0395202..bd51629bd4f6b1eb4aba3055960a0e51c0e93147 100644 --- a/lib/std/math/complex/cosh.zig +++ b/lib/std/math/complex/cosh.zig @@ -14,7 +14,7 @@ const Complex = cmath.Complex; const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; /// Returns the hyperbolic arc-cosine of z. -pub fn cosh(z: var) Complex(@TypeOf(z.re)) { +pub fn cosh(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); return switch (T) { f32 => cosh32(z), diff --git a/lib/std/math/complex/exp.zig b/lib/std/math/complex/exp.zig index 9f9e3db80762e5260381946c9794573ebe04963c..6f6061a94762d31d580c845faf45993e29a0c739 100644 --- a/lib/std/math/complex/exp.zig +++ b/lib/std/math/complex/exp.zig @@ -14,7 +14,7 @@ const Complex = cmath.Complex; const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; /// Returns e raised to the power of z (e^z). -pub fn exp(z: var) @TypeOf(z) { +pub fn exp(z: anytype) @TypeOf(z) { const T = @TypeOf(z.re); return switch (T) { diff --git a/lib/std/math/complex/ldexp.zig b/lib/std/math/complex/ldexp.zig index 9eccd4bb983886d7f7861671f468814de91fa74d..c23b9b346e0ceb5cea2fd8cf3a3a501b94c9cef0 100644 --- a/lib/std/math/complex/ldexp.zig +++ b/lib/std/math/complex/ldexp.zig @@ -11,7 +11,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns exp(z) scaled to avoid overflow. -pub fn ldexp_cexp(z: var, expt: i32) @TypeOf(z) { +pub fn ldexp_cexp(z: anytype, expt: i32) @TypeOf(z) { const T = @TypeOf(z.re); return switch (T) { diff --git a/lib/std/math/complex/log.zig b/lib/std/math/complex/log.zig index f1fad3175e16aa1b952ad3a2543a9e1bf5c2bfb9..ec02c6c32570d438dc3ca7ce0425fd50abfa94a2 100644 --- a/lib/std/math/complex/log.zig +++ b/lib/std/math/complex/log.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the natural logarithm of z. -pub fn log(z: var) Complex(@TypeOf(z.re)) { +pub fn log(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); const r = cmath.abs(z); const phi = cmath.arg(z); diff --git a/lib/std/math/complex/proj.zig b/lib/std/math/complex/proj.zig index 349f6b3abb080bde5d4dfc89413845e93c14f637..e208ae037002124d09696edc9e30c3bc8f2772dc 100644 --- a/lib/std/math/complex/proj.zig +++ b/lib/std/math/complex/proj.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the projection of z onto the riemann sphere. -pub fn proj(z: var) Complex(@TypeOf(z.re)) { +pub fn proj(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); if (math.isInf(z.re) or math.isInf(z.im)) { diff --git a/lib/std/math/complex/sin.zig b/lib/std/math/complex/sin.zig index 87dc57911b27194a6767af463bb7cc771150f87b..1b10f8fca6fecae820be38ca57a5c967c21f74cd 100644 --- a/lib/std/math/complex/sin.zig +++ b/lib/std/math/complex/sin.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the sine of z. -pub fn sin(z: var) Complex(@TypeOf(z.re)) { +pub fn sin(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); const p = Complex(T).new(-z.im, z.re); const q = cmath.sinh(p); diff --git a/lib/std/math/complex/sinh.zig b/lib/std/math/complex/sinh.zig index 7dd880c71c583e4ec12be3f2dfde70560c2ec810..32f2a730fbebffe3bfd7b03febf012c2743ea57b 100644 --- a/lib/std/math/complex/sinh.zig +++ b/lib/std/math/complex/sinh.zig @@ -14,7 +14,7 @@ const Complex = cmath.Complex; const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; /// Returns the hyperbolic sine of z. -pub fn sinh(z: var) @TypeOf(z) { +pub fn sinh(z: anytype) @TypeOf(z) { const T = @TypeOf(z.re); return switch (T) { f32 => sinh32(z), diff --git a/lib/std/math/complex/sqrt.zig b/lib/std/math/complex/sqrt.zig index 57e73f6cd188b4c7a3d82116935bef838d1a7b7e..0edb02a7a96c8d174050d24e98fc54d01a0dfc8f 100644 --- a/lib/std/math/complex/sqrt.zig +++ b/lib/std/math/complex/sqrt.zig @@ -12,7 +12,7 @@ const Complex = cmath.Complex; /// Returns the square root of z. The real and imaginary parts of the result have the same sign /// as the imaginary part of z. -pub fn sqrt(z: var) @TypeOf(z) { +pub fn sqrt(z: anytype) @TypeOf(z) { const T = @TypeOf(z.re); return switch (T) { diff --git a/lib/std/math/complex/tan.zig b/lib/std/math/complex/tan.zig index 70304803dbb295f93c027035fe030edb2d464846..050898c5732288442854729909c10d02de5bb134 100644 --- a/lib/std/math/complex/tan.zig +++ b/lib/std/math/complex/tan.zig @@ -5,7 +5,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the tanget of z. -pub fn tan(z: var) Complex(@TypeOf(z.re)) { +pub fn tan(z: anytype) Complex(@TypeOf(z.re)) { const T = @TypeOf(z.re); const q = Complex(T).new(-z.im, z.re); const r = cmath.tanh(q); diff --git a/lib/std/math/complex/tanh.zig b/lib/std/math/complex/tanh.zig index afd2e6aee44640edbd16dd2c7feba56bbd347e95..1d614cca583d21917922cdbe7d9b9fbfbee1f17d 100644 --- a/lib/std/math/complex/tanh.zig +++ b/lib/std/math/complex/tanh.zig @@ -12,7 +12,7 @@ const cmath = math.complex; const Complex = cmath.Complex; /// Returns the hyperbolic tangent of z. -pub fn tanh(z: var) @TypeOf(z) { +pub fn tanh(z: anytype) @TypeOf(z) { const T = @TypeOf(z.re); return switch (T) { f32 => tanh32(z), diff --git a/lib/std/math/cos.zig b/lib/std/math/cos.zig index aa336769b16d5465df62c10ec708de50c12c7359..df5c0a53be403e37246772a71eb3e44ffe2ed545 100644 --- a/lib/std/math/cos.zig +++ b/lib/std/math/cos.zig @@ -13,7 +13,7 @@ const expect = std.testing.expect; /// Special Cases: /// - cos(+-inf) = nan /// - cos(nan) = nan -pub fn cos(x: var) @TypeOf(x) { +pub fn cos(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => cos_(f32, x), diff --git a/lib/std/math/cosh.zig b/lib/std/math/cosh.zig index 1cd8c5f27f0320c76405d0f4bf34ba95a2bf1f88..bab47dcdbdaf69fe32c5511242ce2dd2f83f7e4d 100644 --- a/lib/std/math/cosh.zig +++ b/lib/std/math/cosh.zig @@ -17,7 +17,7 @@ const maxInt = std.math.maxInt; /// - cosh(+-0) = 1 /// - cosh(+-inf) = +inf /// - cosh(nan) = nan -pub fn cosh(x: var) @TypeOf(x) { +pub fn cosh(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => cosh32(x), diff --git a/lib/std/math/exp.zig b/lib/std/math/exp.zig index da80b201c0bd94fcdec9f30e1a68978098462255..c84d929adfe621cbdb4821495df37b2ff38b3348 100644 --- a/lib/std/math/exp.zig +++ b/lib/std/math/exp.zig @@ -14,7 +14,7 @@ const builtin = @import("builtin"); /// Special Cases: /// - exp(+inf) = +inf /// - exp(nan) = nan -pub fn exp(x: var) @TypeOf(x) { +pub fn exp(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => exp32(x), diff --git a/lib/std/math/exp2.zig b/lib/std/math/exp2.zig index 411f78918755b96171ed6eec281cb015510e9218..da391189b2084b082106274054ae4a7b8783f9a0 100644 --- a/lib/std/math/exp2.zig +++ b/lib/std/math/exp2.zig @@ -13,7 +13,7 @@ const expect = std.testing.expect; /// Special Cases: /// - exp2(+inf) = +inf /// - exp2(nan) = nan -pub fn exp2(x: var) @TypeOf(x) { +pub fn exp2(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => exp2_32(x), diff --git a/lib/std/math/expm1.zig b/lib/std/math/expm1.zig index 91752e9f8082d2cd853a16f62c3e940eb05a68c7..80cdefae20b703d590d9717de294343d264de7d8 100644 --- a/lib/std/math/expm1.zig +++ b/lib/std/math/expm1.zig @@ -18,7 +18,7 @@ const expect = std.testing.expect; /// - expm1(+inf) = +inf /// - expm1(-inf) = -1 /// - expm1(nan) = nan -pub fn expm1(x: var) @TypeOf(x) { +pub fn expm1(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => expm1_32(x), diff --git a/lib/std/math/expo2.zig b/lib/std/math/expo2.zig index e70e365f26bc9b1f5c74974ffb8ae7aa3052c89f..f404570fb6928095d708f35804385f283c86634c 100644 --- a/lib/std/math/expo2.zig +++ b/lib/std/math/expo2.zig @@ -7,7 +7,7 @@ const math = @import("../math.zig"); /// Returns exp(x) / 2 for x >= log(maxFloat(T)). -pub fn expo2(x: var) @TypeOf(x) { +pub fn expo2(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => expo2f(x), diff --git a/lib/std/math/fabs.zig b/lib/std/math/fabs.zig index a659e35ca21e8ab661ef063302ef2d44048d7acd..ca91f594fdc2467c1fa081ca0b51c81de0d9518b 100644 --- a/lib/std/math/fabs.zig +++ b/lib/std/math/fabs.zig @@ -14,7 +14,7 @@ const maxInt = std.math.maxInt; /// Special Cases: /// - fabs(+-inf) = +inf /// - fabs(nan) = nan -pub fn fabs(x: var) @TypeOf(x) { +pub fn fabs(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f16 => fabs16(x), diff --git a/lib/std/math/floor.zig b/lib/std/math/floor.zig index 565e2911a94eb74669360c00aa0a6e6a4e61802c..3a71cc7cdf5dd5f26451230570517a277e849479 100644 --- a/lib/std/math/floor.zig +++ b/lib/std/math/floor.zig @@ -15,7 +15,7 @@ const math = std.math; /// - floor(+-0) = +-0 /// - floor(+-inf) = +-inf /// - floor(nan) = nan -pub fn floor(x: var) @TypeOf(x) { +pub fn floor(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f16 => floor16(x), diff --git a/lib/std/math/frexp.zig b/lib/std/math/frexp.zig index cfdf9f838deab4b492fa865ea063ca20060ca93a..0e4558dc3705b3cfa14727f91d148d398716dd05 100644 --- a/lib/std/math/frexp.zig +++ b/lib/std/math/frexp.zig @@ -24,7 +24,7 @@ pub const frexp64_result = frexp_result(f64); /// - frexp(+-0) = +-0, 0 /// - frexp(+-inf) = +-inf, 0 /// - frexp(nan) = nan, undefined -pub fn frexp(x: var) frexp_result(@TypeOf(x)) { +pub fn frexp(x: anytype) frexp_result(@TypeOf(x)) { const T = @TypeOf(x); return switch (T) { f32 => frexp32(x), diff --git a/lib/std/math/ilogb.zig b/lib/std/math/ilogb.zig index 22e3fbaa9751d397a0a45c316f85bae466bdc83b..748cf9ea0da6101f019abf8903c51ac5c6ebd784 100644 --- a/lib/std/math/ilogb.zig +++ b/lib/std/math/ilogb.zig @@ -16,7 +16,7 @@ const minInt = std.math.minInt; /// - ilogb(+-inf) = maxInt(i32) /// - ilogb(0) = maxInt(i32) /// - ilogb(nan) = maxInt(i32) -pub fn ilogb(x: var) i32 { +pub fn ilogb(x: anytype) i32 { const T = @TypeOf(x); return switch (T) { f32 => ilogb32(x), diff --git a/lib/std/math/isfinite.zig b/lib/std/math/isfinite.zig index 26b3ce54a11059f95ee45284f74e066b31884b6f..0681eae0b755f020053a350e404f9386004a5266 100644 --- a/lib/std/math/isfinite.zig +++ b/lib/std/math/isfinite.zig @@ -4,7 +4,7 @@ const expect = std.testing.expect; const maxInt = std.math.maxInt; /// Returns whether x is a finite value. -pub fn isFinite(x: var) bool { +pub fn isFinite(x: anytype) bool { const T = @TypeOf(x); switch (T) { f16 => { diff --git a/lib/std/math/isinf.zig b/lib/std/math/isinf.zig index 6eacab52adfcdf898d628f453d02bf5b13941480..19357d89d157674c44445873cbaecbbb9ca93720 100644 --- a/lib/std/math/isinf.zig +++ b/lib/std/math/isinf.zig @@ -4,7 +4,7 @@ const expect = std.testing.expect; const maxInt = std.math.maxInt; /// Returns whether x is an infinity, ignoring sign. -pub fn isInf(x: var) bool { +pub fn isInf(x: anytype) bool { const T = @TypeOf(x); switch (T) { f16 => { @@ -30,7 +30,7 @@ pub fn isInf(x: var) bool { } /// Returns whether x is an infinity with a positive sign. -pub fn isPositiveInf(x: var) bool { +pub fn isPositiveInf(x: anytype) bool { const T = @TypeOf(x); switch (T) { f16 => { @@ -52,7 +52,7 @@ pub fn isPositiveInf(x: var) bool { } /// Returns whether x is an infinity with a negative sign. -pub fn isNegativeInf(x: var) bool { +pub fn isNegativeInf(x: anytype) bool { const T = @TypeOf(x); switch (T) { f16 => { diff --git a/lib/std/math/isnan.zig b/lib/std/math/isnan.zig index ac865f0d0c60ba7a8430bb93de3c6729f3701560..797c115d1dda5e397331b2941e573982b74c6db9 100644 --- a/lib/std/math/isnan.zig +++ b/lib/std/math/isnan.zig @@ -4,12 +4,12 @@ const expect = std.testing.expect; const maxInt = std.math.maxInt; /// Returns whether x is a nan. -pub fn isNan(x: var) bool { +pub fn isNan(x: anytype) bool { return x != x; } /// Returns whether x is a signalling nan. -pub fn isSignalNan(x: var) bool { +pub fn isSignalNan(x: anytype) bool { // Note: A signalling nan is identical to a standard nan right now but may have a different bit // representation in the future when required. return isNan(x); diff --git a/lib/std/math/isnormal.zig b/lib/std/math/isnormal.zig index 917b4ebfcf2e23602fba4bca8b9b0bd6d55a9e51..a3144f2784a2e42c38059296df7948f11a6e61ed 100644 --- a/lib/std/math/isnormal.zig +++ b/lib/std/math/isnormal.zig @@ -4,7 +4,7 @@ const expect = std.testing.expect; const maxInt = std.math.maxInt; // Returns whether x has a normalized representation (i.e. integer part of mantissa is 1). -pub fn isNormal(x: var) bool { +pub fn isNormal(x: anytype) bool { const T = @TypeOf(x); switch (T) { f16 => { diff --git a/lib/std/math/ln.zig b/lib/std/math/ln.zig index 555a786907e1960d08b7a4f68bade48a51b17851..99e54c4cc76c7dc91bd976bc2420066b6e437b0e 100644 --- a/lib/std/math/ln.zig +++ b/lib/std/math/ln.zig @@ -15,7 +15,7 @@ const expect = std.testing.expect; /// - ln(0) = -inf /// - ln(x) = nan if x < 0 /// - ln(nan) = nan -pub fn ln(x: var) @TypeOf(x) { +pub fn ln(x: anytype) @TypeOf(x) { const T = @TypeOf(x); switch (@typeInfo(T)) { .ComptimeFloat => { diff --git a/lib/std/math/log10.zig b/lib/std/math/log10.zig index 7367af28c6d72fece3fbd9ac93a9a4a3bed76977..e55bd8c1e879a643c5129bb593473e9d9ff5dcbd 100644 --- a/lib/std/math/log10.zig +++ b/lib/std/math/log10.zig @@ -16,7 +16,7 @@ const maxInt = std.math.maxInt; /// - log10(0) = -inf /// - log10(x) = nan if x < 0 /// - log10(nan) = nan -pub fn log10(x: var) @TypeOf(x) { +pub fn log10(x: anytype) @TypeOf(x) { const T = @TypeOf(x); switch (@typeInfo(T)) { .ComptimeFloat => { diff --git a/lib/std/math/log1p.zig b/lib/std/math/log1p.zig index 5e92cfdea336e5be86d11fe6cb95bfe6d53bf70b..e24ba8d84d7d3bd6a7f0e9241e4b668e3242d1e2 100644 --- a/lib/std/math/log1p.zig +++ b/lib/std/math/log1p.zig @@ -17,7 +17,7 @@ const expect = std.testing.expect; /// - log1p(-1) = -inf /// - log1p(x) = nan if x < -1 /// - log1p(nan) = nan -pub fn log1p(x: var) @TypeOf(x) { +pub fn log1p(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => log1p_32(x), diff --git a/lib/std/math/log2.zig b/lib/std/math/log2.zig index 54f8bc2baa7eacd38ae06fd7a6ea354f8682da78..95d06a2b60b80a0b9d45f6281f1ebe3affd2257f 100644 --- a/lib/std/math/log2.zig +++ b/lib/std/math/log2.zig @@ -16,7 +16,7 @@ const maxInt = std.math.maxInt; /// - log2(0) = -inf /// - log2(x) = nan if x < 0 /// - log2(nan) = nan -pub fn log2(x: var) @TypeOf(x) { +pub fn log2(x: anytype) @TypeOf(x) { const T = @TypeOf(x); switch (@typeInfo(T)) { .ComptimeFloat => { diff --git a/lib/std/math/modf.zig b/lib/std/math/modf.zig index 6fd89e3dda6c22c57f3f119231432c796d781825..5ab5318a79cd95a5f6cddb94b75ad0172cdf8a78 100644 --- a/lib/std/math/modf.zig +++ b/lib/std/math/modf.zig @@ -24,7 +24,7 @@ pub const modf64_result = modf_result(f64); /// Special Cases: /// - modf(+-inf) = +-inf, nan /// - modf(nan) = nan, nan -pub fn modf(x: var) modf_result(@TypeOf(x)) { +pub fn modf(x: anytype) modf_result(@TypeOf(x)) { const T = @TypeOf(x); return switch (T) { f32 => modf32(x), diff --git a/lib/std/math/round.zig b/lib/std/math/round.zig index 052c0f767047c40bf75932993ddd08b3413fee36..854adee4baae2f1198dfeb41f2e7d4f00515f382 100644 --- a/lib/std/math/round.zig +++ b/lib/std/math/round.zig @@ -15,7 +15,7 @@ const math = std.math; /// - round(+-0) = +-0 /// - round(+-inf) = +-inf /// - round(nan) = nan -pub fn round(x: var) @TypeOf(x) { +pub fn round(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => round32(x), diff --git a/lib/std/math/scalbn.zig b/lib/std/math/scalbn.zig index bab109f334b6667bbf1c85f0097c52adc507b78e..71a8110ce71e8f41fe2f3eac72c70cde4c4931ff 100644 --- a/lib/std/math/scalbn.zig +++ b/lib/std/math/scalbn.zig @@ -9,7 +9,7 @@ const math = std.math; const expect = std.testing.expect; /// Returns x * 2^n. -pub fn scalbn(x: var, n: i32) @TypeOf(x) { +pub fn scalbn(x: anytype, n: i32) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => scalbn32(x, n), diff --git a/lib/std/math/signbit.zig b/lib/std/math/signbit.zig index 9cb62b5042a8f2e351d5cb5370c158416dfd45ac..49397f7bd4546abba8df534711efb92c98c92e9f 100644 --- a/lib/std/math/signbit.zig +++ b/lib/std/math/signbit.zig @@ -3,7 +3,7 @@ const math = std.math; const expect = std.testing.expect; /// Returns whether x is negative or negative 0. -pub fn signbit(x: var) bool { +pub fn signbit(x: anytype) bool { const T = @TypeOf(x); return switch (T) { f16 => signbit16(x), diff --git a/lib/std/math/sin.zig b/lib/std/math/sin.zig index e88f5eeeafbb61d42417f100acb9310b35800aa2..df3b294ca6409db0c974ce954dcf8c78babcd5d8 100644 --- a/lib/std/math/sin.zig +++ b/lib/std/math/sin.zig @@ -14,7 +14,7 @@ const expect = std.testing.expect; /// - sin(+-0) = +-0 /// - sin(+-inf) = nan /// - sin(nan) = nan -pub fn sin(x: var) @TypeOf(x) { +pub fn sin(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => sin_(T, x), diff --git a/lib/std/math/sinh.zig b/lib/std/math/sinh.zig index 0e2cb5a3d544ca9fd36d99a6a3ca8a7ada20f4d6..26e0e05f38b3886324b7c26572bb502e13736efd 100644 --- a/lib/std/math/sinh.zig +++ b/lib/std/math/sinh.zig @@ -17,7 +17,7 @@ const maxInt = std.math.maxInt; /// - sinh(+-0) = +-0 /// - sinh(+-inf) = +-inf /// - sinh(nan) = nan -pub fn sinh(x: var) @TypeOf(x) { +pub fn sinh(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => sinh32(x), diff --git a/lib/std/math/sqrt.zig b/lib/std/math/sqrt.zig index 097b0152f72ef8602790efb3a84df0f4ef4b5a37..2f0d2514324cb072f62c93cb04e123fd3a7c2746 100644 --- a/lib/std/math/sqrt.zig +++ b/lib/std/math/sqrt.zig @@ -13,7 +13,7 @@ const maxInt = std.math.maxInt; /// - sqrt(x) = nan if x < 0 /// - sqrt(nan) = nan /// TODO Decide if all this logic should be implemented directly in the @sqrt bultin function. -pub fn sqrt(x: var) Sqrt(@TypeOf(x)) { +pub fn sqrt(x: anytype) Sqrt(@TypeOf(x)) { const T = @TypeOf(x); switch (@typeInfo(T)) { .Float, .ComptimeFloat => return @sqrt(x), diff --git a/lib/std/math/tan.zig b/lib/std/math/tan.zig index 86f473f4487bf65781c03425029953e6f9dc44cc..2cd5a407dfb280366e33dd7d45e53af466e43c8e 100644 --- a/lib/std/math/tan.zig +++ b/lib/std/math/tan.zig @@ -14,7 +14,7 @@ const expect = std.testing.expect; /// - tan(+-0) = +-0 /// - tan(+-inf) = nan /// - tan(nan) = nan -pub fn tan(x: var) @TypeOf(x) { +pub fn tan(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => tan_(f32, x), diff --git a/lib/std/math/tanh.zig b/lib/std/math/tanh.zig index 1cad3997294bdcff8149d659cc16e6e56269bb9e..7697db5271769602c8bf105816db5675580b99c8 100644 --- a/lib/std/math/tanh.zig +++ b/lib/std/math/tanh.zig @@ -17,7 +17,7 @@ const maxInt = std.math.maxInt; /// - sinh(+-0) = +-0 /// - sinh(+-inf) = +-1 /// - sinh(nan) = nan -pub fn tanh(x: var) @TypeOf(x) { +pub fn tanh(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => tanh32(x), diff --git a/lib/std/math/trunc.zig b/lib/std/math/trunc.zig index cdd2fa3c6b0b3009e8776597f11fd726951a694d..df24b77111184fae8a796f106c3f83f4e05caed2 100644 --- a/lib/std/math/trunc.zig +++ b/lib/std/math/trunc.zig @@ -15,7 +15,7 @@ const maxInt = std.math.maxInt; /// - trunc(+-0) = +-0 /// - trunc(+-inf) = +-inf /// - trunc(nan) = nan -pub fn trunc(x: var) @TypeOf(x) { +pub fn trunc(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { f32 => trunc32(x), diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 08ecc5167fac65a81a35660f5bc953af236bc20d..82831787a794a9a669ca881b5ec691ce76a3be71 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -122,7 +122,7 @@ pub const Allocator = struct { assert(resized_len >= new_byte_count); @memset(old_mem.ptr + new_byte_count, undefined, resized_len - new_byte_count); return old_mem.ptr[0..resized_len]; - } else |_| { } + } else |_| {} } if (new_byte_count <= old_mem.len and new_alignment <= old_alignment) { return error.OutOfMemory; @@ -156,7 +156,7 @@ pub const Allocator = struct { /// `ptr` should be the return value of `create`, or otherwise /// have the same address and alignment property. - pub fn destroy(self: *Allocator, ptr: var) void { + pub fn destroy(self: *Allocator, ptr: anytype) void { const T = @TypeOf(ptr).Child; if (@sizeOf(T) == 0) return; const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr)); @@ -225,7 +225,7 @@ pub const Allocator = struct { return self.allocAdvanced(T, alignment, n, .exact); } - const Exact = enum {exact,at_least}; + const Exact = enum { exact, at_least }; pub fn allocAdvanced( self: *Allocator, comptime T: type, @@ -272,7 +272,7 @@ pub const Allocator = struct { /// in `std.ArrayList.shrink`. /// If you need guaranteed success, call `shrink`. /// If `new_n` is 0, this is the same as `free` and it always succeeds. - pub fn realloc(self: *Allocator, old_mem: var, new_n: usize) t: { + pub fn realloc(self: *Allocator, old_mem: anytype, new_n: usize) t: { const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; break :t Error![]align(Slice.alignment) Slice.child; } { @@ -280,7 +280,7 @@ pub const Allocator = struct { return self.reallocAdvanced(old_mem, old_alignment, new_n, .exact); } - pub fn reallocAtLeast(self: *Allocator, old_mem: var, new_n: usize) t: { + pub fn reallocAtLeast(self: *Allocator, old_mem: anytype, new_n: usize) t: { const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; break :t Error![]align(Slice.alignment) Slice.child; } { @@ -291,7 +291,7 @@ pub const Allocator = struct { // Deprecated: use `reallocAdvanced` pub fn alignedRealloc( self: *Allocator, - old_mem: var, + old_mem: anytype, comptime new_alignment: u29, new_n: usize, ) Error![]align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child { @@ -303,7 +303,7 @@ pub const Allocator = struct { /// allocation. pub fn reallocAdvanced( self: *Allocator, - old_mem: var, + old_mem: anytype, comptime new_alignment: u29, new_n: usize, exact: Exact, @@ -321,8 +321,7 @@ pub const Allocator = struct { const old_byte_slice = mem.sliceAsBytes(old_mem); const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory; // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure - const new_byte_slice = try self.reallocBytes(old_byte_slice, Slice.alignment, byte_count, new_alignment, - if (exact == .exact) @as(u29, 0) else @sizeOf(T)); + const new_byte_slice = try self.reallocBytes(old_byte_slice, Slice.alignment, byte_count, new_alignment, if (exact == .exact) @as(u29, 0) else @sizeOf(T)); return mem.bytesAsSlice(T, @alignCast(new_alignment, new_byte_slice)); } @@ -331,7 +330,7 @@ pub const Allocator = struct { /// Shrink always succeeds, and `new_n` must be <= `old_mem.len`. /// Returned slice has same alignment as old_mem. /// Shrinking to 0 is the same as calling `free`. - pub fn shrink(self: *Allocator, old_mem: var, new_n: usize) t: { + pub fn shrink(self: *Allocator, old_mem: anytype, new_n: usize) t: { const Slice = @typeInfo(@TypeOf(old_mem)).Pointer; break :t []align(Slice.alignment) Slice.child; } { @@ -344,7 +343,7 @@ pub const Allocator = struct { /// allocation. pub fn alignedShrink( self: *Allocator, - old_mem: var, + old_mem: anytype, comptime new_alignment: u29, new_n: usize, ) []align(new_alignment) @typeInfo(@TypeOf(old_mem)).Pointer.child { @@ -368,7 +367,7 @@ pub const Allocator = struct { /// Free an array allocated with `alloc`. To free a single item, /// see `destroy`. - pub fn free(self: *Allocator, memory: var) void { + pub fn free(self: *Allocator, memory: anytype) void { const Slice = @typeInfo(@TypeOf(memory)).Pointer; const bytes = mem.sliceAsBytes(memory); const bytes_len = bytes.len + if (Slice.sentinel != null) @sizeOf(Slice.child) else 0; @@ -396,67 +395,69 @@ pub const Allocator = struct { /// Detects and asserts if the std.mem.Allocator interface is violated by the caller /// or the allocator. -pub fn ValidationAllocator(comptime T: type) type { return struct { - const Self = @This(); - allocator: Allocator, - underlying_allocator: T, - pub fn init(allocator: T) @This() { - return .{ - .allocator = .{ - .allocFn = alloc, - .resizeFn = resize, - }, - .underlying_allocator = allocator, +pub fn ValidationAllocator(comptime T: type) type { + return struct { + const Self = @This(); + allocator: Allocator, + underlying_allocator: T, + pub fn init(allocator: T) @This() { + return .{ + .allocator = .{ + .allocFn = alloc, + .resizeFn = resize, + }, + .underlying_allocator = allocator, + }; + } + fn getUnderlyingAllocatorPtr(self: *@This()) *Allocator { + if (T == *Allocator) return self.underlying_allocator; + if (*T == *Allocator) return &self.underlying_allocator; + return &self.underlying_allocator.allocator; + } + pub fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 { + assert(n > 0); + assert(mem.isValidAlign(ptr_align)); + if (len_align != 0) { + assert(mem.isAlignedAnyAlign(n, len_align)); + assert(n >= len_align); + } + + const self = @fieldParentPtr(@This(), "allocator", allocator); + const result = try self.getUnderlyingAllocatorPtr().callAllocFn(n, ptr_align, len_align); + assert(mem.isAligned(@ptrToInt(result.ptr), ptr_align)); + if (len_align == 0) { + assert(result.len == n); + } else { + assert(result.len >= n); + assert(mem.isAlignedAnyAlign(result.len, len_align)); + } + return result; + } + pub fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize { + assert(buf.len > 0); + if (len_align != 0) { + assert(mem.isAlignedAnyAlign(new_len, len_align)); + assert(new_len >= len_align); + } + const self = @fieldParentPtr(@This(), "allocator", allocator); + const result = try self.getUnderlyingAllocatorPtr().callResizeFn(buf, new_len, len_align); + if (len_align == 0) { + assert(result == new_len); + } else { + assert(result >= new_len); + assert(mem.isAlignedAnyAlign(result, len_align)); + } + return result; + } + pub usingnamespace if (T == *Allocator or !@hasDecl(T, "reset")) struct {} else struct { + pub fn reset(self: *Self) void { + self.underlying_allocator.reset(); + } }; - } - fn getUnderlyingAllocatorPtr(self: *@This()) *Allocator { - if (T == *Allocator) return self.underlying_allocator; - if (*T == *Allocator) return &self.underlying_allocator; - return &self.underlying_allocator.allocator; - } - pub fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) Allocator.Error![]u8 { - assert(n > 0); - assert(mem.isValidAlign(ptr_align)); - if (len_align != 0) { - assert(mem.isAlignedAnyAlign(n, len_align)); - assert(n >= len_align); - } - - const self = @fieldParentPtr(@This(), "allocator", allocator); - const result = try self.getUnderlyingAllocatorPtr().callAllocFn(n, ptr_align, len_align); - assert(mem.isAligned(@ptrToInt(result.ptr), ptr_align)); - if (len_align == 0) { - assert(result.len == n); - } else { - assert(result.len >= n); - assert(mem.isAlignedAnyAlign(result.len, len_align)); - } - return result; - } - pub fn resize(allocator: *Allocator, buf: []u8, new_len: usize, len_align: u29) Allocator.Error!usize { - assert(buf.len > 0); - if (len_align != 0) { - assert(mem.isAlignedAnyAlign(new_len, len_align)); - assert(new_len >= len_align); - } - const self = @fieldParentPtr(@This(), "allocator", allocator); - const result = try self.getUnderlyingAllocatorPtr().callResizeFn(buf, new_len, len_align); - if (len_align == 0) { - assert(result == new_len); - } else { - assert(result >= new_len); - assert(mem.isAlignedAnyAlign(result, len_align)); - } - return result; - } - pub usingnamespace if (T == *Allocator or !@hasDecl(T, "reset")) struct {} else struct { - pub fn reset(self: *Self) void { - self.underlying_allocator.reset(); - } }; -};} +} -pub fn validationWrap(allocator: var) ValidationAllocator(@TypeOf(allocator)) { +pub fn validationWrap(allocator: anytype) ValidationAllocator(@TypeOf(allocator)) { return ValidationAllocator(@TypeOf(allocator)).init(allocator); } @@ -465,14 +466,14 @@ pub fn validationWrap(allocator: var) ValidationAllocator(@TypeOf(allocator)) { /// than the `len` that was requsted. This function should only be used by allocators /// that are unaffected by `len_align`. pub fn alignAllocLen(full_len: usize, alloc_len: usize, len_align: u29) usize { - assert(alloc_len > 0); - assert(alloc_len >= len_align); - assert(full_len >= alloc_len); - if (len_align == 0) - return alloc_len; - const adjusted = alignBackwardAnyAlign(full_len, len_align); - assert(adjusted >= alloc_len); - return adjusted; + assert(alloc_len > 0); + assert(alloc_len >= len_align); + assert(full_len >= alloc_len); + if (len_align == 0) + return alloc_len; + const adjusted = alignBackwardAnyAlign(full_len, len_align); + assert(adjusted >= alloc_len); + return adjusted; } var failAllocator = Allocator{ @@ -695,7 +696,7 @@ test "mem.secureZero" { /// Initializes all fields of the struct with their default value, or zero values if no default value is present. /// If the field is present in the provided initial values, it will have that value instead. /// Structs are initialized recursively. -pub fn zeroInit(comptime T: type, init: var) T { +pub fn zeroInit(comptime T: type, init: anytype) T { comptime const Init = @TypeOf(init); switch (@typeInfo(T)) { @@ -895,7 +896,7 @@ test "Span" { /// /// When there is both a sentinel and an array length or slice length, the /// length value is used instead of the sentinel. -pub fn span(ptr: var) Span(@TypeOf(ptr)) { +pub fn span(ptr: anytype) Span(@TypeOf(ptr)) { if (@typeInfo(@TypeOf(ptr)) == .Optional) { if (ptr) |non_null| { return span(non_null); @@ -923,7 +924,7 @@ test "span" { /// Same as `span`, except when there is both a sentinel and an array /// length or slice length, scans the memory for the sentinel value /// rather than using the length. -pub fn spanZ(ptr: var) Span(@TypeOf(ptr)) { +pub fn spanZ(ptr: anytype) Span(@TypeOf(ptr)) { if (@typeInfo(@TypeOf(ptr)) == .Optional) { if (ptr) |non_null| { return spanZ(non_null); @@ -952,7 +953,7 @@ test "spanZ" { /// or a slice, and returns the length. /// In the case of a sentinel-terminated array, it uses the array length. /// For C pointers it assumes it is a pointer-to-many with a 0 sentinel. -pub fn len(value: var) usize { +pub fn len(value: anytype) usize { return switch (@typeInfo(@TypeOf(value))) { .Array => |info| info.len, .Vector => |info| info.len, @@ -1000,7 +1001,7 @@ test "len" { /// In the case of a sentinel-terminated array, it scans the array /// for a sentinel and uses that for the length, rather than using the array length. /// For C pointers it assumes it is a pointer-to-many with a 0 sentinel. -pub fn lenZ(ptr: var) usize { +pub fn lenZ(ptr: anytype) usize { return switch (@typeInfo(@TypeOf(ptr))) { .Array => |info| if (info.sentinel) |sentinel| indexOfSentinel(info.child, sentinel, &ptr) @@ -2031,7 +2032,7 @@ fn AsBytesReturnType(comptime P: type) type { } /// Given a pointer to a single item, returns a slice of the underlying bytes, preserving constness. -pub fn asBytes(ptr: var) AsBytesReturnType(@TypeOf(ptr)) { +pub fn asBytes(ptr: anytype) AsBytesReturnType(@TypeOf(ptr)) { const P = @TypeOf(ptr); return @ptrCast(AsBytesReturnType(P), ptr); } @@ -2071,7 +2072,7 @@ test "asBytes" { } ///Given any value, returns a copy of its bytes in an array. -pub fn toBytes(value: var) [@sizeOf(@TypeOf(value))]u8 { +pub fn toBytes(value: anytype) [@sizeOf(@TypeOf(value))]u8 { return asBytes(&value).*; } @@ -2106,7 +2107,7 @@ fn BytesAsValueReturnType(comptime T: type, comptime B: type) type { ///Given a pointer to an array of bytes, returns a pointer to a value of the specified type /// backed by those bytes, preserving constness. -pub fn bytesAsValue(comptime T: type, bytes: var) BytesAsValueReturnType(T, @TypeOf(bytes)) { +pub fn bytesAsValue(comptime T: type, bytes: anytype) BytesAsValueReturnType(T, @TypeOf(bytes)) { return @ptrCast(BytesAsValueReturnType(T, @TypeOf(bytes)), bytes); } @@ -2149,7 +2150,7 @@ test "bytesAsValue" { ///Given a pointer to an array of bytes, returns a value of the specified type backed by a /// copy of those bytes. -pub fn bytesToValue(comptime T: type, bytes: var) T { +pub fn bytesToValue(comptime T: type, bytes: anytype) T { return bytesAsValue(T, bytes).*; } test "bytesToValue" { @@ -2177,7 +2178,7 @@ fn BytesAsSliceReturnType(comptime T: type, comptime bytesType: type) type { return if (trait.isConstPtr(bytesType)) []align(alignment) const T else []align(alignment) T; } -pub fn bytesAsSlice(comptime T: type, bytes: var) BytesAsSliceReturnType(T, @TypeOf(bytes)) { +pub fn bytesAsSlice(comptime T: type, bytes: anytype) BytesAsSliceReturnType(T, @TypeOf(bytes)) { // let's not give an undefined pointer to @ptrCast // it may be equal to zero and fail a null check if (bytes.len == 0) { @@ -2256,7 +2257,7 @@ fn SliceAsBytesReturnType(comptime sliceType: type) type { return if (trait.isConstPtr(sliceType)) []align(alignment) const u8 else []align(alignment) u8; } -pub fn sliceAsBytes(slice: var) SliceAsBytesReturnType(@TypeOf(slice)) { +pub fn sliceAsBytes(slice: anytype) SliceAsBytesReturnType(@TypeOf(slice)) { const Slice = @TypeOf(slice); // let's not give an undefined pointer to @ptrCast diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 6c10941aa774e86be55655233cd8b2c091ab9e7f..2827cfecf419452eca8387fe07c8d5b781111cfb 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -9,7 +9,7 @@ pub const trait = @import("meta/trait.zig"); const TypeInfo = builtin.TypeInfo; -pub fn tagName(v: var) []const u8 { +pub fn tagName(v: anytype) []const u8 { const T = @TypeOf(v); switch (@typeInfo(T)) { .ErrorSet => return @errorName(v), @@ -430,7 +430,7 @@ test "std.meta.TagType" { } ///Returns the active tag of a tagged union -pub fn activeTag(u: var) @TagType(@TypeOf(u)) { +pub fn activeTag(u: anytype) @TagType(@TypeOf(u)) { const T = @TypeOf(u); return @as(@TagType(T), u); } @@ -480,7 +480,7 @@ test "std.meta.TagPayloadType" { /// Compares two of any type for equality. Containers are compared on a field-by-field basis, /// where possible. Pointers are not followed. -pub fn eql(a: var, b: @TypeOf(a)) bool { +pub fn eql(a: anytype, b: @TypeOf(a)) bool { const T = @TypeOf(a); switch (@typeInfo(T)) { @@ -627,7 +627,7 @@ test "intToEnum with error return" { pub const IntToEnumError = error{InvalidEnumTag}; -pub fn intToEnum(comptime Tag: type, tag_int: var) IntToEnumError!Tag { +pub fn intToEnum(comptime Tag: type, tag_int: anytype) IntToEnumError!Tag { inline for (@typeInfo(Tag).Enum.fields) |f| { const this_tag_value = @field(Tag, f.name); if (tag_int == @enumToInt(this_tag_value)) { @@ -696,7 +696,7 @@ pub fn Vector(comptime len: u32, comptime child: type) type { /// Given a type and value, cast the value to the type as c would. /// This is for translate-c and is not intended for general use. -pub fn cast(comptime DestType: type, target: var) DestType { +pub fn cast(comptime DestType: type, target: anytype) DestType { const TargetType = @TypeOf(target); switch (@typeInfo(DestType)) { .Pointer => { diff --git a/lib/std/meta/trait.zig b/lib/std/meta/trait.zig index 11aa8457ee78bb6b1cae8fceb8c9dbb78ced316f..5cea0ecb9a176b853e2e5c54a6fa47012d99ec61 100644 --- a/lib/std/meta/trait.zig +++ b/lib/std/meta/trait.zig @@ -9,7 +9,7 @@ const meta = @import("../meta.zig"); pub const TraitFn = fn (type) bool; -pub fn multiTrait(comptime traits: var) TraitFn { +pub fn multiTrait(comptime traits: anytype) TraitFn { const Closure = struct { pub fn trait(comptime T: type) bool { inline for (traits) |t| @@ -342,7 +342,7 @@ test "std.meta.trait.isContainer" { testing.expect(!isContainer(u8)); } -pub fn hasDecls(comptime T: type, comptime names: var) bool { +pub fn hasDecls(comptime T: type, comptime names: anytype) bool { inline for (names) |name| { if (!@hasDecl(T, name)) return false; @@ -368,7 +368,7 @@ test "std.meta.trait.hasDecls" { testing.expect(!hasDecls(TestStruct2, tuple)); } -pub fn hasFields(comptime T: type, comptime names: var) bool { +pub fn hasFields(comptime T: type, comptime names: anytype) bool { inline for (names) |name| { if (!@hasField(T, name)) return false; @@ -394,7 +394,7 @@ test "std.meta.trait.hasFields" { testing.expect(!hasFields(TestStruct2, .{ "a", "b", "useless" })); } -pub fn hasFunctions(comptime T: type, comptime names: var) bool { +pub fn hasFunctions(comptime T: type, comptime names: anytype) bool { inline for (names) |name| { if (!hasFn(name)(T)) return false; diff --git a/lib/std/net.zig b/lib/std/net.zig index a9dcf53f9218ece06c1c9fefd95dcfc7093a339e..71bab383fa85701ff33bb19059b0ff5d5365ae3d 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -427,7 +427,7 @@ pub const Address = extern union { self: Address, comptime fmt: []const u8, options: std.fmt.FormatOptions, - out_stream: var, + out_stream: anytype, ) !void { switch (self.any.family) { os.AF_INET => { @@ -1404,8 +1404,8 @@ fn resMSendRc( fn dnsParse( r: []const u8, - ctx: var, - comptime callback: var, + ctx: anytype, + comptime callback: anytype, ) !void { // This implementation is ported from musl libc. // A more idiomatic "ziggy" implementation would be welcome. diff --git a/lib/std/os.zig b/lib/std/os.zig index 1e1049ae51cee701038f1d408f7e9632d3107c25..dfb47208ca9cecb04dd46de155279b9bf7928fa0 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4068,7 +4068,7 @@ pub fn nanosleep(seconds: u64, nanoseconds: u64) void { } pub fn dl_iterate_phdr( - context: var, + context: anytype, comptime Error: type, comptime callback: fn (info: *dl_phdr_info, size: usize, context: @TypeOf(context)) Error!void, ) Error!void { diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 1d2f88794dc7b33acd333a166b0b43edfa45c67a..c037075cd81d3ca69e3566a7a4a7ed1fdf5a0b1b 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -28,7 +28,7 @@ pub const Guid = extern struct { self: @This(), comptime f: []const u8, options: std.fmt.FormatOptions, - out_stream: var, + out_stream: anytype, ) Errors!void { if (f.len == 0) { return std.fmt.format(out_stream, "{x:0>8}-{x:0>4}-{x:0>4}-{x:0>2}{x:0>2}-{x:0>12}", .{ diff --git a/lib/std/progress.zig b/lib/std/progress.zig index d80f8c4423049544e76c826037905e2bc36f5714..b81e81aa2c7a5b4b0adbc35fc6c01966c91781dc 100644 --- a/lib/std/progress.zig +++ b/lib/std/progress.zig @@ -224,7 +224,7 @@ pub const Progress = struct { self.prev_refresh_timestamp = self.timer.read(); } - pub fn log(self: *Progress, comptime format: []const u8, args: var) void { + pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void { const file = self.terminal orelse return; self.refresh(); file.outStream().print(format, args) catch { @@ -234,7 +234,7 @@ pub const Progress = struct { self.columns_written = 0; } - fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: var) void { + fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void { if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| { const amt = written.len; end.* += amt; diff --git a/lib/std/segmented_list.zig b/lib/std/segmented_list.zig index d087e480f6f126392cd76a17f22d97973c01a0ef..d39fe3e2397b087d6c24e75861aee92c3c8f8213 100644 --- a/lib/std/segmented_list.zig +++ b/lib/std/segmented_list.zig @@ -122,7 +122,7 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type self.* = undefined; } - pub fn at(self: var, i: usize) AtType(@TypeOf(self)) { + pub fn at(self: anytype, i: usize) AtType(@TypeOf(self)) { assert(i < self.len); return self.uncheckedAt(i); } @@ -241,7 +241,7 @@ pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type } } - pub fn uncheckedAt(self: var, index: usize) AtType(@TypeOf(self)) { + pub fn uncheckedAt(self: anytype, index: usize) AtType(@TypeOf(self)) { if (index < prealloc_item_count) { return &self.prealloc_segment[index]; } diff --git a/lib/std/sort.zig b/lib/std/sort.zig index cb6162e9b07db2e08c1e404ed088722eb9fedb95..464054e4a50c7c91b48bd25d9d393ae258102f37 100644 --- a/lib/std/sort.zig +++ b/lib/std/sort.zig @@ -9,7 +9,7 @@ pub fn binarySearch( comptime T: type, key: T, items: []const T, - context: var, + context: anytype, comptime compareFn: fn (context: @TypeOf(context), lhs: T, rhs: T) math.Order, ) ?usize { var left: usize = 0; @@ -76,7 +76,7 @@ test "binarySearch" { pub fn insertionSort( comptime T: type, items: []T, - context: var, + context: anytype, comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool, ) void { var i: usize = 1; @@ -182,7 +182,7 @@ const Pull = struct { pub fn sort( comptime T: type, items: []T, - context: var, + context: anytype, comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool, ) void { // Implementation ported from https://github.com/BonzaiThePenguin/WikiSort/blob/master/WikiSort.c @@ -813,7 +813,7 @@ fn mergeInPlace( items: []T, A_arg: Range, B_arg: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, ) void { if (A_arg.length() == 0 or B_arg.length() == 0) return; @@ -862,7 +862,7 @@ fn mergeInternal( items: []T, A: Range, B: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, buffer: Range, ) void { @@ -906,7 +906,7 @@ fn findFirstForward( items: []T, value: T, range: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, unique: usize, ) usize { @@ -928,7 +928,7 @@ fn findFirstBackward( items: []T, value: T, range: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, unique: usize, ) usize { @@ -950,7 +950,7 @@ fn findLastForward( items: []T, value: T, range: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, unique: usize, ) usize { @@ -972,7 +972,7 @@ fn findLastBackward( items: []T, value: T, range: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, unique: usize, ) usize { @@ -994,7 +994,7 @@ fn binaryFirst( items: []T, value: T, range: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, ) usize { var curr = range.start; @@ -1017,7 +1017,7 @@ fn binaryLast( items: []T, value: T, range: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, ) usize { var curr = range.start; @@ -1040,7 +1040,7 @@ fn mergeInto( from: []T, A: Range, B: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, into: []T, ) void { @@ -1078,7 +1078,7 @@ fn mergeExternal( items: []T, A: Range, B: Range, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), T, T) bool, cache: []T, ) void { @@ -1112,7 +1112,7 @@ fn mergeExternal( fn swap( comptime T: type, items: []T, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool, order: *[8]u8, x: usize, @@ -1358,7 +1358,7 @@ fn fuzzTest(rng: *std.rand.Random) !void { pub fn argMin( comptime T: type, items: []const T, - context: var, + context: anytype, comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool, ) ?usize { if (items.len == 0) { @@ -1390,7 +1390,7 @@ test "argMin" { pub fn min( comptime T: type, items: []const T, - context: var, + context: anytype, comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool, ) ?T { const i = argMin(T, items, context, lessThan) orelse return null; @@ -1410,7 +1410,7 @@ test "min" { pub fn argMax( comptime T: type, items: []const T, - context: var, + context: anytype, comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool, ) ?usize { if (items.len == 0) { @@ -1442,7 +1442,7 @@ test "argMax" { pub fn max( comptime T: type, items: []const T, - context: var, + context: anytype, comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool, ) ?T { const i = argMax(T, items, context, lessThan) orelse return null; @@ -1462,7 +1462,7 @@ test "max" { pub fn isSorted( comptime T: type, items: []const T, - context: var, + context: anytype, comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool, ) bool { var i: usize = 1; diff --git a/lib/std/special/build_runner.zig b/lib/std/special/build_runner.zig index 1c88f98e6e784798d387a9cf447080dbbb852d8c..83c181e8413414b8cc14d5f4761e9daa31a4e7da 100644 --- a/lib/std/special/build_runner.zig +++ b/lib/std/special/build_runner.zig @@ -135,7 +135,7 @@ fn runBuild(builder: *Builder) anyerror!void { } } -fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void { +fn usage(builder: *Builder, already_ran_build: bool, out_stream: anytype) !void { // run the build script to collect the options if (!already_ran_build) { builder.setInstallPrefix(null); @@ -202,7 +202,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void { ); } -fn usageAndErr(builder: *Builder, already_ran_build: bool, out_stream: var) void { +fn usageAndErr(builder: *Builder, already_ran_build: bool, out_stream: anytype) void { usage(builder, already_ran_build, out_stream) catch {}; process.exit(1); } diff --git a/lib/std/special/test_runner.zig b/lib/std/special/test_runner.zig index fd8c068f0527e3ed54a70a0cb27dc1477fb381fb..301457dde0a1734f46bc4412c12afbb806e97a9b 100644 --- a/lib/std/special/test_runner.zig +++ b/lib/std/special/test_runner.zig @@ -79,9 +79,9 @@ pub fn log( comptime message_level: std.log.Level, comptime scope: @Type(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { if (@enumToInt(message_level) <= @enumToInt(std.testing.log_level)) { - std.debug.print("[{}] ({}): " ++ format, .{@tagName(scope), @tagName(message_level)} ++ args); + std.debug.print("[{}] ({}): " ++ format, .{ @tagName(scope), @tagName(message_level) } ++ args); } } diff --git a/lib/std/target.zig b/lib/std/target.zig index 110b7a088f50a8a643a9fad2e7232837e12b6239..0b95e8f75a725a5b8c173176bc0ff165fe4d45ac 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -108,23 +108,16 @@ pub const Target = struct { self: WindowsVersion, comptime fmt: []const u8, options: std.fmt.FormatOptions, - out_stream: var, + out_stream: anytype, ) !void { - if (fmt.len > 0 and fmt[0] == 's') { - if ( - @enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.win10_19h1) - ) { + if (fmt.len > 0 and fmt[0] == 's') { + if (@enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.win10_19h1)) { try std.fmt.format(out_stream, ".{}", .{@tagName(self)}); } else { - try std.fmt.format(out_stream, - "@intToEnum(Target.Os.WindowsVersion, {})", - .{ @enumToInt(self) } - ); + try std.fmt.format(out_stream, "@intToEnum(Target.Os.WindowsVersion, {})", .{@enumToInt(self)}); } } else { - if ( - @enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.win10_19h1) - ) { + if (@enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.win10_19h1)) { try std.fmt.format(out_stream, "WindowsVersion.{}", .{@tagName(self)}); } else { try std.fmt.format(out_stream, "WindowsVersion(", .{@typeName(@This())}); @@ -1189,7 +1182,7 @@ pub const Target = struct { pub fn standardDynamicLinkerPath(self: Target) DynamicLinker { var result: DynamicLinker = .{}; const S = struct { - fn print(r: *DynamicLinker, comptime fmt: []const u8, args: var) DynamicLinker { + fn print(r: *DynamicLinker, comptime fmt: []const u8, args: anytype) DynamicLinker { r.max_byte = @intCast(u8, (std.fmt.bufPrint(&r.buffer, fmt, args) catch unreachable).len - 1); return r.*; } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index bdaf759d626cbe1072cfc45163c568c9c9eda97a..44c221d76ae9e02caf7c6dd57286a693cbc46d12 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -19,7 +19,7 @@ pub var log_level = std.log.Level.warn; /// This function is intended to be used only in tests. It prints diagnostics to stderr /// and then aborts when actual_error_union is not expected_error. -pub fn expectError(expected_error: anyerror, actual_error_union: var) void { +pub fn expectError(expected_error: anyerror, actual_error_union: anytype) void { if (actual_error_union) |actual_payload| { std.debug.panic("expected error.{}, found {}", .{ @errorName(expected_error), actual_payload }); } else |actual_error| { @@ -36,7 +36,7 @@ pub fn expectError(expected_error: anyerror, actual_error_union: var) void { /// equal, prints diagnostics to stderr to show exactly how they are not equal, /// then aborts. /// The types must match exactly. -pub fn expectEqual(expected: var, actual: @TypeOf(expected)) void { +pub fn expectEqual(expected: anytype, actual: @TypeOf(expected)) void { switch (@typeInfo(@TypeOf(actual))) { .NoReturn, .BoundFn, diff --git a/lib/std/thread.zig b/lib/std/thread.zig index d07c41c5b0d2d473522cf5980d848294b7683965..3d20f54558d46f0aeabbd2690ed2e9d7a2faa4ae 100644 --- a/lib/std/thread.zig +++ b/lib/std/thread.zig @@ -143,7 +143,7 @@ pub const Thread = struct { /// fn startFn(@TypeOf(context)) T /// where T is u8, noreturn, void, or !void /// caller must call wait on the returned thread - pub fn spawn(context: var, comptime startFn: var) SpawnError!*Thread { + pub fn spawn(context: anytype, comptime startFn: anytype) SpawnError!*Thread { if (builtin.single_threaded) @compileError("cannot spawn thread when building in single-threaded mode"); // TODO compile-time call graph analysis to determine stack upper bound // https://github.com/ziglang/zig/issues/157 diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index e95a8855af765ed2bfa09121bae944b3b5f8cbc1..03153f541a02ae2db6285f6c2a28dc60d5be6dc4 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -29,7 +29,7 @@ pub const Tree = struct { self.arena.promote(self.gpa).deinit(); } - pub fn renderError(self: *Tree, parse_error: *const Error, stream: var) !void { + pub fn renderError(self: *Tree, parse_error: *const Error, stream: anytype) !void { return parse_error.render(self.token_ids, stream); } @@ -167,7 +167,7 @@ pub const Error = union(enum) { DeclBetweenFields: DeclBetweenFields, InvalidAnd: InvalidAnd, - pub fn render(self: *const Error, tokens: []const Token.Id, stream: var) !void { + pub fn render(self: *const Error, tokens: []const Token.Id, stream: anytype) !void { switch (self.*) { .InvalidToken => |*x| return x.render(tokens, stream), .ExpectedContainerMembers => |*x| return x.render(tokens, stream), @@ -322,7 +322,7 @@ pub const Error = union(enum) { pub const ExpectedCall = struct { node: *Node, - pub fn render(self: *const ExpectedCall, tokens: []const Token.Id, stream: var) !void { + pub fn render(self: *const ExpectedCall, tokens: []const Token.Id, stream: anytype) !void { return stream.print("expected " ++ @tagName(Node.Id.Call) ++ ", found {}", .{ @tagName(self.node.id), }); @@ -332,7 +332,7 @@ pub const Error = union(enum) { pub const ExpectedCallOrFnProto = struct { node: *Node, - pub fn render(self: *const ExpectedCallOrFnProto, tokens: []const Token.Id, stream: var) !void { + pub fn render(self: *const ExpectedCallOrFnProto, tokens: []const Token.Id, stream: anytype) !void { return stream.print("expected " ++ @tagName(Node.Id.Call) ++ " or " ++ @tagName(Node.Id.FnProto) ++ ", found {}", .{@tagName(self.node.id)}); } @@ -342,7 +342,7 @@ pub const Error = union(enum) { token: TokenIndex, expected_id: Token.Id, - pub fn render(self: *const ExpectedToken, tokens: []const Token.Id, stream: var) !void { + pub fn render(self: *const ExpectedToken, tokens: []const Token.Id, stream: anytype) !void { const found_token = tokens[self.token]; switch (found_token) { .Invalid => { @@ -360,7 +360,7 @@ pub const Error = union(enum) { token: TokenIndex, end_id: Token.Id, - pub fn render(self: *const ExpectedCommaOrEnd, tokens: []const Token.Id, stream: var) !void { + pub fn render(self: *const ExpectedCommaOrEnd, tokens: []const Token.Id, stream: anytype) !void { const actual_token = tokens[self.token]; return stream.print("expected ',' or '{}', found '{}'", .{ self.end_id.symbol(), @@ -375,7 +375,7 @@ pub const Error = union(enum) { token: TokenIndex, - pub fn render(self: *const ThisError, tokens: []const Token.Id, stream: var) !void { + pub fn render(self: *const ThisError, tokens: []const Token.Id, stream: anytype) !void { const actual_token = tokens[self.token]; return stream.print(msg, .{actual_token.symbol()}); } @@ -388,7 +388,7 @@ pub const Error = union(enum) { token: TokenIndex, - pub fn render(self: *const ThisError, tokens: []const Token.Id, stream: var) !void { + pub fn render(self: *const ThisError, tokens: []const Token.Id, stream: anytype) !void { return stream.writeAll(msg); } }; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 4f8eff3c68d5178a7e021bafe94a59d9521c163e..29b60c0ceeb9368c269a6655feb5bbf5cd9a9f37 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -2955,7 +2955,7 @@ const Parser = struct { const NodeParseFn = fn (p: *Parser) Error!?*Node; - fn ListParseFn(comptime E: type, comptime nodeParseFn: var) ParseFn([]E) { + fn ListParseFn(comptime E: type, comptime nodeParseFn: anytype) ParseFn([]E) { return struct { pub fn parse(p: *Parser) ![]E { var list = std.ArrayList(E).init(p.gpa); diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index cc6030ad15c5b953d61ac23d389ceeca1bdb82c6..f7ceee16ac687c3ced27be3f195b558b85fdd251 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -125,7 +125,7 @@ test "parse" { } /// Writes a Zig-syntax escaped string literal to the stream. Includes the double quotes. -pub fn render(utf8: []const u8, out_stream: var) !void { +pub fn render(utf8: []const u8, out_stream: anytype) !void { try out_stream.writeByte('"'); for (utf8) |byte| switch (byte) { '\n' => try out_stream.writeAll("\\n"), diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 898d376cc7622fc7f845cafdef3f24a3098b72db..af494efbab2de1f6ada9a07626c5c7fe087f9bde 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -130,7 +130,7 @@ pub const NativePaths = struct { return self.appendArray(&self.include_dirs, s); } - pub fn addIncludeDirFmt(self: *NativePaths, comptime fmt: []const u8, args: var) !void { + pub fn addIncludeDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void { const item = try std.fmt.allocPrint0(self.include_dirs.allocator, fmt, args); errdefer self.include_dirs.allocator.free(item); try self.include_dirs.append(item); @@ -140,7 +140,7 @@ pub const NativePaths = struct { return self.appendArray(&self.lib_dirs, s); } - pub fn addLibDirFmt(self: *NativePaths, comptime fmt: []const u8, args: var) !void { + pub fn addLibDirFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void { const item = try std.fmt.allocPrint0(self.lib_dirs.allocator, fmt, args); errdefer self.lib_dirs.allocator.free(item); try self.lib_dirs.append(item); @@ -150,7 +150,7 @@ pub const NativePaths = struct { return self.appendArray(&self.warnings, s); } - pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: var) !void { + pub fn addWarningFmt(self: *NativePaths, comptime fmt: []const u8, args: anytype) !void { const item = try std.fmt.allocPrint0(self.warnings.allocator, fmt, args); errdefer self.warnings.allocator.free(item); try self.warnings.append(item); @@ -887,7 +887,7 @@ pub const NativeTargetInfo = struct { abi: Target.Abi, }; - pub fn elfInt(is_64: bool, need_bswap: bool, int_32: var, int_64: var) @TypeOf(int_64) { + pub fn elfInt(is_64: bool, need_bswap: bool, int_32: anytype, int_64: anytype) @TypeOf(int_64) { if (is_64) { if (need_bswap) { return @byteSwap(@TypeOf(int_64), int_64); diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index a6508943dfcb96020e5ec8775a28a60c7bbbafc9..b0678ea665be5636b281dd542d7854e9a41a023d 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -3575,7 +3575,7 @@ fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *I return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{}); } -fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: var) InnerError { +fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: anytype) InnerError { @setCold(true); const err_msg = try ErrorMsg.create(self.gpa, src, format, args); return self.failWithOwnedErrorMsg(scope, src, err_msg); @@ -3586,7 +3586,7 @@ fn failTok( scope: *Scope, token_index: ast.TokenIndex, comptime format: []const u8, - args: var, + args: anytype, ) InnerError { @setCold(true); const src = scope.tree().token_locs[token_index].start; @@ -3598,7 +3598,7 @@ fn failNode( scope: *Scope, ast_node: *ast.Node, comptime format: []const u8, - args: var, + args: anytype, ) InnerError { @setCold(true); const src = scope.tree().token_locs[ast_node.firstToken()].start; @@ -3662,7 +3662,7 @@ pub const ErrorMsg = struct { byte_offset: usize, msg: []const u8, - pub fn create(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !*ErrorMsg { + pub fn create(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !*ErrorMsg { const self = try gpa.create(ErrorMsg); errdefer gpa.destroy(self); self.* = try init(gpa, byte_offset, format, args); @@ -3675,7 +3675,7 @@ pub const ErrorMsg = struct { gpa.destroy(self); } - pub fn init(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !ErrorMsg { + pub fn init(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !ErrorMsg { return ErrorMsg{ .byte_offset = byte_offset, .msg = try std.fmt.allocPrint(gpa, format, args), diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 7ed84d540c90d7ef1038c1071b3f1319acf1284f..ba91e0726fea45fc82fa998b38aeb1ed2e13a702 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -230,7 +230,7 @@ pub fn generateSymbol( } } -const InnerError = error { +const InnerError = error{ OutOfMemory, CodegenFail, }; @@ -673,9 +673,9 @@ const Function = struct { try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38); const info = inst.args.lhs.ty.intInfo(self.target.*); if (info.signed) { - return MCValue{.compare_flags_signed = inst.args.op}; + return MCValue{ .compare_flags_signed = inst.args.op }; } else { - return MCValue{.compare_flags_unsigned = inst.args.op}; + return MCValue{ .compare_flags_unsigned = inst.args.op }; } }, else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), @@ -721,7 +721,7 @@ const Function = struct { } fn genX86CondBr(self: *Function, inst: *ir.Inst.CondBr, opcode: u8, comptime arch: std.Target.Cpu.Arch) !MCValue { - self.code.appendSliceAssumeCapacity(&[_]u8{0x0f, opcode}); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode }); const reloc = Reloc{ .rel32 = self.code.items.len }; self.code.items.len += 4; try self.genBody(inst.args.true_body, arch); @@ -1081,10 +1081,12 @@ const Function = struct { switch (mcv) { .immediate => |imm| { // This immediate is unsigned. - const U = @Type(.{ .Int = .{ - .bits = ti.bits - @boolToInt(ti.is_signed), - .is_signed = false, - }}); + const U = @Type(.{ + .Int = .{ + .bits = ti.bits - @boolToInt(ti.is_signed), + .is_signed = false, + }, + }); if (imm >= std.math.maxInt(U)) { return self.copyToNewRegister(inst); } @@ -1094,7 +1096,6 @@ const Function = struct { return mcv; } - fn genTypedValue(self: *Function, src: usize, typed_value: TypedValue) !MCValue { const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); @@ -1121,7 +1122,7 @@ const Function = struct { } } - fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } { + fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } { @setCold(true); assert(self.err_msg == null); self.err_msg = try ErrorMsg.create(self.bin_file.allocator, src, format, args); diff --git a/src-self-hosted/dep_tokenizer.zig b/src-self-hosted/dep_tokenizer.zig index cad12834a7d6c4da39a34d7135b60e60da6ca315..20324cbf0c1f5c764384f802225fedca2a3f5a06 100644 --- a/src-self-hosted/dep_tokenizer.zig +++ b/src-self-hosted/dep_tokenizer.zig @@ -299,12 +299,12 @@ pub const Tokenizer = struct { return null; } - fn errorf(self: *Tokenizer, comptime fmt: []const u8, args: var) Error { + fn errorf(self: *Tokenizer, comptime fmt: []const u8, args: anytype) Error { self.error_text = try std.fmt.allocPrintZ(&self.arena.allocator, fmt, args); return Error.InvalidInput; } - fn errorPosition(self: *Tokenizer, position: usize, bytes: []const u8, comptime fmt: []const u8, args: var) Error { + fn errorPosition(self: *Tokenizer, position: usize, bytes: []const u8, comptime fmt: []const u8, args: anytype) Error { var buffer = try std.ArrayListSentineled(u8, 0).initSize(&self.arena.allocator, 0); try buffer.outStream().print(fmt, args); try buffer.appendSlice(" '"); @@ -316,7 +316,7 @@ pub const Tokenizer = struct { return Error.InvalidInput; } - fn errorIllegalChar(self: *Tokenizer, position: usize, char: u8, comptime fmt: []const u8, args: var) Error { + fn errorIllegalChar(self: *Tokenizer, position: usize, char: u8, comptime fmt: []const u8, args: anytype) Error { var buffer = try std.ArrayListSentineled(u8, 0).initSize(&self.arena.allocator, 0); try buffer.appendSlice("illegal char "); try printUnderstandableChar(&buffer, char); @@ -883,7 +883,7 @@ fn depTokenizer(input: []const u8, expect: []const u8) !void { testing.expect(false); } -fn printSection(out: var, label: []const u8, bytes: []const u8) !void { +fn printSection(out: anytype, label: []const u8, bytes: []const u8) !void { try printLabel(out, label, bytes); try hexDump(out, bytes); try printRuler(out); @@ -891,7 +891,7 @@ fn printSection(out: var, label: []const u8, bytes: []const u8) !void { try out.write("\n"); } -fn printLabel(out: var, label: []const u8, bytes: []const u8) !void { +fn printLabel(out: anytype, label: []const u8, bytes: []const u8) !void { var buf: [80]u8 = undefined; var text = try std.fmt.bufPrint(buf[0..], "{} {} bytes ", .{ label, bytes.len }); try out.write(text); @@ -903,7 +903,7 @@ fn printLabel(out: var, label: []const u8, bytes: []const u8) !void { try out.write("\n"); } -fn printRuler(out: var) !void { +fn printRuler(out: anytype) !void { var i: usize = 0; const end = 79; while (i < 79) : (i += 1) { @@ -912,7 +912,7 @@ fn printRuler(out: var) !void { try out.write("\n"); } -fn hexDump(out: var, bytes: []const u8) !void { +fn hexDump(out: anytype, bytes: []const u8) !void { const n16 = bytes.len >> 4; var line: usize = 0; var offset: usize = 0; @@ -959,7 +959,7 @@ fn hexDump(out: var, bytes: []const u8) !void { try out.write("\n"); } -fn hexDump16(out: var, offset: usize, bytes: []const u8) !void { +fn hexDump16(out: anytype, offset: usize, bytes: []const u8) !void { try printDecValue(out, offset, 8); try out.write(":"); try out.write(" "); @@ -977,19 +977,19 @@ fn hexDump16(out: var, offset: usize, bytes: []const u8) !void { try out.write("|\n"); } -fn printDecValue(out: var, value: u64, width: u8) !void { +fn printDecValue(out: anytype, value: u64, width: u8) !void { var buffer: [20]u8 = undefined; const len = std.fmt.formatIntBuf(buffer[0..], value, 10, false, width); try out.write(buffer[0..len]); } -fn printHexValue(out: var, value: u64, width: u8) !void { +fn printHexValue(out: anytype, value: u64, width: u8) !void { var buffer: [16]u8 = undefined; const len = std.fmt.formatIntBuf(buffer[0..], value, 16, false, width); try out.write(buffer[0..len]); } -fn printCharValues(out: var, bytes: []const u8) !void { +fn printCharValues(out: anytype, bytes: []const u8) !void { for (bytes) |b| { try out.write(&[_]u8{printable_char_tab[b]}); } @@ -1020,13 +1020,13 @@ comptime { // output: must be a function that takes a `self` idiom parameter // and a bytes parameter // context: must be that self -fn makeOutput(comptime output: var, context: var) Output(output, @TypeOf(context)) { +fn makeOutput(comptime output: anytype, context: anytype) Output(output, @TypeOf(context)) { return Output(output, @TypeOf(context)){ .context = context, }; } -fn Output(comptime output_func: var, comptime Context: type) type { +fn Output(comptime output_func: anytype, comptime Context: type) type { return struct { context: Context, diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 1de7c626eac7cededc195b4ceeb0832a1c74d620..094b877b530767c2be20b1e02a4a6639427e91d9 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -13,7 +13,7 @@ const codegen = @import("codegen.zig"); pub const Inst = struct { tag: Tag, /// Each bit represents the index of an `Inst` parameter in the `args` field. - /// If a bit is set, it marks the end of the lifetime of the corresponding + /// If a bit is set, it marks the end of the lifetime of the corresponding /// instruction parameter. For example, 0b000_00101 means that the first and /// third `Inst` parameters' lifetimes end after this instruction, and will /// not have any more following references. diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig index dfc0f1235ac5831d7a2ba333cdd3d4d7db31b460..65c6c8c16d4727765c994ba01da238b3e7d3c831 100644 --- a/src-self-hosted/libc_installation.zig +++ b/src-self-hosted/libc_installation.zig @@ -37,7 +37,7 @@ pub const LibCInstallation = struct { pub fn parse( allocator: *Allocator, libc_file: []const u8, - stderr: var, + stderr: anytype, ) !LibCInstallation { var self: LibCInstallation = .{}; @@ -115,7 +115,7 @@ pub const LibCInstallation = struct { return self; } - pub fn render(self: LibCInstallation, out: var) !void { + pub fn render(self: LibCInstallation, out: anytype) !void { @setEvalBranchQuota(4000); const include_dir = self.include_dir orelse ""; const sys_include_dir = self.sys_include_dir orelse ""; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 2fb14ab690f95d120a98ced250626c7257796ee8..b664d933532a7e3e1acc5319f97ec083dbe4b894 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -244,7 +244,7 @@ pub const File = struct { need_noreturn: bool = false, error_msg: *Module.ErrorMsg = undefined, - pub fn fail(self: *C, src: usize, comptime format: []const u8, args: var) !void { + pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) !void { self.error_msg = try Module.ErrorMsg.create(self.allocator, src, format, args); return error.CGenFailure; } @@ -1167,10 +1167,10 @@ pub const File = struct { try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len); if (self.local_symbol_free_list.popOrNull()) |i| { - std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name}); + std.log.debug(.link, "reusing symbol index {} for {}\n", .{ i, decl.name }); decl.link.local_sym_index = i; } else { - std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name}); + std.log.debug(.link, "allocating symbol index {} for {}\n", .{ self.local_symbols.items.len, decl.name }); decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len); _ = self.local_symbols.addOneAssumeCapacity(); } @@ -1657,7 +1657,7 @@ fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !Fil } /// Saturating multiplication -fn satMul(a: var, b: var) @TypeOf(a, b) { +fn satMul(a: anytype, b: anytype) @TypeOf(a, b) { const T = @TypeOf(a, b); return std.math.mul(T, a, b) catch std.math.maxInt(T); } diff --git a/src-self-hosted/liveness.zig b/src-self-hosted/liveness.zig index 28eb2145c708efbf45723dd5ce908f294e1bb6e5..797a55a80c5f3037fd49aa33e3b655108f177be5 100644 --- a/src-self-hosted/liveness.zig +++ b/src-self-hosted/liveness.zig @@ -135,5 +135,5 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void } } - std.log.debug(.liveness, "analyze {}: 0b{b}\n", .{inst.base.tag, inst.base.deaths}); + std.log.debug(.liveness, "analyze {}: 0b{b}\n", .{ inst.base.tag, inst.base.deaths }); } diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index ee60b600a4802115c6c9d3128d4d8b8b6469ec96..c0ac42c8453c43e87116555990ea1af20fca2a4d 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -42,7 +42,7 @@ pub fn log( comptime level: std.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, - args: var, + args: anytype, ) void { if (@enumToInt(level) > @enumToInt(std.log.level)) return; diff --git a/src-self-hosted/print_targets.zig b/src-self-hosted/print_targets.zig index f84a4a2cbc643fc027b4cd1531ef8008fb0c4af5..34eda71ccf43f9892c225ff17c24c4947e459f58 100644 --- a/src-self-hosted/print_targets.zig +++ b/src-self-hosted/print_targets.zig @@ -62,7 +62,7 @@ pub fn cmdTargets( allocator: *Allocator, args: []const []const u8, /// Output stream - stdout: var, + stdout: anytype, native_target: Target, ) !void { const available_glibcs = blk: { diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 261cef37b5035cb63c5bde1c28da1e5e9d5c5c1c..396ee09ef95ca795aa3cd36123a25620bd32af66 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -1117,7 +1117,7 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No return transCreateNodeIdentifier(c, name); } -fn createAlias(c: *Context, alias: var) !void { +fn createAlias(c: *Context, alias: anytype) !void { const node = try transCreateNodeVarDecl(c, true, true, alias.alias); node.eq_token = try appendToken(c, .Equal, "="); node.init_node = try transCreateNodeIdentifier(c, alias.name); @@ -2161,7 +2161,7 @@ fn transCreateNodeArrayType( rp: RestorePoint, source_loc: ZigClangSourceLocation, ty: *const ZigClangType, - len: var, + len: anytype, ) TransError!*ast.Node { var node = try transCreateNodePrefixOp( rp.c, @@ -4187,7 +4187,7 @@ fn transCreateNodeBoolLiteral(c: *Context, value: bool) !*ast.Node { return &node.base; } -fn transCreateNodeInt(c: *Context, int: var) !*ast.Node { +fn transCreateNodeInt(c: *Context, int: anytype) !*ast.Node { const token = try appendTokenFmt(c, .IntegerLiteral, "{}", .{int}); const node = try c.arena.create(ast.Node.IntegerLiteral); node.* = .{ @@ -4196,7 +4196,7 @@ fn transCreateNodeInt(c: *Context, int: var) !*ast.Node { return &node.base; } -fn transCreateNodeFloat(c: *Context, int: var) !*ast.Node { +fn transCreateNodeFloat(c: *Context, int: anytype) !*ast.Node { const token = try appendTokenFmt(c, .FloatLiteral, "{}", .{int}); const node = try c.arena.create(ast.Node.FloatLiteral); node.* = .{ @@ -4907,22 +4907,22 @@ fn finishTransFnProto( fn revertAndWarn( rp: RestorePoint, - err: var, + err: anytype, source_loc: ZigClangSourceLocation, comptime format: []const u8, - args: var, + args: anytype, ) (@TypeOf(err) || error{OutOfMemory}) { rp.activate(); try emitWarning(rp.c, source_loc, format, args); return err; } -fn emitWarning(c: *Context, loc: ZigClangSourceLocation, comptime format: []const u8, args: var) !void { +fn emitWarning(c: *Context, loc: ZigClangSourceLocation, comptime format: []const u8, args: anytype) !void { const args_prefix = .{c.locStr(loc)}; _ = try appendTokenFmt(c, .LineComment, "// {}: warning: " ++ format, args_prefix ++ args); } -pub fn failDecl(c: *Context, loc: ZigClangSourceLocation, name: []const u8, comptime format: []const u8, args: var) !void { +pub fn failDecl(c: *Context, loc: ZigClangSourceLocation, name: []const u8, comptime format: []const u8, args: anytype) !void { // pub const name = @compileError(msg); const pub_tok = try appendToken(c, .Keyword_pub, "pub"); const const_tok = try appendToken(c, .Keyword_const, "const"); @@ -4973,7 +4973,7 @@ fn appendToken(c: *Context, token_id: Token.Id, bytes: []const u8) !ast.TokenInd return appendTokenFmt(c, token_id, "{}", .{bytes}); } -fn appendTokenFmt(c: *Context, token_id: Token.Id, comptime format: []const u8, args: var) !ast.TokenIndex { +fn appendTokenFmt(c: *Context, token_id: Token.Id, comptime format: []const u8, args: anytype) !ast.TokenIndex { assert(token_id != .Invalid); try c.token_ids.ensureCapacity(c.gpa, c.token_ids.items.len + 1); diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index caacf4e7fc74967afb6b629b114bf772747dfd09..82c7cfa607764ccdb652287d8a5d9a3f3131eb53 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -277,7 +277,7 @@ pub const Type = extern union { self: Type, comptime fmt: []const u8, options: std.fmt.FormatOptions, - out_stream: var, + out_stream: anytype, ) @TypeOf(out_stream).Error!void { comptime assert(fmt.len == 0); var ty = self; @@ -591,7 +591,6 @@ pub const Type = extern union { .anyerror => return 2, // TODO revisit this when we have the concept of the error tag type - .int_signed, .int_unsigned => { const bits: u16 = if (self.cast(Payload.IntSigned)) |pl| pl.bits diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 6509ee52f6938b9225408fbf65d0c7dab790b0b6..c1e9a38bd1dfa013ed6025cfa81a359148a44893 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -227,7 +227,7 @@ pub const Value = extern union { self: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, - out_stream: var, + out_stream: anytype, ) !void { comptime assert(fmt.len == 0); var val = self; diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 2a4db02c190e277994aa4b784337075de2421716..8faf2486362e0d4e475ff5b7a174a9f275c7b3e6 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -655,7 +655,7 @@ pub const Module = struct { /// The allocator is used for temporary storage, but this function always returns /// with no resources allocated. - pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void { + pub fn writeToStream(self: Module, allocator: *Allocator, stream: anytype) !void { var write = Writer{ .module = &self, .inst_table = InstPtrTable.init(allocator), @@ -686,7 +686,6 @@ pub const Module = struct { try stream.writeByte('\n'); } } - }; const InstPtrTable = std.AutoHashMap(*Inst, struct { inst: *Inst, index: ?usize, name: []const u8 }); @@ -700,7 +699,7 @@ const Writer = struct { fn writeInstToStream( self: *Writer, - stream: var, + stream: anytype, inst: *Inst, ) (@TypeOf(stream).Error || error{OutOfMemory})!void { // TODO I tried implementing this with an inline for loop and hit a compiler bug @@ -746,7 +745,7 @@ const Writer = struct { fn writeInstToStreamGeneric( self: *Writer, - stream: var, + stream: anytype, comptime inst_tag: Inst.Tag, base: *Inst, ) (@TypeOf(stream).Error || error{OutOfMemory})!void { @@ -783,7 +782,7 @@ const Writer = struct { try stream.writeByte(')'); } - fn writeParamToStream(self: *Writer, stream: var, param: var) !void { + fn writeParamToStream(self: *Writer, stream: anytype, param: anytype) !void { if (@typeInfo(@TypeOf(param)) == .Enum) { return stream.writeAll(@tagName(param)); } @@ -829,7 +828,7 @@ const Writer = struct { } } - fn writeInstParamToStream(self: *Writer, stream: var, inst: *Inst) !void { + fn writeInstParamToStream(self: *Writer, stream: anytype, inst: *Inst) !void { if (self.inst_table.get(inst)) |info| { if (info.index) |i| { try stream.print("%{}", .{info.index}); @@ -1062,7 +1061,7 @@ const Parser = struct { } } - fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError { + fn fail(self: *Parser, comptime format: []const u8, args: anytype) InnerError { @setCold(true); self.error_msg = ErrorMsg{ .byte_offset = self.i, diff --git a/test/stage1/behavior/async_fn.zig b/test/stage1/behavior/async_fn.zig index 4214ed84d23ae96fe1d4312f043ad655887aac52..807e4c62758db1b3d6d6a2730ce8b050a389507e 100644 --- a/test/stage1/behavior/async_fn.zig +++ b/test/stage1/behavior/async_fn.zig @@ -1016,7 +1016,7 @@ test "@asyncCall using the result location inside the frame" { test "@TypeOf an async function call of generic fn with error union type" { const S = struct { - fn func(comptime x: var) anyerror!i32 { + fn func(comptime x: anytype) anyerror!i32 { const T = @TypeOf(async func(x)); comptime expect(T == @TypeOf(@frame()).Child); return undefined; @@ -1032,7 +1032,7 @@ test "using @TypeOf on a generic function call" { var buf: [100]u8 align(16) = undefined; - fn amain(x: var) void { + fn amain(x: anytype) void { if (x == 0) { global_ok = true; return; @@ -1057,7 +1057,7 @@ test "recursive call of await @asyncCall with struct return type" { var buf: [100]u8 align(16) = undefined; - fn amain(x: var) Foo { + fn amain(x: anytype) Foo { if (x == 0) { global_ok = true; return Foo{ .x = 1, .y = 2, .z = 3 }; @@ -1336,7 +1336,7 @@ test "async function passed 0-bit arg after non-0-bit arg" { bar(1, .{}) catch unreachable; } - fn bar(x: i32, args: var) anyerror!void { + fn bar(x: i32, args: anytype) anyerror!void { global_frame = @frame(); suspend; global_int = x; @@ -1357,7 +1357,7 @@ test "async function passed align(16) arg after align(8) arg" { bar(10, .{a}) catch unreachable; } - fn bar(x: u64, args: var) anyerror!void { + fn bar(x: u64, args: anytype) anyerror!void { expect(x == 10); global_frame = @frame(); suspend; diff --git a/test/stage1/behavior/bitcast.zig b/test/stage1/behavior/bitcast.zig index 009f4544ba9af547fb7f913bff934de712f04995..2a86044dc198bb5f0c8ebe42a43cb269e0c312ea 100644 --- a/test/stage1/behavior/bitcast.zig +++ b/test/stage1/behavior/bitcast.zig @@ -171,7 +171,7 @@ test "nested bitcast" { test "bitcast passed as tuple element" { const S = struct { - fn foo(args: var) void { + fn foo(args: anytype) void { comptime expect(@TypeOf(args[0]) == f32); expect(args[0] == 12.34); } @@ -181,7 +181,7 @@ test "bitcast passed as tuple element" { test "triple level result location with bitcast sandwich passed as tuple element" { const S = struct { - fn foo(args: var) void { + fn foo(args: anytype) void { comptime expect(@TypeOf(args[0]) == f64); expect(args[0] > 12.33 and args[0] < 12.35); } diff --git a/test/stage1/behavior/bugs/2114.zig b/test/stage1/behavior/bugs/2114.zig index ab32a22cf3b9f2df1d21794804e093652d507048..1034a256d37e483828abf000c7a1b22f2de73b52 100644 --- a/test/stage1/behavior/bugs/2114.zig +++ b/test/stage1/behavior/bugs/2114.zig @@ -2,7 +2,7 @@ const std = @import("std"); const expect = std.testing.expect; const math = std.math; -fn ctz(x: var) usize { +fn ctz(x: anytype) usize { return @ctz(@TypeOf(x), x); } diff --git a/test/stage1/behavior/bugs/3742.zig b/test/stage1/behavior/bugs/3742.zig index f09127a66f4fc9d9370db74abd5f94ecae44536e..bf6e1f52073e65354d2d36f41282dde8c444fa33 100644 --- a/test/stage1/behavior/bugs/3742.zig +++ b/test/stage1/behavior/bugs/3742.zig @@ -23,7 +23,7 @@ pub fn isCommand(comptime T: type) bool { } pub const ArgSerializer = struct { - pub fn serializeCommand(command: var) void { + pub fn serializeCommand(command: anytype) void { const CmdT = @TypeOf(command); if (comptime isCommand(CmdT)) { diff --git a/test/stage1/behavior/bugs/4328.zig b/test/stage1/behavior/bugs/4328.zig index 0196af1748d972798b65c745d6c02df5e063a83a..98ab7bd155872b23e1c410d45367cbe9a4611911 100644 --- a/test/stage1/behavior/bugs/4328.zig +++ b/test/stage1/behavior/bugs/4328.zig @@ -17,11 +17,11 @@ const S = extern struct { test "Extern function calls in @TypeOf" { const Test = struct { - fn test_fn_1(a: var, b: var) @TypeOf(printf("%d %s\n", a, b)) { + fn test_fn_1(a: anytype, b: anytype) @TypeOf(printf("%d %s\n", a, b)) { return 0; } - fn test_fn_2(a: var) @TypeOf((S{ .state = 0 }).s_do_thing(a)) { + fn test_fn_2(a: anytype) @TypeOf((S{ .state = 0 }).s_do_thing(a)) { return 1; } @@ -56,7 +56,7 @@ test "Extern function calls, dereferences and field access in @TypeOf" { return .{ .dummy_field = 0 }; } - fn test_fn_2(a: var) @TypeOf(fopen("test", "r").*.dummy_field) { + fn test_fn_2(a: anytype) @TypeOf(fopen("test", "r").*.dummy_field) { return 255; } @@ -68,4 +68,4 @@ test "Extern function calls, dereferences and field access in @TypeOf" { Test.doTheTest(); comptime Test.doTheTest(); -} \ No newline at end of file +} diff --git a/test/stage1/behavior/bugs/4769_a.zig b/test/stage1/behavior/bugs/4769_a.zig index ab0c01417a74fea63241d7c8ff072a26d0fed513..8337712ea57f00733b5709b27efb3de797e95575 100644 --- a/test/stage1/behavior/bugs/4769_a.zig +++ b/test/stage1/behavior/bugs/4769_a.zig @@ -1 +1 @@ -// \ No newline at end of file +// diff --git a/test/stage1/behavior/bugs/4769_b.zig b/test/stage1/behavior/bugs/4769_b.zig index 23b2513f17c2efe5662713eb978018fe8a6e9075..9d0f028e570d42d223e656cb7f46798d956fd173 100644 --- a/test/stage1/behavior/bugs/4769_b.zig +++ b/test/stage1/behavior/bugs/4769_b.zig @@ -1 +1 @@ -//! \ No newline at end of file +//! diff --git a/test/stage1/behavior/byval_arg_var.zig b/test/stage1/behavior/byval_arg_var.zig index 3794a965c6d15e0fa6462a88cdee1a3f1fe1a241..ec3d18a532b4f687750e2cbf1aefdd6c193a966f 100644 --- a/test/stage1/behavior/byval_arg_var.zig +++ b/test/stage1/behavior/byval_arg_var.zig @@ -13,11 +13,11 @@ fn start() void { foo("string literal"); } -fn foo(x: var) void { +fn foo(x: anytype) void { bar(x); } -fn bar(x: var) void { +fn bar(x: anytype) void { result = x; } diff --git a/test/stage1/behavior/call.zig b/test/stage1/behavior/call.zig index 40b5be4cd3f20243c6d75bb584430af95f82bfb2..4d05a83a39efade59955dcc0ef4601f6257322ea 100644 --- a/test/stage1/behavior/call.zig +++ b/test/stage1/behavior/call.zig @@ -57,7 +57,7 @@ test "tuple parameters" { test "comptime call with bound function as parameter" { const S = struct { - fn ReturnType(func: var) type { + fn ReturnType(func: anytype) type { return switch (@typeInfo(@TypeOf(func))) { .BoundFn => |info| info, else => unreachable, diff --git a/test/stage1/behavior/enum.zig b/test/stage1/behavior/enum.zig index b6cb86a36396b8a4d370a332efdcd65282fc2c42..765828f5ce14660583cb5b2044fbdcb8073e3a8c 100644 --- a/test/stage1/behavior/enum.zig +++ b/test/stage1/behavior/enum.zig @@ -208,7 +208,7 @@ test "@tagName non-exhaustive enum" { comptime expect(mem.eql(u8, testEnumTagNameBare(NonExhaustive.B), "B")); } -fn testEnumTagNameBare(n: var) []const u8 { +fn testEnumTagNameBare(n: anytype) []const u8 { return @tagName(n); } diff --git a/test/stage1/behavior/error.zig b/test/stage1/behavior/error.zig index def7fd679dec60a8200c86f87151c6996e41ffb2..975e08b04ff32d096f95a2dde86d4adb674f06f1 100644 --- a/test/stage1/behavior/error.zig +++ b/test/stage1/behavior/error.zig @@ -227,7 +227,7 @@ test "error: Infer error set from literals" { _ = comptime intLiteral("n") catch |err| handleErrors(err); } -fn handleErrors(err: var) noreturn { +fn handleErrors(err: anytype) noreturn { switch (err) { error.T => {}, } diff --git a/test/stage1/behavior/eval.zig b/test/stage1/behavior/eval.zig index 2af34eaf7e109e614ebf3070805eda741f03eb12..17d5aafe06bba068e9f2f27b6268917908639eeb 100644 --- a/test/stage1/behavior/eval.zig +++ b/test/stage1/behavior/eval.zig @@ -670,10 +670,10 @@ fn loopNTimes(comptime n: usize) void { } test "variable inside inline loop that has different types on different iterations" { - testVarInsideInlineLoop(.{true, @as(u32, 42)}); + testVarInsideInlineLoop(.{ true, @as(u32, 42) }); } -fn testVarInsideInlineLoop(args: var) void { +fn testVarInsideInlineLoop(args: anytype) void { comptime var i = 0; inline while (i < args.len) : (i += 1) { const x = args[i]; @@ -814,17 +814,16 @@ test "two comptime calls with array default initialized to undefined" { dynamic_linker: DynamicLinker = DynamicLinker{}, pub fn parse() void { - var result: CrossTarget = .{ }; + var result: CrossTarget = .{}; result.getCpuArch(); } - pub fn getCpuArch(self: CrossTarget) void { } + pub fn getCpuArch(self: CrossTarget) void {} }; const DynamicLinker = struct { buffer: [255]u8 = undefined, }; - }; comptime { diff --git a/test/stage1/behavior/fn.zig b/test/stage1/behavior/fn.zig index c1e54593787971ad439a2ab2a7ce5b581345f4cf..c9f7477ecf984766e092a156dd7241c28b139aef 100644 --- a/test/stage1/behavior/fn.zig +++ b/test/stage1/behavior/fn.zig @@ -104,7 +104,7 @@ test "number literal as an argument" { comptime numberLiteralArg(3); } -fn numberLiteralArg(a: var) void { +fn numberLiteralArg(a: anytype) void { expect(a == 3); } @@ -132,7 +132,7 @@ test "pass by non-copying value through var arg" { expect(addPointCoordsVar(Point{ .x = 1, .y = 2 }) == 3); } -fn addPointCoordsVar(pt: var) i32 { +fn addPointCoordsVar(pt: anytype) i32 { comptime expect(@TypeOf(pt) == Point); return pt.x + pt.y; } @@ -267,7 +267,7 @@ test "ability to give comptime types and non comptime types to same parameter" { expect(foo(i32) == 20); } - fn foo(arg: var) i32 { + fn foo(arg: anytype) i32 { if (@typeInfo(@TypeOf(arg)) == .Type and arg == i32) return 20; return 9 + arg; } diff --git a/test/stage1/behavior/generics.zig b/test/stage1/behavior/generics.zig index a5d2f9dabe7a342c912cda4ae4fd608d9e8f1e62..6b584e381d5067b4abc140795a3911ff11a485a2 100644 --- a/test/stage1/behavior/generics.zig +++ b/test/stage1/behavior/generics.zig @@ -47,7 +47,7 @@ comptime { expect(max_f64(1.2, 3.4) == 3.4); } -fn max_var(a: var, b: var) @TypeOf(a + b) { +fn max_var(a: anytype, b: anytype) @TypeOf(a + b) { return if (a > b) a else b; } @@ -133,15 +133,15 @@ fn getFirstByte(comptime T: type, mem: []const T) u8 { return getByte(@ptrCast(*const u8, &mem[0])); } -const foos = [_]fn (var) bool{ +const foos = [_]fn (anytype) bool{ foo1, foo2, }; -fn foo1(arg: var) bool { +fn foo1(arg: anytype) bool { return arg; } -fn foo2(arg: var) bool { +fn foo2(arg: anytype) bool { return !arg; } diff --git a/test/stage1/behavior/optional.zig b/test/stage1/behavior/optional.zig index 0003bb86e1763b91ad994e7206782ee1822decaf..1dc33eb8ea03e4219223cfcfed760ca2fbbad9c0 100644 --- a/test/stage1/behavior/optional.zig +++ b/test/stage1/behavior/optional.zig @@ -67,8 +67,20 @@ fn test_cmp_optional_non_optional() void { // test evaluation is always lexical // ensure that the optional isn't always computed before the non-optional var mutable_state: i32 = 0; - _ = blk1: { mutable_state += 1; break :blk1 @as(?f64, 10.0); } != blk2: { expect(mutable_state == 1); break :blk2 @as(f64, 5.0); }; - _ = blk1: { mutable_state += 1; break :blk1 @as(f64, 10.0); } != blk2: { expect(mutable_state == 2); break :blk2 @as(?f64, 5.0); }; + _ = blk1: { + mutable_state += 1; + break :blk1 @as(?f64, 10.0); + } != blk2: { + expect(mutable_state == 1); + break :blk2 @as(f64, 5.0); + }; + _ = blk1: { + mutable_state += 1; + break :blk1 @as(f64, 10.0); + } != blk2: { + expect(mutable_state == 2); + break :blk2 @as(?f64, 5.0); + }; } test "passing an optional integer as a parameter" { diff --git a/test/stage1/behavior/struct.zig b/test/stage1/behavior/struct.zig index 7b8690d604edc4e9f3492e0d33861bad41f74dd7..2d83bd23bb10f5bea7fa8fdd3ecf28b20e5ad5eb 100644 --- a/test/stage1/behavior/struct.zig +++ b/test/stage1/behavior/struct.zig @@ -713,7 +713,7 @@ test "packed struct field passed to generic function" { a: u1, }; - fn genericReadPackedField(ptr: var) u5 { + fn genericReadPackedField(ptr: anytype) u5 { return ptr.*; } }; @@ -754,7 +754,7 @@ test "fully anonymous struct" { .s = "hi", }); } - fn dump(args: var) void { + fn dump(args: anytype) void { expect(args.int == 1234); expect(args.float == 12.34); expect(args.b); @@ -771,7 +771,7 @@ test "fully anonymous list literal" { fn doTheTest() void { dump(.{ @as(u32, 1234), @as(f64, 12.34), true, "hi" }); } - fn dump(args: var) void { + fn dump(args: anytype) void { expect(args.@"0" == 1234); expect(args.@"1" == 12.34); expect(args.@"2"); @@ -792,8 +792,8 @@ test "anonymous struct literal assigned to variable" { test "struct with var field" { const Point = struct { - x: var, - y: var, + x: anytype, + y: anytype, }; const pt = Point{ .x = 1, diff --git a/test/stage1/behavior/tuple.zig b/test/stage1/behavior/tuple.zig index 0b2fdfe4e0a56998cbee404380751122fb4fef29..299abec3c901c8d9c76eb3dd2916405b6acc9ed0 100644 --- a/test/stage1/behavior/tuple.zig +++ b/test/stage1/behavior/tuple.zig @@ -42,7 +42,7 @@ test "tuple multiplication" { comptime S.doTheTest(); const T = struct { - fn consume_tuple(tuple: var, len: usize) void { + fn consume_tuple(tuple: anytype, len: usize) void { expect(tuple.len == len); } @@ -82,7 +82,7 @@ test "tuple multiplication" { test "pass tuple to comptime var parameter" { const S = struct { - fn Foo(comptime args: var) void { + fn Foo(comptime args: anytype) void { expect(args[0] == 1); } diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index 2685a3552e3e750edb7d9e5b5b0cd7db3a8292ed..48263dba6f4d6309234b49cf9f453375a1ff47eb 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -385,7 +385,7 @@ test "@typeInfo does not force declarations into existence" { } test "defaut value for a var-typed field" { - const S = struct { x: var }; + const S = struct { x: anytype }; expect(@typeInfo(S).Struct.fields[0].default_value == null); } diff --git a/test/stage1/behavior/union.zig b/test/stage1/behavior/union.zig index 8555e712dd393d81942977763cf029183f322c31..da898347b96e575079b51df400f51580e3d6c161 100644 --- a/test/stage1/behavior/union.zig +++ b/test/stage1/behavior/union.zig @@ -296,7 +296,7 @@ const TaggedUnionWithAVoid = union(enum) { B: i32, }; -fn testTaggedUnionInit(x: var) bool { +fn testTaggedUnionInit(x: anytype) bool { const y = TaggedUnionWithAVoid{ .A = x }; return @as(@TagType(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A; } diff --git a/test/stage1/behavior/var_args.zig b/test/stage1/behavior/var_args.zig index 0c8674a7fba678edb012703dc6005c40c883f657..eae8f8f888badbe4715aa2e68b4976e4403d905a 100644 --- a/test/stage1/behavior/var_args.zig +++ b/test/stage1/behavior/var_args.zig @@ -1,6 +1,6 @@ const expect = @import("std").testing.expect; -fn add(args: var) i32 { +fn add(args: anytype) i32 { var sum = @as(i32, 0); { comptime var i: usize = 0; @@ -17,7 +17,7 @@ test "add arbitrary args" { expect(add(.{}) == 0); } -fn readFirstVarArg(args: var) void { +fn readFirstVarArg(args: anytype) void { const value = args[0]; } @@ -31,7 +31,7 @@ test "pass args directly" { expect(addSomeStuff(.{}) == 0); } -fn addSomeStuff(args: var) i32 { +fn addSomeStuff(args: anytype) i32 { return add(args); } @@ -47,7 +47,7 @@ test "runtime parameter before var args" { } } -fn extraFn(extra: u32, args: var) usize { +fn extraFn(extra: u32, args: anytype) usize { if (args.len >= 1) { expect(args[0] == false); } @@ -57,15 +57,15 @@ fn extraFn(extra: u32, args: var) usize { return args.len; } -const foos = [_]fn (var) bool{ +const foos = [_]fn (anytype) bool{ foo1, foo2, }; -fn foo1(args: var) bool { +fn foo1(args: anytype) bool { return true; } -fn foo2(args: var) bool { +fn foo2(args: anytype) bool { return false; } @@ -78,6 +78,6 @@ test "pass zero length array to var args param" { doNothingWithFirstArg(.{""}); } -fn doNothingWithFirstArg(args: var) void { +fn doNothingWithFirstArg(args: anytype) void { const a = args[0]; } diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index 851074e0d137e00dd36454b8df9ca7a7b0d6dc1f..8bdffcd500315773de288f1fc62f54ab606011dd 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -171,7 +171,7 @@ test "load vector elements via comptime index" { expect(v[1] == 2); expect(loadv(&v[2]) == 3); } - fn loadv(ptr: var) i32 { + fn loadv(ptr: anytype) i32 { return ptr.*; } }; @@ -194,7 +194,7 @@ test "store vector elements via comptime index" { storev(&v[0], 100); expect(v[0] == 100); } - fn storev(ptr: var, x: i32) void { + fn storev(ptr: anytype, x: i32) void { ptr.* = x; } }; @@ -392,7 +392,7 @@ test "vector shift operators" { if (builtin.os.tag == .wasi) return error.SkipZigTest; const S = struct { - fn doTheTestShift(x: var, y: var) void { + fn doTheTestShift(x: anytype, y: anytype) void { const N = @typeInfo(@TypeOf(x)).Array.len; const TX = @typeInfo(@TypeOf(x)).Array.child; const TY = @typeInfo(@TypeOf(y)).Array.child; @@ -409,7 +409,7 @@ test "vector shift operators" { expectEqual(x[i] << y[i], v); } } - fn doTheTestShiftExact(x: var, y: var, dir: enum { Left, Right }) void { + fn doTheTestShiftExact(x: anytype, y: anytype, dir: enum { Left, Right }) void { const N = @typeInfo(@TypeOf(x)).Array.len; const TX = @typeInfo(@TypeOf(x)).Array.child; const TY = @typeInfo(@TypeOf(y)).Array.child; -- 2.54.0 From 1a989ba39dbf2b718df7cc9792b6abaac7f5986d Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 21:20:50 +0300 Subject: [PATCH 242/295] fix parser tests and add test for anytype conversion --- lib/std/zig/parser_test.zig | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index c8e7abd5cbc7cba6d2cf5ca6b1fadd26f68801f6..da06d93b96e9171e05aa9304f39a7960491bcea5 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -422,10 +422,10 @@ test "zig fmt: asm expression with comptime content" { ); } -test "zig fmt: var struct field" { +test "zig fmt: anytype struct field" { try testCanonical( \\pub const Pointer = struct { - \\ sentinel: var, + \\ sentinel: anytype, \\}; \\ ); @@ -1932,7 +1932,7 @@ test "zig fmt: preserve spacing" { test "zig fmt: return types" { try testCanonical( \\pub fn main() !void {} - \\pub fn main() var {} + \\pub fn main() anytype {} \\pub fn main() i32 {} \\ ); @@ -2140,9 +2140,9 @@ test "zig fmt: call expression" { ); } -test "zig fmt: var type" { +test "zig fmt: anytype type" { try testCanonical( - \\fn print(args: var) var {} + \\fn print(args: anytype) anytype {} \\ ); } @@ -3180,6 +3180,22 @@ test "zig fmt: convert extern fn proto into callconv(.C)" { ); } +test "zig fmt: convert var to anytype" { + // TODO remove in next release cycle + try testTransform( + \\pub fn main( + \\ a: var, + \\ bar: var, + \\) void {} + , + \\pub fn main( + \\ a: anytype, + \\ bar: anytype, + \\) void {} + \\ + ); +} + const std = @import("std"); const mem = std.mem; const warn = std.debug.warn; -- 2.54.0 From 3e095d8ef32fc93f5050cead708849846d626d1d Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 22:04:38 +0300 Subject: [PATCH 243/295] use 'anytype' in translate-c --- lib/std/zig/ast.zig | 8 ++++---- lib/std/zig/parse.zig | 6 +++--- lib/std/zig/render.zig | 2 +- src-self-hosted/translate_c.zig | 9 ++++----- test/translate_c.zig | 20 ++++++++++---------- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 03153f541a02ae2db6285f6c2a28dc60d5be6dc4..7cb4936444001c3ec2fcff711b2f03b2df50f3f4 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -993,7 +993,7 @@ pub const Node = struct { param_type: ParamType, pub const ParamType = union(enum) { - var_type: *Node, + any_type: *Node, var_args: TokenIndex, type_expr: *Node, }; @@ -1004,7 +1004,7 @@ pub const Node = struct { if (i < 1) { switch (self.param_type) { .var_args => return null, - .var_type, .type_expr => |node| return node, + .any_type, .type_expr => |node| return node, } } i -= 1; @@ -1018,14 +1018,14 @@ pub const Node = struct { if (self.name_token) |name_token| return name_token; switch (self.param_type) { .var_args => |tok| return tok, - .var_type, .type_expr => |node| return node.firstToken(), + .any_type, .type_expr => |node| return node.firstToken(), } } pub fn lastToken(self: *const ParamDecl) TokenIndex { switch (self.param_type) { .var_args => |tok| return tok, - .var_type, .type_expr => |node| return node.lastToken(), + .any_type, .type_expr => |node| return node.lastToken(), } } }; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 29b60c0ceeb9368c269a6655feb5bbf5cd9a9f37..f5f0c10826c2b138701fdc89a8fc636ed97f58ab 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -519,7 +519,7 @@ const Parser = struct { const callconv_expr = try p.parseCallconv(); const exclamation_token = p.eatToken(.Bang); - const return_type_expr = (try p.parseVarType()) orelse + const return_type_expr = (try p.parseAnyType()) orelse try p.expectNodeRecoverable(parseTypeExpr, .{ // most likely the user forgot to specify the return type. // Mark return type as invalid and try to continue. @@ -2028,7 +2028,7 @@ const Parser = struct { fn parseParamType(p: *Parser) !?Node.FnProto.ParamDecl.ParamType { // TODO cast from tuple to error union is broken const P = Node.FnProto.ParamDecl.ParamType; - if (try p.parseVarType()) |node| return P{ .var_type = node }; + if (try p.parseAnyType()) |node| return P{ .any_type = node }; if (p.eatToken(.Ellipsis3)) |token| return P{ .var_args = token }; if (try p.parseTypeExpr()) |node| return P{ .type_expr = node }; return null; @@ -3057,7 +3057,7 @@ const Parser = struct { return &node.base; } - fn parseVarType(p: *Parser) !?*Node { + fn parseAnyType(p: *Parser) !?*Node { const token = p.eatToken(.Keyword_anytype) orelse p.eatToken(.Keyword_var) orelse return null; // TODO remove in next release cycle const node = try p.arena.allocator.create(Node.AnyType); diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 3150d6a3f4470e44b235f92296e7002091634217..f24886c0abd9b8258354181be78c40ba7ad861b5 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -2198,7 +2198,7 @@ fn renderParamDecl( } switch (param_decl.param_type) { .var_args => |token| try renderToken(tree, stream, token, indent, start_col, space), - .var_type, .type_expr => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, space), + .any_type, .type_expr => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, space), } } diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 396ee09ef95ca795aa3cd36123a25620bd32af66..4ae2d74336bbc47568010def2439880a0f8d89b2 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -5215,10 +5215,9 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, const param_name_tok = try appendIdentifier(c, mangled_name); _ = try appendToken(c, .Colon, ":"); - const token_index = try appendToken(c, .Keyword_var, "var"); - const identifier = try c.arena.create(ast.Node.Identifier); - identifier.* = .{ - .token = token_index, + const any_type = try c.arena.create(ast.Node.AnyType); + any_type.* = .{ + .token = try appendToken(c, .Keyword_anytype, "anytype"), }; (try fn_params.addOne()).* = .{ @@ -5226,7 +5225,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, .comptime_token = null, .noalias_token = null, .name_token = param_name_tok, - .param_type = .{ .type_expr = &identifier.base }, + .param_type = .{ .any_type = &any_type.base }, }; if (it.peek().?.id != .Comma) diff --git a/test/translate_c.zig b/test/translate_c.zig index a4e2a330005b7c4a041dc9bd5c72eab14aa19be2..738f9523ad3a5b4222f8931c2cf0073231152105 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -21,7 +21,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("correct semicolon after infixop", \\#define __ferror_unlocked_body(_fp) (((_fp)->_flags & _IO_ERR_SEEN) != 0) , &[_][]const u8{ - \\pub inline fn __ferror_unlocked_body(_fp: var) @TypeOf(((_fp.*._flags) & _IO_ERR_SEEN) != 0) { + \\pub inline fn __ferror_unlocked_body(_fp: anytype) @TypeOf(((_fp.*._flags) & _IO_ERR_SEEN) != 0) { \\ return ((_fp.*._flags) & _IO_ERR_SEEN) != 0; \\} }); @@ -30,7 +30,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define FOO(x) ((x >= 0) + (x >= 0)) \\#define BAR 1 && 2 > 4 , &[_][]const u8{ - \\pub inline fn FOO(x: var) @TypeOf(@boolToInt(x >= 0) + @boolToInt(x >= 0)) { + \\pub inline fn FOO(x: anytype) @TypeOf(@boolToInt(x >= 0) + @boolToInt(x >= 0)) { \\ return @boolToInt(x >= 0) + @boolToInt(x >= 0); \\} , @@ -81,7 +81,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ break :blk bar; \\}; , - \\pub inline fn bar(x: var) @TypeOf(baz(1, 2)) { + \\pub inline fn bar(x: anytype) @TypeOf(baz(1, 2)) { \\ return blk: { \\ _ = &x; \\ _ = 3; @@ -1483,11 +1483,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , &[_][]const u8{ \\pub extern var c: c_int; , - \\pub inline fn BASIC(c_1: var) @TypeOf(c_1 * 2) { + \\pub inline fn BASIC(c_1: anytype) @TypeOf(c_1 * 2) { \\ return c_1 * 2; \\} , - \\pub inline fn FOO(L: var, b: var) @TypeOf(L + b) { + \\pub inline fn FOO(L: anytype, b: anytype) @TypeOf(L + b) { \\ return L + b; \\} }); @@ -2123,7 +2123,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { cases.add("macro call", \\#define CALL(arg) bar(arg) , &[_][]const u8{ - \\pub inline fn CALL(arg: var) @TypeOf(bar(arg)) { + \\pub inline fn CALL(arg: anytype) @TypeOf(bar(arg)) { \\ return bar(arg); \\} }); @@ -2683,7 +2683,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define FOO(bar) baz((void *)(baz)) \\#define BAR (void*) a , &[_][]const u8{ - \\pub inline fn FOO(bar: var) @TypeOf(baz((@import("std").meta.cast(?*c_void, baz)))) { + \\pub inline fn FOO(bar: anytype) @TypeOf(baz((@import("std").meta.cast(?*c_void, baz)))) { \\ return baz((@import("std").meta.cast(?*c_void, baz))); \\} , @@ -2713,11 +2713,11 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define MIN(a, b) ((b) < (a) ? (b) : (a)) \\#define MAX(a, b) ((b) > (a) ? (b) : (a)) , &[_][]const u8{ - \\pub inline fn MIN(a: var, b: var) @TypeOf(if (b < a) b else a) { + \\pub inline fn MIN(a: anytype, b: anytype) @TypeOf(if (b < a) b else a) { \\ return if (b < a) b else a; \\} , - \\pub inline fn MAX(a: var, b: var) @TypeOf(if (b > a) b else a) { + \\pub inline fn MAX(a: anytype, b: anytype) @TypeOf(if (b > a) b else a) { \\ return if (b > a) b else a; \\} }); @@ -2905,7 +2905,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\#define DefaultScreen(dpy) (((_XPrivDisplay)(dpy))->default_screen) \\ , &[_][]const u8{ - \\pub inline fn DefaultScreen(dpy: var) @TypeOf((@import("std").meta.cast(_XPrivDisplay, dpy)).*.default_screen) { + \\pub inline fn DefaultScreen(dpy: anytype) @TypeOf((@import("std").meta.cast(_XPrivDisplay, dpy)).*.default_screen) { \\ return (@import("std").meta.cast(_XPrivDisplay, dpy)).*.default_screen; \\} }); -- 2.54.0 From be1507a7afe4c8869abdbab67a32ede6afe3d938 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 22:04:38 +0300 Subject: [PATCH 244/295] update compile error tests and some doc comments --- doc/docgen.zig | 11 +++++---- lib/std/fmt.zig | 2 +- lib/std/io/serialization.zig | 48 +++++++++++++++++++----------------- lib/std/log.zig | 2 +- lib/std/zig/ast.zig | 4 +-- src-self-hosted/Module.zig | 2 +- src/analyze.cpp | 4 +-- test/compile_errors.zig | 34 ++++++++++++------------- 8 files changed, 56 insertions(+), 51 deletions(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index e2acfae768d9efb192f4e1e093715339240183ed..af4d2530d05bd8b72588bf31bd026eb79437fc34 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -212,7 +212,7 @@ const Tokenizer = struct { } }; -fn parseError(tokenizer: *Tokenizer, token: Token, comptime fmt: []const u8, args: var) anyerror { +fn parseError(tokenizer: *Tokenizer, token: Token, comptime fmt: []const u8, args: anytype) anyerror { const loc = tokenizer.getTokenLocation(token); const args_prefix = .{ tokenizer.source_file_name, loc.line + 1, loc.column + 1 }; warn("{}:{}:{}: error: " ++ fmt ++ "\n", args_prefix ++ args); @@ -634,7 +634,7 @@ fn escapeHtml(allocator: *mem.Allocator, input: []const u8) ![]u8 { return buf.toOwnedSlice(); } -fn writeEscaped(out: var, input: []const u8) !void { +fn writeEscaped(out: anytype, input: []const u8) !void { for (input) |c| { try switch (c) { '&' => out.writeAll("&"), @@ -765,7 +765,7 @@ fn isType(name: []const u8) bool { return false; } -fn tokenizeAndPrintRaw(docgen_tokenizer: *Tokenizer, out: var, source_token: Token, raw_src: []const u8) !void { +fn tokenizeAndPrintRaw(docgen_tokenizer: *Tokenizer, out: anytype, source_token: Token, raw_src: []const u8) !void { const src = mem.trim(u8, raw_src, " \n"); try out.writeAll(""); var tokenizer = std.zig.Tokenizer.init(src); @@ -825,6 +825,7 @@ fn tokenizeAndPrintRaw(docgen_tokenizer: *Tokenizer, out: var, source_token: Tok .Keyword_volatile, .Keyword_allowzero, .Keyword_while, + .Keyword_anytype, => { try out.writeAll(""); try writeEscaped(out, src[token.loc.start..token.loc.end]); @@ -977,12 +978,12 @@ fn tokenizeAndPrintRaw(docgen_tokenizer: *Tokenizer, out: var, source_token: Tok try out.writeAll(""); } -fn tokenizeAndPrint(docgen_tokenizer: *Tokenizer, out: var, source_token: Token) !void { +fn tokenizeAndPrint(docgen_tokenizer: *Tokenizer, out: anytype, source_token: Token) !void { const raw_src = docgen_tokenizer.buffer[source_token.start..source_token.end]; return tokenizeAndPrintRaw(docgen_tokenizer, out, source_token, raw_src); } -fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var, zig_exe: []const u8) !void { +fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: anytype, zig_exe: []const u8) !void { var code_progress_index: usize = 0; var env_map = try process.getEnvMap(allocator); diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 7415b1b520f4a4bdc4a11f825b62c8794c24114b..7e288170af454ec13b6859102e8ff1032339c6ab 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -69,7 +69,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool { /// /// If a formatted user type contains a function of the type /// ``` -/// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: var) !void +/// pub fn format(value: ?, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void /// ``` /// with `?` being the type formatted, this function will be called instead of the default implementation. /// This allows user types to be formatted in a logical manner instead of dumping all fields of the type. diff --git a/lib/std/io/serialization.zig b/lib/std/io/serialization.zig index 8fe0782c849e48320518483c199916914ac8a1c1..317dde6417122b3157011e38f3baebf196549ea0 100644 --- a/lib/std/io/serialization.zig +++ b/lib/std/io/serialization.zig @@ -16,14 +16,16 @@ pub const Packing = enum { }; /// Creates a deserializer that deserializes types from any stream. -/// If `is_packed` is true, the data stream is treated as bit-packed, -/// otherwise data is expected to be packed to the smallest byte. -/// Types may implement a custom deserialization routine with a -/// function named `deserialize` in the form of: -/// pub fn deserialize(self: *Self, deserializer: var) !void -/// which will be called when the deserializer is used to deserialize -/// that type. It will pass a pointer to the type instance to deserialize -/// into and a pointer to the deserializer struct. +/// If `is_packed` is true, the data stream is treated as bit-packed, +/// otherwise data is expected to be packed to the smallest byte. +/// Types may implement a custom deserialization routine with a +/// function named `deserialize` in the form of: +/// ``` +/// pub fn deserialize(self: *Self, deserializer: anytype) !void +/// ``` +/// which will be called when the deserializer is used to deserialize +/// that type. It will pass a pointer to the type instance to deserialize +/// into and a pointer to the deserializer struct. pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime ReaderType: type) type { return struct { in_stream: if (packing == .Bit) io.BitReader(endian, ReaderType) else ReaderType, @@ -108,7 +110,7 @@ pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, const C = comptime meta.Child(T); const child_type_id = @typeInfo(C); - //custom deserializer: fn(self: *Self, deserializer: var) !void + //custom deserializer: fn(self: *Self, deserializer: anytype) !void if (comptime trait.hasFn("deserialize")(C)) return C.deserialize(ptr, self); if (comptime trait.isPacked(C) and packing != .Bit) { @@ -196,18 +198,20 @@ pub fn deserializer( } /// Creates a serializer that serializes types to any stream. -/// If `is_packed` is true, the data will be bit-packed into the stream. -/// Note that the you must call `serializer.flush()` when you are done -/// writing bit-packed data in order ensure any unwritten bits are committed. -/// If `is_packed` is false, data is packed to the smallest byte. In the case -/// of packed structs, the struct will written bit-packed and with the specified -/// endianess, after which data will resume being written at the next byte boundary. -/// Types may implement a custom serialization routine with a -/// function named `serialize` in the form of: -/// pub fn serialize(self: Self, serializer: var) !void -/// which will be called when the serializer is used to serialize that type. It will -/// pass a const pointer to the type instance to be serialized and a pointer -/// to the serializer struct. +/// If `is_packed` is true, the data will be bit-packed into the stream. +/// Note that the you must call `serializer.flush()` when you are done +/// writing bit-packed data in order ensure any unwritten bits are committed. +/// If `is_packed` is false, data is packed to the smallest byte. In the case +/// of packed structs, the struct will written bit-packed and with the specified +/// endianess, after which data will resume being written at the next byte boundary. +/// Types may implement a custom serialization routine with a +/// function named `serialize` in the form of: +/// ``` +/// pub fn serialize(self: Self, serializer: anytype) !void +/// ``` +/// which will be called when the serializer is used to serialize that type. It will +/// pass a const pointer to the type instance to be serialized and a pointer +/// to the serializer struct. pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, comptime OutStreamType: type) type { return struct { out_stream: if (packing == .Bit) io.BitOutStream(endian, OutStreamType) else OutStreamType, @@ -270,7 +274,7 @@ pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, co return; } - //custom serializer: fn(self: Self, serializer: var) !void + //custom serializer: fn(self: Self, serializer: anytype) !void if (comptime trait.hasFn("serialize")(T)) return T.serialize(value, self); if (comptime trait.isPacked(T) and packing != .Bit) { diff --git a/lib/std/log.zig b/lib/std/log.zig index 000658003181d7bf39024a666c84b203e2887eee..3fb75b7e3719126132b026233452ec44b5cddd43 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -22,7 +22,7 @@ const root = @import("root"); //! comptime level: std.log.Level, //! comptime scope: @TypeOf(.EnumLiteral), //! comptime format: []const u8, -//! args: var, +//! args: anytype, //! ) void { //! // Ignore all non-critical logging from sources other than //! // .my_project and .nice_library diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 7cb4936444001c3ec2fcff711b2f03b2df50f3f4..0502f5bb6488219164bfa7a603a10ff88cc1ae6d 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -1052,12 +1052,12 @@ pub const Node = struct { const params_len: usize = if (self.params_len == 0) 0 else switch (self.paramsConst()[self.params_len - 1].param_type) { - .var_type, .type_expr => self.params_len, + .any_type, .type_expr => self.params_len, .var_args => self.params_len - 1, }; if (i < params_len) { switch (self.paramsConst()[i].param_type) { - .var_type => |n| return n, + .any_type => |n| return n, .var_args => unreachable, .type_expr => |n| return n, } diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index b0678ea665be5636b281dd542d7854e9a41a023d..d173bd36e8b79b663b526ce1fd81d9544743d753 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1132,7 +1132,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_decls.len); for (param_decls) |param_decl, i| { const param_type_node = switch (param_decl.param_type) { - .var_type => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement anytype parameter", .{}), + .any_type => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement anytype parameter", .{}), .var_args => |tok| return self.failTok(&fn_type_scope.base, tok, "TODO implement var args", .{}), .type_expr => |node| node, }; diff --git a/src/analyze.cpp b/src/analyze.cpp index 5eba515e687656d5dfa5526a420755587bd84346..67c900507d0177c8dbed4fc9b3833c6cdcb07b62 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1511,13 +1511,13 @@ ZigType *get_generic_fn_type(CodeGen *g, FnTypeId *fn_type_id) { } for (; i < fn_type_id->param_count; i += 1) { const char *comma_str = (i == 0) ? "" : ","; - buf_appendf(&fn_type->name, "%svar", comma_str); + buf_appendf(&fn_type->name, "%sanytype", comma_str); } buf_append_str(&fn_type->name, ")"); if (fn_type_id->cc != CallingConventionUnspecified) { buf_appendf(&fn_type->name, " callconv(.%s)", calling_convention_name(fn_type_id->cc)); } - buf_append_str(&fn_type->name, " var"); + buf_append_str(&fn_type->name, " anytype"); fn_type->data.fn.fn_type_id = *fn_type_id; fn_type->data.fn.is_generic = true; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index df2e759bf1c4ed47ecced3b68c84ba947d0fe8ab..fb4428fbc64d37341ddac3e39dcd16c240695de9 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -42,7 +42,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\fn foo() Foo { \\ return .{ .x = 42 }; \\} - \\fn bar(val: var) Foo { + \\fn bar(val: anytype) Foo { \\ return .{ .x = val }; \\} \\export fn entry() void { @@ -1034,7 +1034,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ storev(&v[i], 42); \\} \\ - \\fn storev(ptr: var, val: i32) void { + \\fn storev(ptr: anytype, val: i32) void { \\ ptr.* = val; \\} , &[_][]const u8{ @@ -1049,7 +1049,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ var x = loadv(&v[i]); \\} \\ - \\fn loadv(ptr: var) i32 { + \\fn loadv(ptr: anytype) i32 { \\ return ptr.*; \\} , &[_][]const u8{ @@ -1832,7 +1832,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ while (true) {} \\} , &[_][]const u8{ - "error: expected type 'fn([]const u8, ?*std.builtin.StackTrace) noreturn', found 'fn([]const u8,var) var'", + "error: expected type 'fn([]const u8, ?*std.builtin.StackTrace) noreturn', found 'fn([]const u8,anytype) anytype'", "note: only one of the functions is generic", }); @@ -2032,11 +2032,11 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { }); cases.add("export generic function", - \\export fn foo(num: var) i32 { + \\export fn foo(num: anytype) i32 { \\ return 0; \\} , &[_][]const u8{ - "tmp.zig:1:15: error: parameter of type 'var' not allowed in function with calling convention 'C'", + "tmp.zig:1:15: error: parameter of type 'anytype' not allowed in function with calling convention 'C'", }); cases.add("C pointer to c_void", @@ -2836,7 +2836,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { }); cases.add("missing parameter name of generic function", - \\fn dump(var) void {} + \\fn dump(anytype) void {} \\export fn entry() void { \\ var a: u8 = 9; \\ dump(a); @@ -2859,13 +2859,13 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { }); cases.add("generic fn as parameter without comptime keyword", - \\fn f(_: fn (var) void) void {} - \\fn g(_: var) void {} + \\fn f(_: fn (anytype) void) void {} + \\fn g(_: anytype) void {} \\export fn entry() void { \\ f(g); \\} , &[_][]const u8{ - "tmp.zig:1:9: error: parameter of type 'fn(var) var' must be declared comptime", + "tmp.zig:1:9: error: parameter of type 'fn(anytype) anytype' must be declared comptime", }); cases.add("optional pointer to void in extern struct", @@ -3165,7 +3165,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { cases.add("var makes structs required to be comptime known", \\export fn entry() void { - \\ const S = struct{v: var}; + \\ const S = struct{v: anytype}; \\ var s = S{.v=@as(i32, 10)}; \\} , &[_][]const u8{ @@ -6072,10 +6072,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { }); cases.add("calling a generic function only known at runtime", - \\var foos = [_]fn(var) void { foo1, foo2 }; + \\var foos = [_]fn(anytype) void { foo1, foo2 }; \\ - \\fn foo1(arg: var) void {} - \\fn foo2(arg: var) void {} + \\fn foo1(arg: anytype) void {} + \\fn foo2(arg: anytype) void {} \\ \\pub fn main() !void { \\ foos[0](true); @@ -6920,12 +6920,12 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { }); cases.add("getting return type of generic function", - \\fn generic(a: var) void {} + \\fn generic(a: anytype) void {} \\comptime { \\ _ = @TypeOf(generic).ReturnType; \\} , &[_][]const u8{ - "tmp.zig:3:25: error: ReturnType has not been resolved because 'fn(var) var' is generic", + "tmp.zig:3:25: error: ReturnType has not been resolved because 'fn(anytype) anytype' is generic", }); cases.add("unsupported modifier at start of asm output constraint", @@ -7493,7 +7493,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { }); cases.add("issue #5221: invalid struct init type referenced by @typeInfo and passed into function", - \\fn ignore(comptime param: var) void {} + \\fn ignore(comptime param: anytype) void {} \\ \\export fn foo() void { \\ const MyStruct = struct { -- 2.54.0 From f23987db7d83403fc504b63171531e9ad1ffdc1a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 11 Jul 2020 18:33:56 -0400 Subject: [PATCH 245/295] Sponsors Button => ZSF Zig Software Foundation --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 23f70d6202bdb3b2a3d9322bdade97b4da46ad13..02ba7f0d317b4a4148fc9d675f42d0b3cc517535 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -github: [andrewrk] +github: [ziglang] -- 2.54.0 From bfe9d4184fb9b4d0cfd0964f18bb3789f3023939 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 20:35:00 +0300 Subject: [PATCH 246/295] fix alignment parsing in stage1 --- src/parser.cpp | 2 +- test/stage1/behavior/eval.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index fcd1133b0d8fd46cf494cc50b059ee8854e9f7ef..26233ec6a43283f44045cba69496a92d9be8639a 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2679,7 +2679,7 @@ static AstNode *ast_parse_prefix_type_op(ParseContext *pc) { if (eat_token_if(pc, TokenIdKeywordAlign) != nullptr) { expect_token(pc, TokenIdLParen); - AstNode *align_expr = ast_parse_expr(pc); + AstNode *align_expr = ast_expect(pc, ast_parse_expr); child->data.pointer_type.align_expr = align_expr; if (eat_token_if(pc, TokenIdColon) != nullptr) { Token *bit_offset_start = expect_token(pc, TokenIdIntLiteral); diff --git a/test/stage1/behavior/eval.zig b/test/stage1/behavior/eval.zig index 17d5aafe06bba068e9f2f27b6268917908639eeb..38dd12c59df825b1d9644605a79be0758f08b396 100644 --- a/test/stage1/behavior/eval.zig +++ b/test/stage1/behavior/eval.zig @@ -758,7 +758,7 @@ test "comptime bitwise operators" { test "*align(1) u16 is the same as *align(1:0:2) u16" { comptime { expect(*align(1:0:2) u16 == *align(1) u16); - expect(*align(:0:2) u16 == *u16); + expect(*align(2:0:2) u16 == *u16); } } -- 2.54.0 From e1a5e061ca7897ce6e08b724d517a83ae439bf41 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sun, 12 Jul 2020 11:27:50 +0300 Subject: [PATCH 247/295] revert accidental format of tests these test the tokenizers handling of EOF and formatting makes them useless --- test/stage1/behavior/bugs/4769_a.zig | 2 +- test/stage1/behavior/bugs/4769_b.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/stage1/behavior/bugs/4769_a.zig b/test/stage1/behavior/bugs/4769_a.zig index 8337712ea57f00733b5709b27efb3de797e95575..ab0c01417a74fea63241d7c8ff072a26d0fed513 100644 --- a/test/stage1/behavior/bugs/4769_a.zig +++ b/test/stage1/behavior/bugs/4769_a.zig @@ -1 +1 @@ -// +// \ No newline at end of file diff --git a/test/stage1/behavior/bugs/4769_b.zig b/test/stage1/behavior/bugs/4769_b.zig index 9d0f028e570d42d223e656cb7f46798d956fd173..23b2513f17c2efe5662713eb978018fe8a6e9075 100644 --- a/test/stage1/behavior/bugs/4769_b.zig +++ b/test/stage1/behavior/bugs/4769_b.zig @@ -1 +1 @@ -//! +//! \ No newline at end of file -- 2.54.0 From 4b48266bb7dd0a9f9353c0695a2c98c497c0165c Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 13 Jul 2020 00:34:02 +1000 Subject: [PATCH 248/295] std: add StringHashMapUnmanaged --- lib/std/hash_map.zig | 4 ++++ lib/std/std.zig | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index cfb75f077ed7177f4ab21d2ce7819eddefd29cd3..3952ecb4b2052443a5057cece7bead1ef73f4596 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -24,6 +24,10 @@ pub fn StringHashMap(comptime V: type) type { return HashMap([]const u8, V, hashString, eqlString, true); } +pub fn StringHashMapUnmanaged(comptime V: type) type { + return HashMapUnmanaged([]const u8, V, hashString, eqlString, true); +} + pub fn eqlString(a: []const u8, b: []const u8) bool { return mem.eql(u8, a, b); } diff --git a/lib/std/std.zig b/lib/std/std.zig index be4aad5f0d9d44fb41fd724c94a9fafb1cea91b5..50aaef6f118e39062d8c31e4ae018e6f99b91d01 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -25,6 +25,7 @@ pub const SegmentedList = @import("segmented_list.zig").SegmentedList; pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList; pub const SpinLock = @import("spinlock.zig").SpinLock; pub const StringHashMap = hash_map.StringHashMap; +pub const StringHashMapUnmanaged = hash_map.StringHashMapUnmanaged; pub const TailQueue = @import("linked_list.zig").TailQueue; pub const Target = @import("target.zig").Target; pub const Thread = @import("thread.zig").Thread; -- 2.54.0 From f7e4014c823f1134040beed700c2b0fff5384da1 Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 13 Jul 2020 00:34:58 +1000 Subject: [PATCH 249/295] std: use *Unmanaged data structures in http.Headers object --- lib/std/http/headers.zig | 58 ++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/std/http/headers.zig b/lib/std/http/headers.zig index f5465d4151e3994c3133f4fa5cc9ab180e550706..3c589c55d1483b822ce3085314cf9f499d1d582b 100644 --- a/lib/std/http/headers.zig +++ b/lib/std/http/headers.zig @@ -98,9 +98,9 @@ test "HeaderEntry" { testing.expectEqualSlices(u8, "x", e.value); } -const HeaderList = std.ArrayList(HeaderEntry); -const HeaderIndexList = std.ArrayList(usize); -const HeaderIndex = std.StringHashMap(HeaderIndexList); +const HeaderList = std.ArrayListUnmanaged(HeaderEntry); +const HeaderIndexList = std.ArrayListUnmanaged(usize); +const HeaderIndex = std.StringHashMapUnmanaged(HeaderIndexList); pub const Headers = struct { // the owned header field name is stored in the index as part of the key @@ -113,8 +113,8 @@ pub const Headers = struct { pub fn init(allocator: *Allocator) Self { return Self{ .allocator = allocator, - .data = HeaderList.init(allocator), - .index = HeaderIndex.init(allocator), + .data = HeaderList{}, + .index = HeaderIndex{}, }; } @@ -122,16 +122,16 @@ pub const Headers = struct { { for (self.index.items()) |*entry| { const dex = &entry.value; - dex.deinit(); + dex.deinit(self.allocator); self.allocator.free(entry.key); } - self.index.deinit(); + self.index.deinit(self.allocator); } { - for (self.data.span()) |entry| { + for (self.data.items) |entry| { entry.deinit(); } - self.data.deinit(); + self.data.deinit(self.allocator); } self.* = undefined; } @@ -139,36 +139,36 @@ pub const Headers = struct { pub fn clone(self: Self, allocator: *Allocator) !Self { var other = Headers.init(allocator); errdefer other.deinit(); - try other.data.ensureCapacity(self.data.items.len); - try other.index.initCapacity(self.index.entries.len); - for (self.data.span()) |entry| { + try other.data.ensureCapacity(allocator, self.data.items.len); + try other.index.initCapacity(allocator, self.index.entries.len); + for (self.data.items) |entry| { try other.append(entry.name, entry.value, entry.never_index); } return other; } pub fn toSlice(self: Self) []const HeaderEntry { - return self.data.span(); + return self.data.items; } pub fn append(self: *Self, name: []const u8, value: []const u8, never_index: ?bool) !void { const n = self.data.items.len + 1; - try self.data.ensureCapacity(n); + try self.data.ensureCapacity(self.allocator, n); var entry: HeaderEntry = undefined; if (self.index.getEntry(name)) |kv| { entry = try HeaderEntry.init(self.allocator, kv.key, value, never_index); errdefer entry.deinit(); const dex = &kv.value; - try dex.append(n - 1); + try dex.append(self.allocator, n - 1); } else { const name_dup = try self.allocator.dupe(u8, name); errdefer self.allocator.free(name_dup); entry = try HeaderEntry.init(self.allocator, name_dup, value, never_index); errdefer entry.deinit(); - var dex = HeaderIndexList.init(self.allocator); - try dex.append(n - 1); - errdefer dex.deinit(); - _ = try self.index.put(name_dup, dex); + var dex = HeaderIndexList{}; + try dex.append(self.allocator, n - 1); + errdefer dex.deinit(self.allocator); + _ = try self.index.put(self.allocator, name_dup, dex); } self.data.appendAssumeCapacity(entry); } @@ -194,7 +194,7 @@ pub const Headers = struct { /// Returns boolean indicating if something was deleted. pub fn delete(self: *Self, name: []const u8) bool { - if (self.index.remove(name)) |kv| { + if (self.index.remove(name)) |*kv| { const dex = &kv.value; // iterate backwards var i = dex.items.len; @@ -205,7 +205,7 @@ pub const Headers = struct { assert(mem.eql(u8, removed.name, name)); removed.deinit(); } - dex.deinit(); + dex.deinit(self.allocator); self.allocator.free(kv.key); self.rebuildIndex(); return true; @@ -225,13 +225,13 @@ pub const Headers = struct { const dex = &kv.value; if (dex.items.len == 1) { // was last item; delete the index - dex.deinit(); + dex.deinit(self.allocator); removed.deinit(); const key = kv.key; _ = self.index.remove(key); // invalidates `kv` and `dex` self.allocator.free(key); } else { - dex.shrink(dex.items.len - 1); + dex.shrink(self.allocator, dex.items.len - 1); removed.deinit(); } // if it was the last item; no need to rebuild index @@ -250,13 +250,13 @@ pub const Headers = struct { const dex = &kv.value; if (dex.items.len == 1) { // was last item; delete the index - dex.deinit(); + dex.deinit(self.allocator); removed.deinit(); const key = kv.key; _ = self.index.remove(key); // invalidates `kv` and `dex` self.allocator.free(key); } else { - dex.shrink(dex.items.len - 1); + dex.shrink(self.allocator, dex.items.len - 1); removed.deinit(); } // if it was the last item; no need to rebuild index @@ -282,7 +282,7 @@ pub const Headers = struct { const buf = try allocator.alloc(HeaderEntry, dex.items.len); var n: usize = 0; - for (dex.span()) |idx| { + for (dex.items) |idx| { buf[n] = self.data.items[idx]; n += 1; } @@ -305,7 +305,7 @@ pub const Headers = struct { // adapted from mem.join const total_len = blk: { var sum: usize = dex.items.len - 1; // space for separator(s) - for (dex.span()) |idx| + for (dex.items) |idx| sum += self.data.items[idx].value.len; break :blk sum; }; @@ -493,8 +493,8 @@ test "Headers.getIndices" { try h.append("set-cookie", "y=2", null); testing.expect(null == h.getIndices("not-present")); - testing.expectEqualSlices(usize, &[_]usize{0}, h.getIndices("foo").?.span()); - testing.expectEqualSlices(usize, &[_]usize{ 1, 2 }, h.getIndices("set-cookie").?.span()); + testing.expectEqualSlices(usize, &[_]usize{0}, h.getIndices("foo").?.items); + testing.expectEqualSlices(usize, &[_]usize{ 1, 2 }, h.getIndices("set-cookie").?.items); } test "Headers.get" { -- 2.54.0 From 91235b937195910076a87cbb9ecbc6c5214ddd3b Mon Sep 17 00:00:00 2001 From: daurnimator Date: Mon, 13 Jul 2020 00:38:59 +1000 Subject: [PATCH 250/295] std: don't store allocator inside of std.http.HeaderEntry --- lib/std/http/headers.zig | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/std/http/headers.zig b/lib/std/http/headers.zig index 3c589c55d1483b822ce3085314cf9f499d1d582b..86aff5f36443804af3c2303ecebf19ad86a5355c 100644 --- a/lib/std/http/headers.zig +++ b/lib/std/http/headers.zig @@ -27,7 +27,6 @@ fn never_index_default(name: []const u8) bool { } const HeaderEntry = struct { - allocator: *Allocator, name: []const u8, value: []u8, never_index: bool, @@ -36,23 +35,22 @@ const HeaderEntry = struct { fn init(allocator: *Allocator, name: []const u8, value: []const u8, never_index: ?bool) !Self { return Self{ - .allocator = allocator, .name = name, // takes reference .value = try allocator.dupe(u8, value), .never_index = never_index orelse never_index_default(name), }; } - fn deinit(self: Self) void { - self.allocator.free(self.value); + fn deinit(self: Self, allocator: *Allocator) void { + allocator.free(self.value); } - pub fn modify(self: *Self, value: []const u8, never_index: ?bool) !void { + pub fn modify(self: *Self, allocator: *Allocator, value: []const u8, never_index: ?bool) !void { const old_len = self.value.len; if (value.len > old_len) { - self.value = try self.allocator.realloc(self.value, value.len); + self.value = try allocator.realloc(self.value, value.len); } else if (value.len < old_len) { - self.value = self.allocator.shrink(self.value, value.len); + self.value = allocator.shrink(self.value, value.len); } mem.copy(u8, self.value, value); self.never_index = never_index orelse never_index_default(self.name); @@ -85,16 +83,16 @@ const HeaderEntry = struct { test "HeaderEntry" { var e = try HeaderEntry.init(testing.allocator, "foo", "bar", null); - defer e.deinit(); + defer e.deinit(testing.allocator); testing.expectEqualSlices(u8, "foo", e.name); testing.expectEqualSlices(u8, "bar", e.value); testing.expectEqual(false, e.never_index); - try e.modify("longer value", null); + try e.modify(testing.allocator, "longer value", null); testing.expectEqualSlices(u8, "longer value", e.value); // shorter value - try e.modify("x", null); + try e.modify(testing.allocator, "x", null); testing.expectEqualSlices(u8, "x", e.value); } @@ -129,7 +127,7 @@ pub const Headers = struct { } { for (self.data.items) |entry| { - entry.deinit(); + entry.deinit(self.allocator); } self.data.deinit(self.allocator); } @@ -157,14 +155,14 @@ pub const Headers = struct { var entry: HeaderEntry = undefined; if (self.index.getEntry(name)) |kv| { entry = try HeaderEntry.init(self.allocator, kv.key, value, never_index); - errdefer entry.deinit(); + errdefer entry.deinit(self.allocator); const dex = &kv.value; try dex.append(self.allocator, n - 1); } else { const name_dup = try self.allocator.dupe(u8, name); errdefer self.allocator.free(name_dup); entry = try HeaderEntry.init(self.allocator, name_dup, value, never_index); - errdefer entry.deinit(); + errdefer entry.deinit(self.allocator); var dex = HeaderIndexList{}; try dex.append(self.allocator, n - 1); errdefer dex.deinit(self.allocator); @@ -203,7 +201,7 @@ pub const Headers = struct { const data_index = dex.items[i]; const removed = self.data.orderedRemove(data_index); assert(mem.eql(u8, removed.name, name)); - removed.deinit(); + removed.deinit(self.allocator); } dex.deinit(self.allocator); self.allocator.free(kv.key); @@ -226,13 +224,13 @@ pub const Headers = struct { if (dex.items.len == 1) { // was last item; delete the index dex.deinit(self.allocator); - removed.deinit(); + removed.deinit(self.allocator); const key = kv.key; _ = self.index.remove(key); // invalidates `kv` and `dex` self.allocator.free(key); } else { dex.shrink(self.allocator, dex.items.len - 1); - removed.deinit(); + removed.deinit(self.allocator); } // if it was the last item; no need to rebuild index if (i != self.data.items.len) { @@ -251,13 +249,13 @@ pub const Headers = struct { if (dex.items.len == 1) { // was last item; delete the index dex.deinit(self.allocator); - removed.deinit(); + removed.deinit(self.allocator); const key = kv.key; _ = self.index.remove(key); // invalidates `kv` and `dex` self.allocator.free(key); } else { dex.shrink(self.allocator, dex.items.len - 1); - removed.deinit(); + removed.deinit(self.allocator); } // if it was the last item; no need to rebuild index if (i != self.data.items.len) { -- 2.54.0 From eea7271c4e9d56e9c0fadcaac0ffef54ad786bf8 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 9 Jul 2020 07:59:32 +0200 Subject: [PATCH 251/295] Fix incorrect continue condition in PreopeonList Also, check for overflow on incremented file descriptors. Previously, we'd trigger a panic if we exceeded the `fd_t` resolution. Now, instead, we throw an `error.Overflow` to signal that there can be no more file descriptors available from the runtime. This way we give the user the ability to still be able to check if their desired preopen exists in the list or not. --- lib/std/fs/wasi.zig | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index 4a897c78f70d758221a9c1e4afbe908228650f22..149ede252d5ca1a3732de458f03c2512691852ed 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -1,6 +1,7 @@ const std = @import("std"); const os = std.os; const mem = std.mem; +const math = std.math; const Allocator = mem.Allocator; usingnamespace std.os.wasi; @@ -72,7 +73,7 @@ pub const PreopenList = struct { const Self = @This(); - pub const Error = os.UnexpectedError || Allocator.Error; + pub const Error = error{ OutOfMemory, Overflow } || os.UnexpectedError; /// Deinitialize with `deinit`. pub fn init(allocator: *Allocator) Self { @@ -94,6 +95,12 @@ pub const PreopenList = struct { /// /// If called more than once, it will clear its contents every time before /// issuing the syscalls. + /// + /// In the unlinkely event of overflowing the number of available file descriptors, + /// returns `error.Overflow`. In this case, even though an error condition was reached + /// the preopen list still contains all valid preopened file descriptors that are valid + /// for use. Therefore, it is fine to call `find`, `asSlice`, or `toOwnedSlice`. Finally, + /// `deinit` still must be called! pub fn populate(self: *Self) Error!void { // Clear contents if we're being called again for (self.toOwnedSlice()) |preopen| { @@ -110,6 +117,7 @@ pub const PreopenList = struct { ESUCCESS => {}, ENOTSUP => { // not a preopen, so keep going + fd = try math.add(fd_t, fd, 1); continue; }, EBADF => { @@ -127,7 +135,7 @@ pub const PreopenList = struct { } const preopen = Preopen.new(fd, PreopenType{ .Dir = path_buf }); try self.buffer.append(preopen); - fd += 1; + fd = try math.add(fd_t, fd, 1); } } -- 2.54.0 From 873e187f05400a8cb515f31896ce3ca11a377ac8 Mon Sep 17 00:00:00 2001 From: Sam Tebbs Date: Sun, 12 Jul 2020 18:14:56 +0100 Subject: [PATCH 252/295] Make allocator test functions public --- lib/std/heap.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/heap.zig b/lib/std/heap.zig index ba96257557b0e9fb74d11b25261de7663d60cc61..a8ab72941375544ff5e7e9815504b7c398cd4013 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -815,7 +815,7 @@ test "ThreadSafeFixedBufferAllocator" { try testAllocatorAlignedShrink(&fixed_buffer_allocator.allocator); } -fn testAllocator(base_allocator: *mem.Allocator) !void { +pub fn testAllocator(base_allocator: *mem.Allocator) !void { var validationAllocator = mem.validationWrap(base_allocator); const allocator = &validationAllocator.allocator; @@ -846,7 +846,7 @@ fn testAllocator(base_allocator: *mem.Allocator) !void { allocator.free(slice); } -fn testAllocatorAligned(base_allocator: *mem.Allocator, comptime alignment: u29) !void { +pub fn testAllocatorAligned(base_allocator: *mem.Allocator, comptime alignment: u29) !void { var validationAllocator = mem.validationWrap(base_allocator); const allocator = &validationAllocator.allocator; @@ -873,7 +873,7 @@ fn testAllocatorAligned(base_allocator: *mem.Allocator, comptime alignment: u29) testing.expect(slice.len == 0); } -fn testAllocatorLargeAlignment(base_allocator: *mem.Allocator) mem.Allocator.Error!void { +pub fn testAllocatorLargeAlignment(base_allocator: *mem.Allocator) mem.Allocator.Error!void { var validationAllocator = mem.validationWrap(base_allocator); const allocator = &validationAllocator.allocator; @@ -905,7 +905,7 @@ fn testAllocatorLargeAlignment(base_allocator: *mem.Allocator) mem.Allocator.Err allocator.free(slice); } -fn testAllocatorAlignedShrink(base_allocator: *mem.Allocator) mem.Allocator.Error!void { +pub fn testAllocatorAlignedShrink(base_allocator: *mem.Allocator) mem.Allocator.Error!void { var validationAllocator = mem.validationWrap(base_allocator); const allocator = &validationAllocator.allocator; -- 2.54.0 From 5b570bceb531656c320684fbe42a87feff710a3b Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Sun, 12 Jul 2020 17:39:25 +0200 Subject: [PATCH 253/295] document a few functions in std.mem --- lib/std/mem.zig | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 82831787a794a9a669ca881b5ec691ce76a3be71..2f17a834221d7fd5331fd9e1c2c717cb0a5cb7ea 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -8,6 +8,7 @@ const meta = std.meta; const trait = meta.trait; const testing = std.testing; +// https://github.com/ziglang/zig/issues/2564 pub const page_size = switch (builtin.arch) { .wasm32, .wasm64 => 64 * 1024, else => 4 * 1024, @@ -518,6 +519,7 @@ pub fn copyBackwards(comptime T: type, dest: []T, source: []const T) void { } } +/// Sets all elements of `dest` to `value`. pub fn set(comptime T: type, dest: []T, value: T) void { for (dest) |*d| d.* = value; @@ -675,6 +677,8 @@ test "mem.zeroes" { } } +/// Sets a slice to zeroes. +/// Prevents the store from being optimized out. pub fn secureZero(comptime T: type, s: []T) void { // NOTE: We do not use a volatile slice cast here since LLVM cannot // see that it can be replaced by a memset. @@ -767,6 +771,7 @@ test "zeroInit" { }); } +/// Compares two slices of numbers lexicographically. O(n). pub fn order(comptime T: type, lhs: []const T, rhs: []const T) math.Order { const n = math.min(lhs.len, rhs.len); var i: usize = 0; @@ -1904,6 +1909,8 @@ fn testWriteIntImpl() void { })); } +/// Returns the smallest number in a slice. O(n). +/// `slice` must not be empty. pub fn min(comptime T: type, slice: []const T) T { var best = slice[0]; for (slice[1..]) |item| { @@ -1916,6 +1923,8 @@ test "mem.min" { testing.expect(min(u8, "abcdefg") == 'a'); } +/// Returns the largest number in a slice. O(n). +/// `slice` must not be empty. pub fn max(comptime T: type, slice: []const T) T { var best = slice[0]; for (slice[1..]) |item| { @@ -2071,7 +2080,7 @@ test "asBytes" { testing.expect(eql(u8, asBytes(&zero), "")); } -///Given any value, returns a copy of its bytes in an array. +/// Given any value, returns a copy of its bytes in an array. pub fn toBytes(value: anytype) [@sizeOf(@TypeOf(value))]u8 { return asBytes(&value).*; } @@ -2105,7 +2114,7 @@ fn BytesAsValueReturnType(comptime T: type, comptime B: type) type { return if (comptime trait.isConstPtr(B)) *align(alignment) const T else *align(alignment) T; } -///Given a pointer to an array of bytes, returns a pointer to a value of the specified type +/// Given a pointer to an array of bytes, returns a pointer to a value of the specified type /// backed by those bytes, preserving constness. pub fn bytesAsValue(comptime T: type, bytes: anytype) BytesAsValueReturnType(T, @TypeOf(bytes)) { return @ptrCast(BytesAsValueReturnType(T, @TypeOf(bytes)), bytes); @@ -2148,7 +2157,7 @@ test "bytesAsValue" { testing.expect(meta.eql(inst, inst2.*)); } -///Given a pointer to an array of bytes, returns a value of the specified type backed by a +/// Given a pointer to an array of bytes, returns a value of the specified type backed by a /// copy of those bytes. pub fn bytesToValue(comptime T: type, bytes: anytype) T { return bytesAsValue(T, bytes).*; -- 2.54.0 From ef17af1270155e803a77955c9f37175dd09c43d4 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Wed, 8 Jul 2020 23:07:44 +0200 Subject: [PATCH 254/295] std: add mem.joinZ currently the only options are doing a second allocation and copying or implementing this yourself. --- lib/std/mem.zig | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 2f17a834221d7fd5331fd9e1c2c717cb0a5cb7ea..0fbe96942eedd03a3af3f610b0856475d7f77992 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1674,12 +1674,23 @@ pub const SplitIterator = struct { /// Naively combines a series of slices with a separator. /// Allocates memory for the result, which must be freed by the caller. pub fn join(allocator: *Allocator, separator: []const u8, slices: []const []const u8) ![]u8 { + return joinMaybeZ(allocator, separator, slices, false); +} + +/// Naively combines a series of slices with a separator and null terminator. +/// Allocates memory for the result, which must be freed by the caller. +pub fn joinZ(allocator: *Allocator, separator: []const u8, slices: []const []const u8) ![:0]u8 { + const out = try joinMaybeZ(allocator, separator, slices, true); + return out[0 .. out.len - 1 :0]; +} + +fn joinMaybeZ(allocator: *Allocator, separator: []const u8, slices: []const []const u8, zero: bool) ![]u8 { if (slices.len == 0) return &[0]u8{}; const total_len = blk: { var sum: usize = separator.len * (slices.len - 1); - for (slices) |slice| - sum += slice.len; + for (slices) |slice| sum += slice.len; + if (zero) sum += 1; break :blk sum; }; @@ -1695,6 +1706,8 @@ pub fn join(allocator: *Allocator, separator: []const u8, slices: []const []cons buf_index += slice.len; } + if (zero) buf[buf.len - 1] = 0; + // No need for shrink since buf is exactly the correct size. return buf; } @@ -1717,6 +1730,27 @@ test "mem.join" { } } +test "mem.joinZ" { + { + const str = try joinZ(testing.allocator, ",", &[_][]const u8{ "a", "b", "c" }); + defer testing.allocator.free(str); + testing.expect(eql(u8, str, "a,b,c")); + testing.expectEqual(str[str.len], 0); + } + { + const str = try joinZ(testing.allocator, ",", &[_][]const u8{"a"}); + defer testing.allocator.free(str); + testing.expect(eql(u8, str, "a")); + testing.expectEqual(str[str.len], 0); + } + { + const str = try joinZ(testing.allocator, ",", &[_][]const u8{ "a", "", "b", "", "c" }); + defer testing.allocator.free(str); + testing.expect(eql(u8, str, "a,,b,,c")); + testing.expectEqual(str[str.len], 0); + } +} + /// Copies each T from slices into a new slice that exactly holds all the elements. pub fn concat(allocator: *Allocator, comptime T: type, slices: []const []const T) ![]T { if (slices.len == 0) return &[0]T{}; -- 2.54.0 From dff1ac1089dd8a7cae5c167bdb4e6269c9b84bb6 Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 11 Jul 2020 12:08:29 +0300 Subject: [PATCH 255/295] check for invalid sentinel when creating pointer with `@Type` --- src/ir.cpp | 7 ++++++- test/compile_errors.zig | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index 6447db8c715d70df70b561314de7984903b8efa6..dc380b4389c25ae6fa275e13633d49f70d422ed2 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -25915,6 +25915,11 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI { return ira->codegen->invalid_inst_gen->value->type; } + if (sentinel != nullptr && (size_enum_index == BuiltinPtrSizeOne || size_enum_index == BuiltinPtrSizeC)) { + ir_add_error(ira, source_instr, + buf_sprintf("sentinels are only allowed on slices and unknown-length pointers")); + return ira->codegen->invalid_inst_gen->value->type; + } BigInt *bi = get_const_field_lit_int(ira, source_instr->source_node, payload, "alignment", 3); if (bi == nullptr) return ira->codegen->invalid_inst_gen->value->type; @@ -25948,7 +25953,7 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI 0, // host_int_bytes is_allowzero, VECTOR_INDEX_NONE, nullptr, sentinel); - if (size_enum_index != 2) + if (size_enum_index != BuiltinPtrSizeSlice) return ptr_type; return get_slice_type(ira->codegen, ptr_type); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index fb4428fbc64d37341ddac3e39dcd16c240695de9..5de56f8ca75ced07be0deb67b70628fb79ba43a7 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,22 @@ const tests = @import("tests.zig"); const std = @import("std"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.addTest("invalid pointer with @Type", + \\export fn entry() void { + \\ _ = @Type(.{ .Pointer = .{ + \\ .size = .One, + \\ .is_const = false, + \\ .is_volatile = false, + \\ .alignment = 1, + \\ .child = u8, + \\ .is_allowzero = false, + \\ .sentinel = 0, + \\ }}); + \\} + , &[_][]const u8{ + "tmp.zig:2:16: error: sentinels are only allowed on slices and unknown-length pointers", + }); + cases.addTest("int/float conversion to comptime_int/float", \\export fn foo() void { \\ var a: f32 = 2; -- 2.54.0 From 2c882b2e651386399fd48c24302fd2d4ba430ef3 Mon Sep 17 00:00:00 2001 From: pixelherodev Date: Sun, 12 Jul 2020 22:56:31 -0400 Subject: [PATCH 256/295] CBE: Make C an ObjectFormat instead of a special bool (#5849) --- lib/std/target.zig | 1 + src-self-hosted/Module.zig | 2 -- src-self-hosted/link.zig | 8 +++++--- src-self-hosted/main.zig | 12 +++++++----- src-self-hosted/test.zig | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index a464cbb85c1174755c3251f400a7e49397d2e353..e1aab7278690ca9363bfe4cdc14aa9377c37b4a2 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -435,6 +435,7 @@ pub const Target = struct { elf, macho, wasm, + c, }; pub const SubSystem = enum { diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index d173bd36e8b79b663b526ce1fd81d9544743d753..4a33f0121d5a13b724a2af05ed7f4209976daf64 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -723,7 +723,6 @@ pub const InitOptions = struct { object_format: ?std.builtin.ObjectFormat = null, optimize_mode: std.builtin.Mode = .Debug, keep_source_files_loaded: bool = false, - cbe: bool = false, }; pub fn init(gpa: *Allocator, options: InitOptions) !Module { @@ -733,7 +732,6 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .output_mode = options.output_mode, .link_mode = options.link_mode orelse .Static, .object_format = options.object_format orelse options.target.getObjectFormat(), - .cbe = options.cbe, }); errdefer bin_file.destroy(); diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index b664d933532a7e3e1acc5319f97ec083dbe4b894..64328d4df5f598b562e67130b0ab0b7a970de7c2 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -22,7 +22,6 @@ pub const Options = struct { /// Used for calculating how much space to reserve for executable program code in case /// the binary file deos not already have such a section. program_code_size_hint: u64 = 256 * 1024, - cbe: bool = false, }; /// Attempts incremental linking, if the file already exists. @@ -35,10 +34,11 @@ pub fn openBinFilePath( sub_path: []const u8, options: Options, ) !*File { - const file = try dir.createFile(sub_path, .{ .truncate = options.cbe, .read = true, .mode = determineMode(options) }); + const cbe = options.object_format == .c; + const file = try dir.createFile(sub_path, .{ .truncate = cbe, .read = true, .mode = determineMode(options) }); errdefer file.close(); - if (options.cbe) { + if (cbe) { var bin_file = try allocator.create(File.C); errdefer allocator.destroy(bin_file); bin_file.* = try openCFile(allocator, file, options); @@ -1573,6 +1573,7 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !Fi .Lib => return error.TODOImplementWritingLibFiles, } switch (options.object_format) { + .c => unreachable, .unknown => unreachable, // TODO remove this tag from the enum .coff => return error.TODOImplementWritingCOFF, .elf => {}, @@ -1632,6 +1633,7 @@ fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !Fil } switch (options.object_format) { .unknown => unreachable, // TODO remove this tag from the enum + .c => unreachable, .coff => return error.IncrFailed, .elf => {}, .macho => return error.IncrFailed, diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index c0ac42c8453c43e87116555990ea1af20fca2a4d..f63e915c2529facd0bfb6eff6dfdc5685ec06d52 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -194,8 +194,8 @@ fn buildOutputType( var emit_zir: Emit = .no; var target_arch_os_abi: []const u8 = "native"; var target_mcpu: ?[]const u8 = null; - var cbe: bool = false; var target_dynamic_linker: ?[]const u8 = null; + var object_format: ?std.builtin.ObjectFormat = null; var system_libs = std.ArrayList([]const u8).init(gpa); defer system_libs.deinit(); @@ -283,7 +283,11 @@ fn buildOutputType( i += 1; target_mcpu = args[i]; } else if (mem.eql(u8, arg, "--c")) { - cbe = true; + if (object_format) |old| { + std.debug.print("attempted to override object format {} with C\n", .{old}); + process.exit(1); + } + object_format = .c; } else if (mem.startsWith(u8, arg, "-mcpu=")) { target_mcpu = arg["-mcpu=".len..]; } else if (mem.eql(u8, arg, "--dynamic-linker")) { @@ -417,7 +421,6 @@ fn buildOutputType( else => |e| return e, }; - const object_format: ?std.builtin.ObjectFormat = null; var target_info = try std.zig.system.NativeTargetInfo.detect(gpa, cross_target); if (target_info.cpu_detection_unimplemented) { // TODO We want to just use detected_info.target but implementing @@ -436,7 +439,7 @@ fn buildOutputType( std.debug.print("-fno-emit-bin not supported yet", .{}); process.exit(1); }, - .yes_default_path => if (cbe) + .yes_default_path => if (object_format != null and object_format.? == .c) try std.fmt.allocPrint(arena, "{}.c", .{root_name}) else try std.zig.binNameAlloc(arena, root_name, target_info.target, output_mode, link_mode), @@ -470,7 +473,6 @@ fn buildOutputType( .object_format = object_format, .optimize_mode = build_mode, .keep_source_files_loaded = zir_out_path != null, - .cbe = cbe, }); defer module.deinit(); diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 3e4958cc9559313293934a5e2a8ff70997a0c4f4..c64ff3bbcda2b3c2e62e645c8347886d8dc3e270 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -444,7 +444,7 @@ pub const TestContext = struct { .bin_file_path = bin_name, .root_pkg = root_pkg, .keep_source_files_loaded = true, - .cbe = case.cbe, + .object_format = if (case.cbe) .c else null, }); defer module.deinit(); -- 2.54.0 From 3bad1c16ccbe9d77df373a0dbbe934dccac13175 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 9 Jul 2020 15:38:54 -0400 Subject: [PATCH 257/295] Get basic return test working --- src-self-hosted/Module.zig | 34 ++++++++++++++++++---- src-self-hosted/cgen.zig | 36 +++++++++++++++++++++++ src-self-hosted/type.zig | 59 ++++++++++++++++++++++++++++++++++++++ test/stage2/cbe.zig | 44 ++++++++++++++-------------- 4 files changed, 145 insertions(+), 28 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 4a33f0121d5a13b724a2af05ed7f4209976daf64..3f5a833680218d141914b6c74f7ad1fdb1e41506 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -989,14 +989,22 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { error.AnalysisFail => { decl.analysis = .dependency_failure; }, + error.CGenFailure => { + // Error is handled by CBE, don't try adding it again + }, else => { try self.failed_decls.ensureCapacity(self.gpa, self.failed_decls.items().len + 1); - self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - self.gpa, - decl.src(), - "unable to codegen: {}", - .{@errorName(err)}, - )); + const result = self.failed_decls.getOrPutAssumeCapacity(decl); + if (result.found_existing) { + std.debug.panic("Internal error: attempted to override error '{}' with 'unable to codegen: {}'", .{ result.entry.value.msg, @errorName(err) }); + } else { + result.entry.value = try ErrorMsg.create( + self.gpa, + decl.src(), + "unable to codegen: {}", + .{@errorName(err)}, + ); + } decl.analysis = .codegen_failure_retryable; }, }; @@ -1300,6 +1308,20 @@ fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) InnerError!*zir.Inst { switch (infix_node.op) { + .Assign => { + if (infix_node.lhs.id == .Identifier) { + const ident = @fieldParentPtr(ast.Node.Identifier, "base", infix_node.lhs); + const tree = scope.tree(); + const ident_name = tree.tokenSlice(ident.token); + if (std.mem.eql(u8, ident_name, "_")) { + return self.astGenExpr(scope, infix_node.rhs); + } else { + return self.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); + } + } else { + return self.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); + } + }, .Add => { const lhs = try self.astGenExpr(scope, infix_node.lhs); const rhs = try self.astGenExpr(scope, infix_node.rhs); diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 75caddc131a1134b6a06b44d6c65bd91d5ed9b8e..81e2b4b7f88f6296aad06ed0c660e8c7c2c4f35d 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -26,6 +26,14 @@ fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) ! try writer.writeAll("noreturn void"); }, .Void => try writer.writeAll("void"), + .Int => { + if (T.tag() == .u8) { + file.need_stdint = true; + try writer.writeAll("uint8_t"); + } else { + return file.fail(src, "TODO implement int types", .{}); + } + }, else => |e| return file.fail(src, "TODO implement type {}", .{e}), } } @@ -116,6 +124,12 @@ pub fn generate(file: *C, decl: *Decl) !void { if (call.func.cast(ir.Inst.Constant)) |func_inst| { if (func_inst.val.cast(Value.Payload.Function)) |func_val| { const target = func_val.func.owner_decl; + const target_ty = target.typed_value.most_recent.typed_value.ty; + const ret_ty = target_ty.fnReturnType().tag(); + if (ret_ty != .void and ret_ty != .noreturn) { + // TODO: don't do this if we're actually using the value + try writer.print("(void)", .{}); + } const tname = mem.spanZ(target.name); if (file.called.get(tname) == null) { try file.called.put(tname, void{}); @@ -133,6 +147,28 @@ pub fn generate(file: *C, decl: *Decl) !void { return file.fail(decl.src(), "TODO non-constant call inst?", .{}); } }, + .ret => { + const ret_value: *ir.Inst = inst.cast(ir.Inst.Ret).?.args.operand; + const expected_return_type = tv.ty.fnReturnType(); + const value = ret_value.value().?; + if (expected_return_type.eql(ret_value.ty)) { + return file.fail(decl.src(), "TODO return {}", .{expected_return_type}); + } else { + if (expected_return_type.isInt() and ret_value.ty.tag() == .comptime_int) { + if (value.intFitsInType(expected_return_type, file.options.target)) { + if (expected_return_type.intInfo(file.options.target).bits <= 64) { + try writer.print("return {};", .{value.toUnsignedInt()}); + } else { + return file.fail(decl.src(), "TODO return ints > 64 bits", .{}); + } + } else { + return file.fail(decl.src(), "comptime int {} does not fit in {}", .{ value.toUnsignedInt(), expected_return_type }); + } + } else { + return file.fail(decl.src(), "return type mismatch: expected {}, found {}", .{ expected_return_type, ret_value.ty }); + } + } + }, else => |e| { return file.fail(decl.src(), "TODO {}", .{e}); }, diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 82c7cfa607764ccdb652287d8a5d9a3f3131eb53..c0d88b1414daccf5382f91b5bf4c36c117ad2924 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -921,6 +921,11 @@ pub const Type = extern union { }; } + /// Returns true if and only if the type is a fixed-width integer. + pub fn isInt(self: Type) bool { + return self.isSignedInt() or self.isUnsignedInt(); + } + /// Returns true if and only if the type is a fixed-width, signed integer. pub fn isSignedInt(self: Type) bool { return switch (self.tag()) { @@ -975,6 +980,60 @@ pub const Type = extern union { }; } + /// Returns true if and only if the type is a fixed-width, unsigned integer. + pub fn isUnsignedInt(self: Type) bool { + return switch (self.tag()) { + .f16, + .f32, + .f64, + .f128, + .c_longdouble, + .c_void, + .bool, + .void, + .type, + .anyerror, + .comptime_int, + .comptime_float, + .noreturn, + .@"null", + .@"undefined", + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + .function, + .array, + .single_const_pointer, + .single_const_pointer_to_comptime_int, + .array_u8_sentinel_0, + .const_slice_u8, + .int_signed, + .i8, + .isize, + .c_short, + .c_int, + .c_long, + .c_longlong, + .i16, + .i32, + .i64, + => false, + + .int_unsigned, + .u8, + .usize, + .c_ushort, + .c_uint, + .c_ulong, + .c_ulonglong, + .u16, + .u32, + .u64, + => true, + }; + } + /// Asserts the type is an integer. pub fn intInfo(self: Type, target: Target) struct { signed: bool, bits: u16 } { return switch (self.tag()) { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 5961ab4c0c823c5b4cbc2a021994513dfbfe9ff0..e642489b01bf1d5b393753a48adc411b7a77f0df 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -65,26 +65,26 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ ); - //ctx.c("basic return", linux_x64, - // \\fn main() u8 { - // \\ return 103; - // \\} - // \\ - // \\export fn _start() noreturn { - // \\ _ = main(); - // \\} - //, - // \\#include - // \\ - // \\uint8_t main(void); - // \\ - // \\noreturn void _start(void) { - // \\ (void)main(); - // \\} - // \\ - // \\uint8_t main(void) { - // \\ return 103; - // \\} - // \\ - //); + ctx.c("basic return", linux_x64, + \\fn main() u8 { + \\ return 103; + \\} + \\ + \\export fn _start() noreturn { + \\ _ = main(); + \\} + , + \\#include + \\ + \\uint8_t main(void); + \\ + \\noreturn void _start(void) { + \\ (void)main(); + \\} + \\ + \\uint8_t main(void) { + \\ return 103; + \\} + \\ + ); } -- 2.54.0 From a124b027b45384e595c6967adabe8b5033b9c410 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 13 Jul 2020 01:10:13 -0400 Subject: [PATCH 258/295] CBE: Use hasCodeGenBits instead of checking against void and noreturn --- src-self-hosted/cgen.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 81e2b4b7f88f6296aad06ed0c660e8c7c2c4f35d..daac5c328d3593c422971088e323134e16bd9f28 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -126,8 +126,7 @@ pub fn generate(file: *C, decl: *Decl) !void { const target = func_val.func.owner_decl; const target_ty = target.typed_value.most_recent.typed_value.ty; const ret_ty = target_ty.fnReturnType().tag(); - if (ret_ty != .void and ret_ty != .noreturn) { - // TODO: don't do this if we're actually using the value + if (target_ty.fnReturnType().hasCodeGenBits()) { try writer.print("(void)", .{}); } const tname = mem.spanZ(target.name); -- 2.54.0 From 4a462481982e96e1f94e2dd431aeaf8686c0c4b1 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 13 Jul 2020 01:12:49 -0400 Subject: [PATCH 259/295] CBE: Only generate `(void)` for calls whose return values are ignored --- src-self-hosted/cgen.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index daac5c328d3593c422971088e323134e16bd9f28..376ce904e95732bdc130fa57e19c099778d8e6cc 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -120,13 +120,13 @@ pub fn generate(file: *C, decl: *Decl) !void { try writer.writeAll(");"); }, .call => { - const call = inst.cast(ir.Inst.Call).?.args; - if (call.func.cast(ir.Inst.Constant)) |func_inst| { + const call = inst.cast(ir.Inst.Call).?; + if (call.args.func.cast(ir.Inst.Constant)) |func_inst| { if (func_inst.val.cast(Value.Payload.Function)) |func_val| { const target = func_val.func.owner_decl; const target_ty = target.typed_value.most_recent.typed_value.ty; const ret_ty = target_ty.fnReturnType().tag(); - if (target_ty.fnReturnType().hasCodeGenBits()) { + if (target_ty.fnReturnType().hasCodeGenBits() and call.base.isUnused()) { try writer.print("(void)", .{}); } const tname = mem.spanZ(target.name); @@ -139,7 +139,7 @@ pub fn generate(file: *C, decl: *Decl) !void { } else { return file.fail(decl.src(), "TODO non-function call target?", .{}); } - if (call.args.len != 0) { + if (call.args.args.len != 0) { return file.fail(decl.src(), "TODO function arguments", .{}); } } else { -- 2.54.0 From 8d6cadee1622b2548e02c0c63f9a65625d608ada Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Mon, 13 Jul 2020 01:47:44 -0400 Subject: [PATCH 260/295] CBE: Code cleanup --- src-self-hosted/cgen.zig | 288 ++++++++++++++++++++------------------- 1 file changed, 145 insertions(+), 143 deletions(-) diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 376ce904e95732bdc130fa57e19c099778d8e6cc..91dc4a81a4e35e89791d013903c8071d0b823342 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/cgen.zig @@ -1,9 +1,11 @@ const link = @import("link.zig"); const Module = @import("Module.zig"); -const ir = @import("ir.zig"); + +const std = @import("std"); + +const Inst = @import("ir.zig").Inst; const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; -const std = @import("std"); const C = link.File.C; const Decl = Module.Decl; @@ -45,159 +47,159 @@ fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *De const name = try map(file.allocator, mem.spanZ(decl.name)); defer file.allocator.free(name); try writer.print(" {}(", .{name}); - if (tv.ty.fnParamLen() == 0) { - try writer.writeAll("void)"); - } else { + if (tv.ty.fnParamLen() == 0) + try writer.writeAll("void)") + else return file.fail(decl.src(), "TODO implement parameters", .{}); - } } pub fn generate(file: *C, decl: *Decl) !void { + switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { + .Fn => try genFn(file, decl), + .Array => try genArray(file, decl), + else => |e| return file.fail(decl.src(), "TODO {}", .{e}), + } +} + +fn genArray(file: *C, decl: *Decl) !void { + const tv = decl.typed_value.most_recent.typed_value; + // TODO: prevent inline asm constants from being emitted + const name = try map(file.allocator, mem.span(decl.name)); + defer file.allocator.free(name); + if (tv.val.cast(Value.Payload.Bytes)) |payload| + if (tv.ty.arraySentinel()) |sentinel| + if (sentinel.toUnsignedInt() == 0) + try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }) + else + return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{}) + else + return file.fail(decl.src(), "TODO byte arrays without sentinels", .{}) + else + return file.fail(decl.src(), "TODO non-byte arrays", .{}); +} + +fn genFn(file: *C, decl: *Decl) !void { + const writer = file.main.writer(); + const tv = decl.typed_value.most_recent.typed_value; + + try renderFunctionSignature(file, writer, decl); + + try writer.writeAll(" {"); + + const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; + const instructions = func.analysis.success.instructions; + if (instructions.len > 0) { + for (instructions) |inst| { + try writer.writeAll("\n\t"); + switch (inst.tag) { + .assembly => try genAsm(file, inst.cast(Inst.Assembly).?, decl), + .call => try genCall(file, inst.cast(Inst.Call).?, decl), + .ret => try genRet(file, inst.cast(Inst.Ret).?, decl, tv.ty.fnReturnType()), + else => |e| return file.fail(decl.src(), "TODO {}", .{e}), + } + } + try writer.writeAll("\n"); + } + + try writer.writeAll("}\n\n"); +} + +fn genRet(file: *C, inst: *Inst.Ret, decl: *Decl, expected_return_type: Type) !void { + const writer = file.main.writer(); + const ret_value = inst.args.operand; + const value = ret_value.value().?; + if (expected_return_type.eql(ret_value.ty)) + return file.fail(decl.src(), "TODO return {}", .{expected_return_type}) + else if (expected_return_type.isInt() and ret_value.ty.tag() == .comptime_int) + if (value.intFitsInType(expected_return_type, file.options.target)) + if (expected_return_type.intInfo(file.options.target).bits <= 64) + try writer.print("return {};", .{value.toUnsignedInt()}) + else + return file.fail(decl.src(), "TODO return ints > 64 bits", .{}) + else + return file.fail(decl.src(), "comptime int {} does not fit in {}", .{ value.toUnsignedInt(), expected_return_type }) + else + return file.fail(decl.src(), "return type mismatch: expected {}, found {}", .{ expected_return_type, ret_value.ty }); +} + +fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { const writer = file.main.writer(); const header = file.header.writer(); - const tv = decl.typed_value.most_recent.typed_value; - switch (tv.ty.zigTypeTag()) { - .Fn => { - try renderFunctionSignature(file, writer, decl); - - try writer.writeAll(" {"); + if (inst.args.func.cast(Inst.Constant)) |func_inst| { + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const target = func_val.func.owner_decl; + const target_ty = target.typed_value.most_recent.typed_value.ty; + const ret_ty = target_ty.fnReturnType().tag(); + if (target_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) { + try writer.print("(void)", .{}); + } + const tname = mem.spanZ(target.name); + if (file.called.get(tname) == null) { + try file.called.put(tname, void{}); + try renderFunctionSignature(file, header, target); + try header.writeAll(";\n"); + } + try writer.print("{}();", .{tname}); + } else { + return file.fail(decl.src(), "TODO non-function call target?", .{}); + } + if (inst.args.args.len != 0) { + return file.fail(decl.src(), "TODO function arguments", .{}); + } + } else { + return file.fail(decl.src(), "TODO non-constant call inst?", .{}); + } +} - const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; - const instructions = func.analysis.success.instructions; - if (instructions.len > 0) { - for (instructions) |inst| { - try writer.writeAll("\n\t"); - switch (inst.tag) { - .assembly => { - const as = inst.cast(ir.Inst.Assembly).?.args; - for (as.inputs) |i, index| { - if (i[0] == '{' and i[i.len - 1] == '}') { - const reg = i[1 .. i.len - 1]; - const arg = as.args[index]; - if (arg.cast(ir.Inst.Constant)) |c| { - if (c.val.tag() == .int_u64) { - try writer.writeAll("register "); - try renderType(file, writer, arg.ty, decl.src()); - try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() }); - } else { - return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()}); - } - } else { - return file.fail(decl.src(), "TODO non-constant inline asm args", .{}); - } - } else { - return file.fail(decl.src(), "TODO non-explicit inline asm regs", .{}); - } - } - try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); - if (as.output) |o| { - return file.fail(decl.src(), "TODO inline asm output", .{}); - } - if (as.inputs.len > 0) { - if (as.output == null) { - try writer.writeAll(" :"); - } - try writer.writeAll(": "); - for (as.inputs) |i, index| { - if (i[0] == '{' and i[i.len - 1] == '}') { - const reg = i[1 .. i.len - 1]; - const arg = as.args[index]; - if (index > 0) { - try writer.writeAll(", "); - } - if (arg.cast(ir.Inst.Constant)) |c| { - try writer.print("\"\"({}_constant)", .{reg}); - } else { - // This is blocked by the earlier test - unreachable; - } - } else { - // This is blocked by the earlier test - unreachable; - } - } - } - try writer.writeAll(");"); - }, - .call => { - const call = inst.cast(ir.Inst.Call).?; - if (call.args.func.cast(ir.Inst.Constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const target = func_val.func.owner_decl; - const target_ty = target.typed_value.most_recent.typed_value.ty; - const ret_ty = target_ty.fnReturnType().tag(); - if (target_ty.fnReturnType().hasCodeGenBits() and call.base.isUnused()) { - try writer.print("(void)", .{}); - } - const tname = mem.spanZ(target.name); - if (file.called.get(tname) == null) { - try file.called.put(tname, void{}); - try renderFunctionSignature(file, header, target); - try header.writeAll(";\n"); - } - try writer.print("{}();", .{tname}); - } else { - return file.fail(decl.src(), "TODO non-function call target?", .{}); - } - if (call.args.args.len != 0) { - return file.fail(decl.src(), "TODO function arguments", .{}); - } - } else { - return file.fail(decl.src(), "TODO non-constant call inst?", .{}); - } - }, - .ret => { - const ret_value: *ir.Inst = inst.cast(ir.Inst.Ret).?.args.operand; - const expected_return_type = tv.ty.fnReturnType(); - const value = ret_value.value().?; - if (expected_return_type.eql(ret_value.ty)) { - return file.fail(decl.src(), "TODO return {}", .{expected_return_type}); - } else { - if (expected_return_type.isInt() and ret_value.ty.tag() == .comptime_int) { - if (value.intFitsInType(expected_return_type, file.options.target)) { - if (expected_return_type.intInfo(file.options.target).bits <= 64) { - try writer.print("return {};", .{value.toUnsignedInt()}); - } else { - return file.fail(decl.src(), "TODO return ints > 64 bits", .{}); - } - } else { - return file.fail(decl.src(), "comptime int {} does not fit in {}", .{ value.toUnsignedInt(), expected_return_type }); - } - } else { - return file.fail(decl.src(), "return type mismatch: expected {}, found {}", .{ expected_return_type, ret_value.ty }); - } - } - }, - else => |e| { - return file.fail(decl.src(), "TODO {}", .{e}); - }, - } +fn genAsm(file: *C, inst: *Inst.Assembly, decl: *Decl) !void { + const as = inst.args; + const writer = file.main.writer(); + for (as.inputs) |i, index| { + if (i[0] == '{' and i[i.len - 1] == '}') { + const reg = i[1 .. i.len - 1]; + const arg = as.args[index]; + if (arg.cast(Inst.Constant)) |c| { + if (c.val.tag() == .int_u64) { + try writer.writeAll("register "); + try renderType(file, writer, arg.ty, decl.src()); + try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() }); + } else { + return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()}); } - try writer.writeAll("\n"); + } else { + return file.fail(decl.src(), "TODO non-constant inline asm args", .{}); } - - try writer.writeAll("}\n\n"); - }, - .Array => { - // TODO: prevent inline asm constants from being emitted - const name = try map(file.allocator, mem.span(decl.name)); - defer file.allocator.free(name); - if (tv.val.cast(Value.Payload.Bytes)) |payload| { - if (tv.ty.arraySentinel()) |sentinel| { - if (sentinel.toUnsignedInt() == 0) { - try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }); - } else { - return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{}); - } + } else { + return file.fail(decl.src(), "TODO non-explicit inline asm regs", .{}); + } + } + try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); + if (as.output) |o| { + return file.fail(decl.src(), "TODO inline asm output", .{}); + } + if (as.inputs.len > 0) { + if (as.output == null) { + try writer.writeAll(" :"); + } + try writer.writeAll(": "); + for (as.inputs) |i, index| { + if (i[0] == '{' and i[i.len - 1] == '}') { + const reg = i[1 .. i.len - 1]; + const arg = as.args[index]; + if (index > 0) { + try writer.writeAll(", "); + } + if (arg.cast(Inst.Constant)) |c| { + try writer.print("\"\"({}_constant)", .{reg}); } else { - return file.fail(decl.src(), "TODO byte arrays without sentinels", .{}); + // This is blocked by the earlier test + unreachable; } } else { - return file.fail(decl.src(), "TODO non-byte arrays", .{}); + // This is blocked by the earlier test + unreachable; } - }, - else => |e| { - return file.fail(decl.src(), "TODO {}", .{e}); - }, + } } + try writer.writeAll(");"); } -- 2.54.0 From 8fe63d504226d5b181e627361e997a160dee4908 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jul 2020 23:04:00 -0700 Subject: [PATCH 261/295] stage2: peer type resolution with noreturn --- src-self-hosted/Module.zig | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 3f5a833680218d141914b6c74f7ad1fdb1e41506..bb8eca5ccf793eac6fd1dff7386f2fcaac74a3c5 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -3518,9 +3518,26 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { if (instructions.len == 0) return Type.initTag(.noreturn); + if (instructions.len == 1) return instructions[0].ty; - return self.fail(scope, instructions[0].src, "TODO peer type resolution", .{}); + + var prev_inst = instructions[0]; + for (instructions[1..]) |next_inst| { + if (next_inst.ty.eql(prev_inst.ty)) + continue; + if (next_inst.ty.zigTypeTag() == .NoReturn) + continue; + if (prev_inst.ty.zigTypeTag() == .NoReturn) { + prev_inst = next_inst; + continue; + } + + // TODO error notes pointing out each type + return self.fail(scope, next_inst.src, "incompatible types: '{}' and '{}'", .{ prev_inst.ty, next_inst.ty }); + } + + return prev_inst.ty; } fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { -- 2.54.0 From b75a51f94b3b2ba1dc240a27caced8e848db5e10 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jul 2020 23:04:24 -0700 Subject: [PATCH 262/295] stage2: implement function calling convention for calls --- src-self-hosted/codegen.zig | 319 +++++++++++++++++++++--------------- 1 file changed, 186 insertions(+), 133 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index ba91e0726fea45fc82fa998b38aeb1ed2e13a702..487f20d1dbf6aab40291a590f996416ced654253 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -53,10 +53,8 @@ pub fn generateSymbol( const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen()); defer bin_file.allocator.free(param_types); fn_type.fnParamTypes(param_types); - // A parameter may be broken into multiple machine code parameters, so we don't - // know the size up front. - var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len); - defer mc_args.deinit(); + var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len); + defer bin_file.allocator.free(mc_args); var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator); defer { @@ -67,57 +65,6 @@ pub fn generateSymbol( const branch = try branch_stack.addOne(); branch.* = .{}; - switch (fn_type.fnCallingConvention()) { - .Naked => assert(mc_args.items.len == 0), - .Unspecified, .C => { - // Prepare the function parameters - switch (bin_file.options.target.cpu.arch) { - .x86_64 => { - const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; - var next_int_reg: usize = 0; - - for (param_types) |param_type, src_i| { - switch (param_type.zigTypeTag()) { - .Bool, .Int => { - if (next_int_reg >= integer_registers.len) { - try mc_args.append(.{ .stack_offset = branch.next_stack_offset }); - branch.next_stack_offset += @intCast(u32, param_type.abiSize(bin_file.options.target)); - } else { - try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg]) }); - next_int_reg += 1; - } - }, - else => return Result{ - .fail = try ErrorMsg.create( - bin_file.allocator, - src, - "TODO implement function parameters of type {}", - .{@tagName(param_type.zigTypeTag())}, - ), - }, - } - } - }, - else => return Result{ - .fail = try ErrorMsg.create( - bin_file.allocator, - src, - "TODO implement function parameters for {}", - .{bin_file.options.target.cpu.arch}, - ), - }, - } - }, - else => return Result{ - .fail = try ErrorMsg.create( - bin_file.allocator, - src, - "TODO implement {} calling convention", - .{fn_type.fnCallingConvention()}, - ), - }, - } - var function = Function{ .gpa = bin_file.allocator, .target = &bin_file.options.target, @@ -125,11 +72,17 @@ pub fn generateSymbol( .mod_fn = module_fn, .code = code, .err_msg = null, - .args = mc_args.items, + .args = mc_args, .branch_stack = &branch_stack, + .src = src, + }; + + const cc = fn_type.fnCallingConvention(); + branch.max_end_stack = function.resolveParameters(src, cc, param_types, mc_args) catch |err| switch (err) { + error.CodegenFail => return Result{ .fail = function.err_msg.? }, + else => |e| return e, }; - branch.max_end_stack = branch.next_stack_offset; function.gen() catch |err| switch (err) { error.CodegenFail => return Result{ .fail = function.err_msg.? }, else => |e| return e, @@ -235,6 +188,65 @@ const InnerError = error{ CodegenFail, }; +const MCValue = union(enum) { + /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc. + none, + /// Control flow will not allow this value to be observed. + unreach, + /// No more references to this value remain. + dead, + /// A pointer-sized integer that fits in a register. + immediate: u64, + /// The constant was emitted into the code, at this offset. + embedded_in_code: usize, + /// The value is in a target-specific register. The value can + /// be @intToEnum casted to the respective Reg enum. + register: usize, + /// The value is in memory at a hard-coded address. + memory: u64, + /// The value is one of the stack variables. + stack_offset: u64, + /// The value is in the compare flags assuming an unsigned operation, + /// with this operator applied on top of it. + compare_flags_unsigned: std.math.CompareOperator, + /// The value is in the compare flags assuming a signed operation, + /// with this operator applied on top of it. + compare_flags_signed: std.math.CompareOperator, + + fn isMemory(mcv: MCValue) bool { + return switch (mcv) { + .embedded_in_code, .memory, .stack_offset => true, + else => false, + }; + } + + fn isImmediate(mcv: MCValue) bool { + return switch (mcv) { + .immediate => true, + else => false, + }; + } + + fn isMutable(mcv: MCValue) bool { + return switch (mcv) { + .none => unreachable, + .unreach => unreachable, + .dead => unreachable, + + .immediate, + .embedded_in_code, + .memory, + .compare_flags_unsigned, + .compare_flags_signed, + => false, + + .register, + .stack_offset, + => true, + }; + } +}; + const Function = struct { gpa: *Allocator, bin_file: *link.File.Elf, @@ -243,6 +255,7 @@ const Function = struct { code: *std.ArrayList(u8), err_msg: ?*ErrorMsg, args: []MCValue, + src: usize, /// Whenever there is a runtime branch, we push a Branch onto this stack, /// and pop it off when the runtime branch joins. This provides an "overlay" @@ -284,65 +297,6 @@ const Function = struct { size: u32, }; - const MCValue = union(enum) { - /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc. - none, - /// Control flow will not allow this value to be observed. - unreach, - /// No more references to this value remain. - dead, - /// A pointer-sized integer that fits in a register. - immediate: u64, - /// The constant was emitted into the code, at this offset. - embedded_in_code: usize, - /// The value is in a target-specific register. The value can - /// be @intToEnum casted to the respective Reg enum. - register: usize, - /// The value is in memory at a hard-coded address. - memory: u64, - /// The value is one of the stack variables. - stack_offset: u64, - /// The value is in the compare flags assuming an unsigned operation, - /// with this operator applied on top of it. - compare_flags_unsigned: std.math.CompareOperator, - /// The value is in the compare flags assuming a signed operation, - /// with this operator applied on top of it. - compare_flags_signed: std.math.CompareOperator, - - fn isMemory(mcv: MCValue) bool { - return switch (mcv) { - .embedded_in_code, .memory, .stack_offset => true, - else => false, - }; - } - - fn isImmediate(mcv: MCValue) bool { - return switch (mcv) { - .immediate => true, - else => false, - }; - } - - fn isMutable(mcv: MCValue) bool { - return switch (mcv) { - .none => unreachable, - .unreach => unreachable, - .dead => unreachable, - - .immediate, - .embedded_in_code, - .memory, - .compare_flags_unsigned, - .compare_flags_signed, - => false, - - .register, - .stack_offset, - => true, - }; - } - }; - fn gen(self: *Function) !void { switch (self.target.cpu.arch) { .arm => return self.genArch(.arm), @@ -400,7 +354,28 @@ const Function = struct { } fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void { - return self.genBody(self.mod_fn.analysis.success, arch); + try self.code.ensureCapacity(self.code.items.len + 11); + + // push rbp + // mov rbp, rsp + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 }); + + // sub rsp, x + const stack_end = self.branch_stack.items[0].max_end_stack; + if (stack_end > std.math.maxInt(i32)) { + return self.fail(self.src, "too much stack used in call parameters", .{}); + } else if (stack_end > std.math.maxInt(i8)) { + // 48 83 ec xx sub rsp,0x10 + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xec }); + const x = @intCast(u32, stack_end); + mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); + } else if (stack_end != 0) { + // 48 81 ec xx xx xx xx sub rsp,0x80 + const x = @intCast(u8, stack_end); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xec, x }); + } + + try self.genBody(self.mod_fn.analysis.success, arch); } fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void { @@ -593,13 +568,42 @@ const Function = struct { } fn genCall(self: *Function, inst: *ir.Inst.Call, comptime arch: std.Target.Cpu.Arch) !MCValue { + const fn_ty = inst.args.func.ty; + const cc = fn_ty.fnCallingConvention(); + const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen()); + defer self.gpa.free(param_types); + fn_ty.fnParamTypes(param_types); + var mc_args = try self.gpa.alloc(MCValue, param_types.len); + defer self.gpa.free(mc_args); + const stack_byte_count = try self.resolveParameters(inst.base.src, cc, param_types, mc_args); + switch (arch) { - .x86_64, .i386 => { + .x86_64 => { + for (mc_args) |mc_arg, arg_i| { + const arg = inst.args.args[arg_i]; + const arg_mcv = try self.resolveInst(inst.args.args[arg_i]); + switch (mc_arg) { + .none => continue, + .register => |reg| { + try self.genSetReg(arg.src, arch, @intToEnum(Reg(arch), @intCast(u8, reg)), arg_mcv); + // TODO interact with the register allocator to mark the instruction as moved. + }, + .stack_offset => { + // Here we need to emit instructions like this: + // mov qword ptr [rsp + stack_offset], x + return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); + }, + .immediate => unreachable, + .unreach => unreachable, + .dead => unreachable, + .embedded_in_code => unreachable, + .memory => unreachable, + .compare_flags_signed => unreachable, + .compare_flags_unsigned => unreachable, + } + } + if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| { - if (inst.args.args.len != 0) { - return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{}); - } - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { const func = func_val.func; const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?]; @@ -607,17 +611,11 @@ const Function = struct { const ptr_bytes: u64 = @divExact(ptr_bits, 8); const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes); // ff 14 25 xx xx xx xx call [addr] - try self.code.resize(self.code.items.len + 7); - self.code.items[self.code.items.len - 7 ..][0..3].* = [3]u8{ 0xff, 0x14, 0x25 }; - mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], got_addr); - const return_type = func.owner_decl.typed_value.most_recent.typed_value.ty.fnReturnType(); - switch (return_type.zigTypeTag()) { - .Void => return MCValue{ .none = {} }, - .NoReturn => return MCValue{ .unreach = {} }, - else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}), - } + try self.code.ensureCapacity(self.code.items.len + 7); + self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 }); + mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr); } else { - return self.fail(inst.base.src, "TODO implement calling weird function values", .{}); + return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); } } else { return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); @@ -625,6 +623,13 @@ const Function = struct { }, else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), } + + const return_type = fn_ty.fnReturnType(); + switch (return_type.zigTypeTag()) { + .Void => return MCValue{ .none = {} }, + .NoReturn => return MCValue{ .unreach = {} }, + else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}), + } } fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue { @@ -632,9 +637,15 @@ const Function = struct { return self.fail(src, "TODO implement return with non-void operand", .{}); } switch (arch) { - .i386, .x86_64 => { + .i386 => { try self.code.append(0xc3); // ret }, + .x86_64 => { + try self.code.appendSlice(&[_]u8{ + 0x5d, // pop rbp + 0xc3, // ret + }); + }, else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), } return .unreach; @@ -1122,6 +1133,48 @@ const Function = struct { } } + fn resolveParameters( + self: *Function, + src: usize, + cc: std.builtin.CallingConvention, + param_types: []const Type, + results: []MCValue, + ) !u32 { + switch (self.target.cpu.arch) { + .x86_64 => { + switch (cc) { + .Naked => { + assert(results.len == 0); + return 0; + }, + .Unspecified, .C => { + var next_int_reg: usize = 0; + var next_stack_offset: u32 = 0; + + const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; + for (param_types) |ty, i| { + switch (ty.zigTypeTag()) { + .Bool, .Int => { + if (next_int_reg >= integer_registers.len) { + results[i] = .{ .stack_offset = next_stack_offset }; + next_stack_offset += @intCast(u32, ty.abiSize(self.target.*)); + } else { + results[i] = .{ .register = @enumToInt(integer_registers[next_int_reg]) }; + next_int_reg += 1; + } + }, + else => return self.fail(src, "TODO implement function parameters of type {}", .{@tagName(ty.zigTypeTag())}), + } + } + return next_stack_offset; + }, + else => return self.fail(src, "TODO implement function parameters for {}", .{cc}), + } + }, + else => return self.fail(src, "TODO implement C ABI support for {}", .{self.target.cpu.arch}), + } + } + fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } { @setCold(true); assert(self.err_msg == null); -- 2.54.0 From c306392b4404a5a05dcb0958b88b50ecc9c7b6f5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jul 2020 23:12:41 -0700 Subject: [PATCH 263/295] stage2: codegen: more branching support --- src-self-hosted/codegen.zig | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 487f20d1dbf6aab40291a590f996416ced654253..143ad787b7ae095305ca360163b783d1d3a19e4e 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -780,14 +780,22 @@ const Function = struct { } fn genBr(self: *Function, inst: *ir.Inst.Br, comptime arch: std.Target.Cpu.Arch) !MCValue { + if (!inst.args.operand.ty.hasCodeGenBits()) + return self.brVoid(inst.base.src, inst.args.block, arch); + + const operand = try self.resolveInst(inst.args.operand); switch (arch) { else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}), } } fn genBrVoid(self: *Function, inst: *ir.Inst.BrVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { + return self.brVoid(inst.base.src, inst.args.block, arch); + } + + fn brVoid(self: *Function, src: usize, block: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { // Emit a jump with a relocation. It will be patched up after the block ends. - try inst.args.block.codegen.relocs.ensureCapacity(self.gpa, inst.args.block.codegen.relocs.items.len + 1); + try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1); switch (arch) { .i386, .x86_64 => { @@ -796,9 +804,9 @@ const Function = struct { try self.code.resize(self.code.items.len + 5); self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 // Leave the jump offset undefined - inst.args.block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); + block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); }, - else => return self.fail(inst.base.src, "TODO implement brvoid for {}", .{self.target.cpu.arch}), + else => return self.fail(src, "TODO implement brvoid for {}", .{self.target.cpu.arch}), } return .none; } -- 2.54.0 From 25b1c00c72b51ef9e011867b3fc4f37b3e216223 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jul 2020 23:50:25 -0700 Subject: [PATCH 264/295] stage2: add implicit return void where applicable --- src-self-hosted/Module.zig | 10 +++++++-- src-self-hosted/ir.zig | 30 ------------------------- src-self-hosted/type.zig | 4 ++++ src-self-hosted/zir.zig | 46 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 32 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index bb8eca5ccf793eac6fd1dff7386f2fcaac74a3c5..03307ab96de1f5768873af728f1e7ac55166630a 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1210,6 +1210,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { try self.astGenBlock(&gen_scope.base, body_block); + const last_inst = gen_scope.instructions.items[gen_scope.instructions.items.len - 1]; + if (!last_inst.tag.isNoReturn()) { + const src = tree.token_locs[body_block.rbrace].start; + _ = try self.addZIRInst(&gen_scope.base, src, zir.Inst.ReturnVoid, .{}, .{}); + } + const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); fn_zir.* = .{ .body = .{ @@ -2686,7 +2692,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr // Blocks must terminate with noreturn instruction. assert(child_block.instructions.items.len != 0); - assert(child_block.instructions.items[child_block.instructions.items.len - 1].tag.isNoReturn()); + assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); // Need to set the type and emit the Block instruction. This allows machine code generation // to emit a jump instruction to after the block when it encounters the break. @@ -3271,7 +3277,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner defer false_block.instructions.deinit(self.gpa); try self.analyzeBody(&false_block.base, inst.positionals.false_body); - return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){ + return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.noreturn), Inst.CondBr, Inst.Args(Inst.CondBr){ .condition = cond, .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) }, .false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) }, diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 094b877b530767c2be20b1e02a4a6639427e91d9..74da3430a76f19c94a31ceb64a75b140c23addcb 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -60,36 +60,6 @@ pub const Inst = struct { retvoid, sub, unreach, - - /// Returns whether the instruction is one of the control flow "noreturn" types. - /// Function calls do not count. When ZIR is generated, the compiler automatically - /// emits an `Unreach` after a function call with the `noreturn` return type. - pub fn isNoReturn(tag: Tag) bool { - return switch (tag) { - .add, - .arg, - .assembly, - .bitcast, - .block, - .breakpoint, - .call, - .cmp, - .constant, - .isnonnull, - .isnull, - .ptrtoint, - .sub, - => false, - - .br, - .brvoid, - .condbr, - .ret, - .retvoid, - .unreach, - => true, - }; - } }; pub fn cast(base: *Inst, comptime T: type) ?*T { diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index c0d88b1414daccf5382f91b5bf4c36c117ad2924..512dd631a75eee974829b8c7e9e2990689e99dc1 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -468,6 +468,10 @@ pub const Type = extern union { }; } + pub fn isNoReturn(self: Type) bool { + return self.zigTypeTag() == .NoReturn; + } + /// Asserts that hasCodeGenBits() is true. pub fn abiAlignment(self: Type, target: Target) u32 { return switch (self.tag()) { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 8faf2486362e0d4e475ff5b7a174a9f275c7b3e6..0dfbdd20a0bbd10e79cfbbe4baf6daa53575c655 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -81,6 +81,52 @@ pub const Inst = struct { condbr, isnull, isnonnull, + + /// Returns whether the instruction is one of the control flow "noreturn" types. + /// Function calls do not count. + pub fn isNoReturn(tag: Tag) bool { + return switch (tag) { + .arg, + .block, + .breakpoint, + .call, + .@"const", + .declref, + .declref_str, + .declval, + .declval_in_module, + .str, + .int, + .inttype, + .ptrtoint, + .fieldptr, + .deref, + .as, + .@"asm", + .@"fn", + .fntype, + .@"export", + .primitive, + .intcast, + .bitcast, + .elemptr, + .add, + .sub, + .cmp, + .isnull, + .isnonnull, + => false, + + .condbr, + .@"unreachable", + .@"return", + .returnvoid, + .@"break", + .breakvoid, + .compileerror, + => true, + }; + } }; pub fn TagToType(tag: Tag) type { -- 2.54.0 From 08154c0deb653e016d6eb285c85094048eeded89 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2020 00:28:11 -0700 Subject: [PATCH 265/295] stage2: add retvoid support to CBE --- src-self-hosted/Module.zig | 5 +++-- src-self-hosted/{cgen.zig => codegen/c.zig} | 15 ++++++++------- src-self-hosted/link.zig | 4 ++-- test/stage2/cbe.zig | 1 + 4 files changed, 14 insertions(+), 11 deletions(-) rename src-self-hosted/{cgen.zig => codegen/c.zig} (95%) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 03307ab96de1f5768873af728f1e7ac55166630a..975158c477bedd7b68b2bfeca0cca8ef74e595d7 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1210,8 +1210,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { try self.astGenBlock(&gen_scope.base, body_block); - const last_inst = gen_scope.instructions.items[gen_scope.instructions.items.len - 1]; - if (!last_inst.tag.isNoReturn()) { + if (!fn_type.fnReturnType().isNoReturn() and (gen_scope.instructions.items.len == 0 or + !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) + { const src = tree.token_locs[body_block.rbrace].start; _ = try self.addZIRInst(&gen_scope.base, src, zir.Inst.ReturnVoid, .{}, .{}); } diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/codegen/c.zig similarity index 95% rename from src-self-hosted/cgen.zig rename to src-self-hosted/codegen/c.zig index 91dc4a81a4e35e89791d013903c8071d0b823342..ebc4ff7e1a89dc0658ff88ab56d66c7af4b16c20 100644 --- a/src-self-hosted/cgen.zig +++ b/src-self-hosted/codegen/c.zig @@ -1,11 +1,11 @@ -const link = @import("link.zig"); -const Module = @import("Module.zig"); - const std = @import("std"); -const Inst = @import("ir.zig").Inst; -const Value = @import("value.zig").Value; -const Type = @import("type.zig").Type; +const link = @import("../link.zig"); +const Module = @import("../Module.zig"); + +const Inst = @import("../ir.zig").Inst; +const Value = @import("../value.zig").Value; +const Type = @import("../type.zig").Type; const C = link.File.C; const Decl = Module.Decl; @@ -95,7 +95,8 @@ fn genFn(file: *C, decl: *Decl) !void { .assembly => try genAsm(file, inst.cast(Inst.Assembly).?, decl), .call => try genCall(file, inst.cast(Inst.Call).?, decl), .ret => try genRet(file, inst.cast(Inst.Ret).?, decl, tv.ty.fnReturnType()), - else => |e| return file.fail(decl.src(), "TODO {}", .{e}), + .retvoid => try file.main.writer().print("return;", .{}), + else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}), } } try writer.writeAll("\n"); diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 64328d4df5f598b562e67130b0ab0b7a970de7c2..776f6de31cc0458aab111c2cc2c4e69317c59568 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -7,7 +7,7 @@ const Module = @import("Module.zig"); const fs = std.fs; const elf = std.elf; const codegen = @import("codegen.zig"); -const cgen = @import("cgen.zig"); +const c_codegen = @import("codegen/c.zig"); const default_entry_addr = 0x8000000; @@ -259,7 +259,7 @@ pub const File = struct { } pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { - cgen.generate(self, decl) catch |err| { + c_codegen.generate(self, decl) catch |err| { if (err == error.CGenFailure) { try module.failed_decls.put(module.gpa, decl, self.error_msg); } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index e642489b01bf1d5b393753a48adc411b7a77f0df..e817029b839b1d4ac8ca8d56c618f04c75f8219d 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -62,6 +62,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ register size_t rax_constant __asm__("rax") = 231; \\ register size_t rdi_constant __asm__("rdi") = 0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\ return; \\} \\ ); -- 2.54.0 From c94652a2fd210fe4f007dbdd47c9e55b38e482fb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2020 00:31:55 -0700 Subject: [PATCH 266/295] stage2: add new test case --- test/stage2/compare_output.zig | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 1f2853707a3e045fcfe5cb7d4add303c0e8f75fd..27c9f48d72e43c4a1003dad3de8b409dbbe42b94 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -120,7 +120,7 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.exe("adding numbers", linux_x64); + var case = ctx.exe("adding numbers at comptime", linux_x64); case.addCompareOutput( \\export fn _start() noreturn { \\ asm volatile ("syscall" @@ -143,4 +143,31 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); } + + { + var case = ctx.exe("adding numbers at runtime", linux_x64); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ add(3, 4); + \\ + \\ exit(); + \\} + \\ + \\fn add(a: u32, b: u32) void { + \\ if (a + b != 7) unreachable; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + } } -- 2.54.0 From 204f61d7f5aa7b5e27bdaaab9237fca17096ad3e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2020 15:34:31 -0700 Subject: [PATCH 267/295] stage2: Module: use StringHashMapUnmanaged --- src-self-hosted/Module.zig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 975158c477bedd7b68b2bfeca0cca8ef74e595d7..089e570edabf649556ff6d8a1ecca0ab1b685aff 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -36,7 +36,7 @@ bin_file_path: []const u8, decl_exports: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{}, /// We track which export is associated with the given symbol name for quick /// detection of symbol collisions. -symbol_exports: std.StringHashMap(*Export), +symbol_exports: std.StringHashMapUnmanaged(*Export) = .{}, /// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that /// is performing the export of another Decl. @@ -769,7 +769,6 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module { .bin_file_path = options.bin_file_path, .bin_file = bin_file, .optimize_mode = options.optimize_mode, - .symbol_exports = std.StringHashMap(*Export).init(gpa), .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), .keep_source_files_loaded = options.keep_source_files_loaded, }; @@ -812,7 +811,7 @@ pub fn deinit(self: *Module) void { } self.export_owners.deinit(gpa); - self.symbol_exports.deinit(); + self.symbol_exports.deinit(gpa); self.root_scope.destroy(gpa); self.* = undefined; } @@ -2305,7 +2304,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const return; } - try self.symbol_exports.putNoClobber(symbol_name, new_export); + try self.symbol_exports.putNoClobber(self.gpa, symbol_name, new_export); self.bin_file.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => { -- 2.54.0 From a8d8ce9733a6e859fef3372008ae81a0b57027ec Mon Sep 17 00:00:00 2001 From: Paul Espinosa Date: Mon, 13 Jul 2020 15:29:46 +0700 Subject: [PATCH 268/295] Use Writer for Language Reference Hello World Example `OutStream` has been deprecated, so the "Hello, World!" example has been updated to use `Writer`. --- doc/langref.html.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 015865ee3bfdfc73bf664794462b75d2b7871e2d..340cfdd303003417f993bde5df2aa4d4a0db6f95 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -231,7 +231,7 @@ const std = @import("std"); pub fn main() !void { - const stdout = std.io.getStdOut().outStream(); + const stdout = std.io.getStdOut().writer(); try stdout.print("Hello, {}!\n", .{"world"}); } {#code_end#} -- 2.54.0 From bc900cdeaf3434acc150a8db203eb5a485c09b67 Mon Sep 17 00:00:00 2001 From: Nathan Michaels Date: Sun, 5 Jul 2020 18:39:28 -0400 Subject: [PATCH 269/295] Document top-level doc comments, per #2288. --- doc/langref.html.in | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/langref.html.in b/doc/langref.html.in index 6fb6557ca6cef87d1adf99a120f7b286eadfe1be..f4320df1b58f895ba4d573b57136fdd7a78f5463 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -392,6 +392,18 @@ const Timestamp = struct { in the middle of an expression, or just before a non-doc comment.

{#header_close#} + {#header_open|Top-Level Doc Comments#} +

User documentation that doesn't belong to whatever + immediately follows it, like package-level documentation, goes + in top-level doc comments. A top-level doc comment is one that + begins with two slashes and an exclamation point: + {#syntax#}//!{#endsyntax#}.

+ {#code_begin|syntax|tldoc_comments#} +//! This module provides functions for retrieving the current date and +//! time with varying degrees of precision and accuracy. It does not +//! depend on libc, but will use functions from it if available. + {#code_end#} + {#header_close#} {#header_close#} {#header_open|Values#} {#code_begin|exe|values#} -- 2.54.0 From 14cef9dd3d8074b0dc2ee48a0905300ce6317aed Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2020 20:13:51 -0700 Subject: [PATCH 270/295] stage2 parser: split out PrefixOp into separate AST Nodes This is part of a larger effort to improve the memory layout of AST nodes of the self-hosted parser to reduce wasted memory. Reduction of wasted memory also translates to improved performance because of fewer memory allocations, and fewer cache misses. Compared to master, when running `zig fmt` on the std lib: * cache-misses: 801,829 => 768,624 * instructions: 3,234,877,167 => 3,232,075,022 * peak memory: 81480 KB => 75964 KB --- lib/std/zig/ast.zig | 246 +++++++++++++------- lib/std/zig/parse.zig | 348 ++++++++++++++++++++--------- lib/std/zig/render.zig | 382 +++++++++++++++++++------------- src-self-hosted/Module.zig | 8 + src-self-hosted/translate_c.zig | 167 +++++++------- 5 files changed, 727 insertions(+), 424 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 0502f5bb6488219164bfa7a603a10ff88cc1ae6d..1de00dd0b073f46ac250da982a2e0712358b0e37 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -410,7 +410,20 @@ pub const Node = struct { // Operators InfixOp, - PrefixOp, + AddressOf, + Await, + BitNot, + BoolNot, + OptionalType, + Negation, + NegationWrap, + Resume, + Try, + ArrayType, + /// ArrayType but has a sentinel node. + ArrayTypeSentinel, + PtrType, + SliceType, /// Not all suffix operations are under this tag. To save memory, some /// suffix operations have dedicated Node tags. SuffixOp, @@ -1797,85 +1810,152 @@ pub const Node = struct { } }; - pub const PrefixOp = struct { - base: Node = Node{ .id = .PrefixOp }, + pub const AddressOf = SimplePrefixOp(.AddressOf); + pub const Await = SimplePrefixOp(.Await); + pub const BitNot = SimplePrefixOp(.BitNot); + pub const BoolNot = SimplePrefixOp(.BoolNot); + pub const OptionalType = SimplePrefixOp(.OptionalType); + pub const Negation = SimplePrefixOp(.Negation); + pub const NegationWrap = SimplePrefixOp(.NegationWrap); + pub const Resume = SimplePrefixOp(.Resume); + pub const Try = SimplePrefixOp(.Try); + + pub fn SimplePrefixOp(comptime tag: Id) type { + return struct { + base: Node = Node{ .id = tag }, + op_token: TokenIndex, + rhs: *Node, + + const Self = @This(); + + pub fn iterate(self: *const Self, index: usize) ?*Node { + if (index == 0) return self.rhs; + return null; + } + + pub fn firstToken(self: *const Self) TokenIndex { + return self.op_token; + } + + pub fn lastToken(self: *const Self) TokenIndex { + return self.rhs.lastToken(); + } + }; + } + + pub const ArrayType = struct { + base: Node = Node{ .id = .ArrayType }, op_token: TokenIndex, - op: Op, rhs: *Node, + len_expr: *Node, - pub const Op = union(enum) { - AddressOf, - ArrayType: ArrayInfo, - Await, - BitNot, - BoolNot, - OptionalType, - Negation, - NegationWrap, - Resume, - PtrType: PtrInfo, - SliceType: PtrInfo, - Try, - }; + pub fn iterate(self: *const ArrayType, index: usize) ?*Node { + var i = index; - pub const ArrayInfo = struct { - len_expr: *Node, - sentinel: ?*Node, - }; + if (i < 1) return self.len_expr; + i -= 1; - pub const PtrInfo = struct { - allowzero_token: ?TokenIndex = null, - align_info: ?Align = null, - const_token: ?TokenIndex = null, - volatile_token: ?TokenIndex = null, - sentinel: ?*Node = null, - - pub const Align = struct { - node: *Node, - bit_range: ?BitRange, - - pub const BitRange = struct { - start: *Node, - end: *Node, - }; - }; - }; + if (i < 1) return self.rhs; + i -= 1; + + return null; + } + + pub fn firstToken(self: *const ArrayType) TokenIndex { + return self.op_token; + } + + pub fn lastToken(self: *const ArrayType) TokenIndex { + return self.rhs.lastToken(); + } + }; + + pub const ArrayTypeSentinel = struct { + base: Node = Node{ .id = .ArrayTypeSentinel }, + op_token: TokenIndex, + rhs: *Node, + len_expr: *Node, + sentinel: *Node, + + pub fn iterate(self: *const ArrayTypeSentinel, index: usize) ?*Node { + var i = index; + + if (i < 1) return self.len_expr; + i -= 1; + + if (i < 1) return self.sentinel; + i -= 1; + + if (i < 1) return self.rhs; + i -= 1; + + return null; + } + + pub fn firstToken(self: *const ArrayTypeSentinel) TokenIndex { + return self.op_token; + } + + pub fn lastToken(self: *const ArrayTypeSentinel) TokenIndex { + return self.rhs.lastToken(); + } + }; - pub fn iterate(self: *const PrefixOp, index: usize) ?*Node { + pub const PtrType = struct { + base: Node = Node{ .id = .PtrType }, + op_token: TokenIndex, + rhs: *Node, + /// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents + /// one of these possibly-null things. Then we have them directly follow the PtrType in memory. + ptr_info: PtrInfo = .{}, + + pub fn iterate(self: *const PtrType, index: usize) ?*Node { var i = index; - switch (self.op) { - .PtrType, .SliceType => |addr_of_info| { - if (addr_of_info.sentinel) |sentinel| { - if (i < 1) return sentinel; - i -= 1; - } - - if (addr_of_info.align_info) |align_info| { - if (i < 1) return align_info.node; - i -= 1; - } - }, - - .ArrayType => |array_info| { - if (i < 1) return array_info.len_expr; - i -= 1; - if (array_info.sentinel) |sentinel| { - if (i < 1) return sentinel; - i -= 1; - } - }, - - .AddressOf, - .Await, - .BitNot, - .BoolNot, - .OptionalType, - .Negation, - .NegationWrap, - .Try, - .Resume, - => {}, + if (self.ptr_info.sentinel) |sentinel| { + if (i < 1) return sentinel; + i -= 1; + } + + if (self.ptr_info.align_info) |align_info| { + if (i < 1) return align_info.node; + i -= 1; + } + + if (i < 1) return self.rhs; + i -= 1; + + return null; + } + + pub fn firstToken(self: *const PtrType) TokenIndex { + return self.op_token; + } + + pub fn lastToken(self: *const PtrType) TokenIndex { + return self.rhs.lastToken(); + } + }; + + pub const SliceType = struct { + base: Node = Node{ .id = .SliceType }, + op_token: TokenIndex, + rhs: *Node, + /// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents + /// one of these possibly-null things. Then we have them directly follow the SliceType in memory. + ptr_info: PtrInfo = .{}, + + pub fn iterate(self: *const SliceType, index: usize) ?*Node { + var i = index; + + if (self.ptr_info.sentinel) |sentinel| { + if (i < 1) return sentinel; + i -= 1; + } + + if (self.ptr_info.align_info) |align_info| { + if (i < 1) return align_info.node; + i -= 1; } if (i < 1) return self.rhs; @@ -1884,11 +1964,11 @@ pub const Node = struct { return null; } - pub fn firstToken(self: *const PrefixOp) TokenIndex { + pub fn firstToken(self: *const SliceType) TokenIndex { return self.op_token; } - pub fn lastToken(self: *const PrefixOp) TokenIndex { + pub fn lastToken(self: *const SliceType) TokenIndex { return self.rhs.lastToken(); } }; @@ -2797,6 +2877,24 @@ pub const Node = struct { }; }; +pub const PtrInfo = struct { + allowzero_token: ?TokenIndex = null, + align_info: ?Align = null, + const_token: ?TokenIndex = null, + volatile_token: ?TokenIndex = null, + sentinel: ?*Node = null, + + pub const Align = struct { + node: *Node, + bit_range: ?BitRange = null, + + pub const BitRange = struct { + start: *Node, + end: *Node, + }; + }; +}; + test "iterate" { var root = Node.Root{ .base = Node{ .id = Node.Id.Root }, diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index f5f0c10826c2b138701fdc89a8fc636ed97f58ab..d46833c23f64ee7941b4f8be728202540b3c5a1f 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1120,10 +1120,9 @@ const Parser = struct { const expr_node = try p.expectNode(parseExpr, .{ .ExpectedExpr = .{ .token = p.tok_i }, }); - const node = try p.arena.allocator.create(Node.PrefixOp); + const node = try p.arena.allocator.create(Node.Resume); node.* = .{ .op_token = token, - .op = .Resume, .rhs = expr_node, }; return &node.base; @@ -2413,24 +2412,25 @@ const Parser = struct { /// / KEYWORD_await fn parsePrefixOp(p: *Parser) !?*Node { const token = p.nextToken(); - const op: Node.PrefixOp.Op = switch (p.token_ids[token]) { - .Bang => .BoolNot, - .Minus => .Negation, - .Tilde => .BitNot, - .MinusPercent => .NegationWrap, - .Ampersand => .AddressOf, - .Keyword_try => .Try, - .Keyword_await => .Await, + switch (p.token_ids[token]) { + .Bang => return p.allocSimplePrefixOp(.BoolNot, token), + .Minus => return p.allocSimplePrefixOp(.Negation, token), + .Tilde => return p.allocSimplePrefixOp(.BitNot, token), + .MinusPercent => return p.allocSimplePrefixOp(.NegationWrap, token), + .Ampersand => return p.allocSimplePrefixOp(.AddressOf, token), + .Keyword_try => return p.allocSimplePrefixOp(.Try, token), + .Keyword_await => return p.allocSimplePrefixOp(.Await, token), else => { p.putBackToken(token); return null; }, - }; + } + } - const node = try p.arena.allocator.create(Node.PrefixOp); + fn allocSimplePrefixOp(p: *Parser, comptime tag: Node.Id, token: TokenIndex) !?*Node { + const node = try p.arena.allocator.create(Node.SimplePrefixOp(tag)); node.* = .{ .op_token = token, - .op = op, .rhs = undefined, // set by caller }; return &node.base; @@ -2450,19 +2450,14 @@ const Parser = struct { /// / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* fn parsePrefixTypeOp(p: *Parser) !?*Node { if (p.eatToken(.QuestionMark)) |token| { - const node = try p.arena.allocator.create(Node.PrefixOp); + const node = try p.arena.allocator.create(Node.OptionalType); node.* = .{ .op_token = token, - .op = .OptionalType, .rhs = undefined, // set by caller }; return &node.base; } - // TODO: Returning a AnyFrameType instead of PrefixOp makes casting and setting .rhs or - // .return_type more difficult for the caller (see parsePrefixOpExpr helper). - // Consider making the AnyFrameType a member of PrefixOp and add a - // PrefixOp.AnyFrameType variant? if (p.eatToken(.Keyword_anyframe)) |token| { const arrow = p.eatToken(.Arrow) orelse { p.putBackToken(token); @@ -2482,11 +2477,15 @@ const Parser = struct { if (try p.parsePtrTypeStart()) |node| { // If the token encountered was **, there will be two nodes instead of one. // The attributes should be applied to the rightmost operator. - const prefix_op = node.cast(Node.PrefixOp).?; - var ptr_info = if (p.token_ids[prefix_op.op_token] == .AsteriskAsterisk) - &prefix_op.rhs.cast(Node.PrefixOp).?.op.PtrType + var ptr_info = if (node.cast(Node.PtrType)) |ptr_type| + if (p.token_ids[ptr_type.op_token] == .AsteriskAsterisk) + &ptr_type.rhs.cast(Node.PtrType).?.ptr_info + else + &ptr_type.ptr_info + else if (node.cast(Node.SliceType)) |slice_type| + &slice_type.ptr_info else - &prefix_op.op.PtrType; + unreachable; while (true) { if (p.eatToken(.Keyword_align)) |align_token| { @@ -2505,7 +2504,7 @@ const Parser = struct { .ExpectedIntegerLiteral = .{ .token = p.tok_i }, }); - break :bit_range_value Node.PrefixOp.PtrInfo.Align.BitRange{ + break :bit_range_value ast.PtrInfo.Align.BitRange{ .start = range_start, .end = range_end, }; @@ -2519,7 +2518,7 @@ const Parser = struct { continue; } - ptr_info.align_info = Node.PrefixOp.PtrInfo.Align{ + ptr_info.align_info = ast.PtrInfo.Align{ .node = expr_node, .bit_range = bit_range, }; @@ -2563,58 +2562,54 @@ const Parser = struct { } if (try p.parseArrayTypeStart()) |node| { - switch (node.cast(Node.PrefixOp).?.op) { - .ArrayType => {}, - .SliceType => |*slice_type| { - // Collect pointer qualifiers in any order, but disallow duplicates - while (true) { - if (try p.parseByteAlign()) |align_expr| { - if (slice_type.align_info != null) { - try p.errors.append(p.gpa, .{ - .ExtraAlignQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - slice_type.align_info = Node.PrefixOp.PtrInfo.Align{ - .node = align_expr, - .bit_range = null, - }; + if (node.cast(Node.SliceType)) |slice_type| { + // Collect pointer qualifiers in any order, but disallow duplicates + while (true) { + if (try p.parseByteAlign()) |align_expr| { + if (slice_type.ptr_info.align_info != null) { + try p.errors.append(p.gpa, .{ + .ExtraAlignQualifier = .{ .token = p.tok_i - 1 }, + }); continue; } - if (p.eatToken(.Keyword_const)) |const_token| { - if (slice_type.const_token != null) { - try p.errors.append(p.gpa, .{ - .ExtraConstQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - slice_type.const_token = const_token; + slice_type.ptr_info.align_info = ast.PtrInfo.Align{ + .node = align_expr, + .bit_range = null, + }; + continue; + } + if (p.eatToken(.Keyword_const)) |const_token| { + if (slice_type.ptr_info.const_token != null) { + try p.errors.append(p.gpa, .{ + .ExtraConstQualifier = .{ .token = p.tok_i - 1 }, + }); continue; } - if (p.eatToken(.Keyword_volatile)) |volatile_token| { - if (slice_type.volatile_token != null) { - try p.errors.append(p.gpa, .{ - .ExtraVolatileQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - slice_type.volatile_token = volatile_token; + slice_type.ptr_info.const_token = const_token; + continue; + } + if (p.eatToken(.Keyword_volatile)) |volatile_token| { + if (slice_type.ptr_info.volatile_token != null) { + try p.errors.append(p.gpa, .{ + .ExtraVolatileQualifier = .{ .token = p.tok_i - 1 }, + }); continue; } - if (p.eatToken(.Keyword_allowzero)) |allowzero_token| { - if (slice_type.allowzero_token != null) { - try p.errors.append(p.gpa, .{ - .ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 }, - }); - continue; - } - slice_type.allowzero_token = allowzero_token; + slice_type.ptr_info.volatile_token = volatile_token; + continue; + } + if (p.eatToken(.Keyword_allowzero)) |allowzero_token| { + if (slice_type.ptr_info.allowzero_token != null) { + try p.errors.append(p.gpa, .{ + .ExtraAllowZeroQualifier = .{ .token = p.tok_i - 1 }, + }); continue; } - break; + slice_type.ptr_info.allowzero_token = allowzero_token; + continue; } - }, - else => unreachable, + break; + } } return node; } @@ -2728,29 +2723,32 @@ const Parser = struct { null; const rbracket = try p.expectToken(.RBracket); - const op: Node.PrefixOp.Op = if (expr) |len_expr| - .{ - .ArrayType = .{ + if (expr) |len_expr| { + if (sentinel) |s| { + const node = try p.arena.allocator.create(Node.ArrayTypeSentinel); + node.* = .{ + .op_token = lbracket, + .rhs = undefined, // set by caller .len_expr = len_expr, - .sentinel = sentinel, - }, + .sentinel = s, + }; + return &node.base; + } else { + const node = try p.arena.allocator.create(Node.ArrayType); + node.* = .{ + .op_token = lbracket, + .rhs = undefined, // set by caller + .len_expr = len_expr, + }; + return &node.base; } - else - .{ - .SliceType = Node.PrefixOp.PtrInfo{ - .allowzero_token = null, - .align_info = null, - .const_token = null, - .volatile_token = null, - .sentinel = sentinel, - }, - }; + } - const node = try p.arena.allocator.create(Node.PrefixOp); + const node = try p.arena.allocator.create(Node.SliceType); node.* = .{ .op_token = lbracket, - .op = op, .rhs = undefined, // set by caller + .ptr_info = .{ .sentinel = sentinel }, }; return &node.base; } @@ -2768,28 +2766,26 @@ const Parser = struct { }) else null; - const node = try p.arena.allocator.create(Node.PrefixOp); + const node = try p.arena.allocator.create(Node.PtrType); node.* = .{ .op_token = asterisk, - .op = .{ .PtrType = .{ .sentinel = sentinel } }, .rhs = undefined, // set by caller + .ptr_info = .{ .sentinel = sentinel }, }; return &node.base; } if (p.eatToken(.AsteriskAsterisk)) |double_asterisk| { - const node = try p.arena.allocator.create(Node.PrefixOp); + const node = try p.arena.allocator.create(Node.PtrType); node.* = .{ .op_token = double_asterisk, - .op = .{ .PtrType = .{} }, .rhs = undefined, // set by caller }; // Special case for **, which is its own token - const child = try p.arena.allocator.create(Node.PrefixOp); + const child = try p.arena.allocator.create(Node.PtrType); child.* = .{ .op_token = double_asterisk, - .op = .{ .PtrType = .{} }, .rhs = undefined, // set by caller }; node.rhs = &child.base; @@ -2808,10 +2804,9 @@ const Parser = struct { p.putBackToken(ident); } else { _ = try p.expectToken(.RBracket); - const node = try p.arena.allocator.create(Node.PrefixOp); + const node = try p.arena.allocator.create(Node.PtrType); node.* = .{ .op_token = lbracket, - .op = .{ .PtrType = .{} }, .rhs = undefined, // set by caller }; return &node.base; @@ -2824,11 +2819,11 @@ const Parser = struct { else null; _ = try p.expectToken(.RBracket); - const node = try p.arena.allocator.create(Node.PrefixOp); + const node = try p.arena.allocator.create(Node.PtrType); node.* = .{ .op_token = lbracket, - .op = .{ .PtrType = .{ .sentinel = sentinel } }, .rhs = undefined, // set by caller + .ptr_info = .{ .sentinel = sentinel }, }; return &node.base; } @@ -3146,10 +3141,9 @@ const Parser = struct { fn parseTry(p: *Parser) !?*Node { const token = p.eatToken(.Keyword_try) orelse return null; - const node = try p.arena.allocator.create(Node.PrefixOp); + const node = try p.arena.allocator.create(Node.Try); node.* = .{ .op_token = token, - .op = .Try, .rhs = undefined, // set by caller }; return &node.base; @@ -3228,15 +3222,87 @@ const Parser = struct { var rightmost_op = first_op; while (true) { switch (rightmost_op.id) { - .PrefixOp => { - var prefix_op = rightmost_op.cast(Node.PrefixOp).?; + .AddressOf => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.AddressOf).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .Await => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.Await).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .BitNot => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.BitNot).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .BoolNot => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.BoolNot).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .OptionalType => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.OptionalType).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .Negation => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.Negation).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .NegationWrap => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.NegationWrap).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .Resume => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.Resume).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .Try => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.Try).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .ArrayType => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.ArrayType).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .ArrayTypeSentinel => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.ArrayTypeSentinel).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .SliceType => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.SliceType).?.rhs = rhs; + rightmost_op = rhs; + } else break; + }, + .PtrType => { + var ptr_type = rightmost_op.cast(Node.PtrType).?; // If the token encountered was **, there will be two nodes - if (p.token_ids[prefix_op.op_token] == .AsteriskAsterisk) { - rightmost_op = prefix_op.rhs; - prefix_op = rightmost_op.cast(Node.PrefixOp).?; + if (p.token_ids[ptr_type.op_token] == .AsteriskAsterisk) { + rightmost_op = ptr_type.rhs; + ptr_type = rightmost_op.cast(Node.PtrType).?; } if (try opParseFn(p)) |rhs| { - prefix_op.rhs = rhs; + ptr_type.rhs = rhs; rightmost_op = rhs; } else break; }, @@ -3253,8 +3319,80 @@ const Parser = struct { // If any prefix op existed, a child node on the RHS is required switch (rightmost_op.id) { - .PrefixOp => { - const prefix_op = rightmost_op.cast(Node.PrefixOp).?; + .AddressOf => { + const prefix_op = rightmost_op.cast(Node.AddressOf).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .Await => { + const prefix_op = rightmost_op.cast(Node.Await).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .BitNot => { + const prefix_op = rightmost_op.cast(Node.BitNot).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .BoolNot => { + const prefix_op = rightmost_op.cast(Node.BoolNot).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .OptionalType => { + const prefix_op = rightmost_op.cast(Node.OptionalType).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .Negation => { + const prefix_op = rightmost_op.cast(Node.Negation).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .NegationWrap => { + const prefix_op = rightmost_op.cast(Node.NegationWrap).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .Resume => { + const prefix_op = rightmost_op.cast(Node.Resume).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .Try => { + const prefix_op = rightmost_op.cast(Node.Try).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .ArrayType => { + const prefix_op = rightmost_op.cast(Node.ArrayType).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .ArrayTypeSentinel => { + const prefix_op = rightmost_op.cast(Node.ArrayTypeSentinel).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .PtrType => { + const prefix_op = rightmost_op.cast(Node.PtrType).?; + prefix_op.rhs = try p.expectNode(childParseFn, .{ + .InvalidToken = .{ .token = p.tok_i }, + }); + }, + .SliceType => { + const prefix_op = rightmost_op.cast(Node.SliceType).?; prefix_op.rhs = try p.expectNode(childParseFn, .{ .InvalidToken = .{ .token = p.tok_i }, }); diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index f24886c0abd9b8258354181be78c40ba7ad861b5..b75e6f6ca3e1e2fa21cfcf2c9cfe5da88266b2a5 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -468,166 +468,192 @@ fn renderExpression( return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space); }, - .PrefixOp => { - const prefix_op_node = @fieldParentPtr(ast.Node.PrefixOp, "base", base); - - switch (prefix_op_node.op) { - .PtrType => |ptr_info| { - const op_tok_id = tree.token_ids[prefix_op_node.op_token]; - switch (op_tok_id) { - .Asterisk, .AsteriskAsterisk => try stream.writeByte('*'), - .LBracket => if (tree.token_ids[prefix_op_node.op_token + 2] == .Identifier) - try stream.writeAll("[*c") - else - try stream.writeAll("[*"), - else => unreachable, - } - if (ptr_info.sentinel) |sentinel| { - const colon_token = tree.prevToken(sentinel.firstToken()); - try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : - const sentinel_space = switch (op_tok_id) { - .LBracket => Space.None, - else => Space.Space, - }; - try renderExpression(allocator, stream, tree, indent, start_col, sentinel, sentinel_space); - } - switch (op_tok_id) { - .Asterisk, .AsteriskAsterisk => {}, - .LBracket => try stream.writeByte(']'), - else => unreachable, - } - if (ptr_info.allowzero_token) |allowzero_token| { - try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero - } - if (ptr_info.align_info) |align_info| { - const lparen_token = tree.prevToken(align_info.node.firstToken()); - const align_token = tree.prevToken(lparen_token); - - try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align - try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // ( - - try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None); - - if (align_info.bit_range) |bit_range| { - const colon1 = tree.prevToken(bit_range.start.firstToken()); - const colon2 = tree.prevToken(bit_range.end.firstToken()); - - try renderToken(tree, stream, colon1, indent, start_col, Space.None); // : - try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None); - try renderToken(tree, stream, colon2, indent, start_col, Space.None); // : - try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None); - - const rparen_token = tree.nextToken(bit_range.end.lastToken()); - try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) - } else { - const rparen_token = tree.nextToken(align_info.node.lastToken()); - try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) - } - } - if (ptr_info.const_token) |const_token| { - try renderToken(tree, stream, const_token, indent, start_col, Space.Space); // const - } - if (ptr_info.volatile_token) |volatile_token| { - try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile - } - }, - - .SliceType => |ptr_info| { - try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None); // [ - if (ptr_info.sentinel) |sentinel| { - const colon_token = tree.prevToken(sentinel.firstToken()); - try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : - try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); - try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ] - } else { - try renderToken(tree, stream, tree.nextToken(prefix_op_node.op_token), indent, start_col, Space.None); // ] - } - - if (ptr_info.allowzero_token) |allowzero_token| { - try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero - } - if (ptr_info.align_info) |align_info| { - const lparen_token = tree.prevToken(align_info.node.firstToken()); - const align_token = tree.prevToken(lparen_token); - - try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align - try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // ( - - try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None); - - if (align_info.bit_range) |bit_range| { - const colon1 = tree.prevToken(bit_range.start.firstToken()); - const colon2 = tree.prevToken(bit_range.end.firstToken()); - - try renderToken(tree, stream, colon1, indent, start_col, Space.None); // : - try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None); - try renderToken(tree, stream, colon2, indent, start_col, Space.None); // : - try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None); - - const rparen_token = tree.nextToken(bit_range.end.lastToken()); - try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) - } else { - const rparen_token = tree.nextToken(align_info.node.lastToken()); - try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) - } - } - if (ptr_info.const_token) |const_token| { - try renderToken(tree, stream, const_token, indent, start_col, Space.Space); - } - if (ptr_info.volatile_token) |volatile_token| { - try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); - } - }, - - .ArrayType => |array_info| { - const lbracket = prefix_op_node.op_token; - const rbracket = tree.nextToken(if (array_info.sentinel) |sentinel| - sentinel.lastToken() - else - array_info.len_expr.lastToken()); - - try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [ - - const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; - const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment; - const new_indent = if (ends_with_comment) indent + indent_delta else indent; - const new_space = if (ends_with_comment) Space.Newline else Space.None; - try renderExpression(allocator, stream, tree, new_indent, start_col, array_info.len_expr, new_space); - if (starts_with_comment) { - try stream.writeByte('\n'); - } - if (ends_with_comment or starts_with_comment) { - try stream.writeByteNTimes(' ', indent); - } - if (array_info.sentinel) |sentinel| { - const colon_token = tree.prevToken(sentinel.firstToken()); - try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : - try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); - } - try renderToken(tree, stream, rbracket, indent, start_col, Space.None); // ] - }, - .BitNot, - .BoolNot, - .Negation, - .NegationWrap, - .OptionalType, - .AddressOf, - => { - try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.None); - }, - - .Try, - .Resume, - => { - try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.Space); - }, - - .Await => |await_info| { - try renderToken(tree, stream, prefix_op_node.op_token, indent, start_col, Space.Space); - }, + .BitNot => { + const bit_not = @fieldParentPtr(ast.Node.BitNot, "base", base); + try renderToken(tree, stream, bit_not.op_token, indent, start_col, Space.None); + return renderExpression(allocator, stream, tree, indent, start_col, bit_not.rhs, space); + }, + .BoolNot => { + const bool_not = @fieldParentPtr(ast.Node.BoolNot, "base", base); + try renderToken(tree, stream, bool_not.op_token, indent, start_col, Space.None); + return renderExpression(allocator, stream, tree, indent, start_col, bool_not.rhs, space); + }, + .Negation => { + const negation = @fieldParentPtr(ast.Node.Negation, "base", base); + try renderToken(tree, stream, negation.op_token, indent, start_col, Space.None); + return renderExpression(allocator, stream, tree, indent, start_col, negation.rhs, space); + }, + .NegationWrap => { + const negation_wrap = @fieldParentPtr(ast.Node.NegationWrap, "base", base); + try renderToken(tree, stream, negation_wrap.op_token, indent, start_col, Space.None); + return renderExpression(allocator, stream, tree, indent, start_col, negation_wrap.rhs, space); + }, + .OptionalType => { + const opt_type = @fieldParentPtr(ast.Node.OptionalType, "base", base); + try renderToken(tree, stream, opt_type.op_token, indent, start_col, Space.None); + return renderExpression(allocator, stream, tree, indent, start_col, opt_type.rhs, space); + }, + .AddressOf => { + const addr_of = @fieldParentPtr(ast.Node.AddressOf, "base", base); + try renderToken(tree, stream, addr_of.op_token, indent, start_col, Space.None); + return renderExpression(allocator, stream, tree, indent, start_col, addr_of.rhs, space); + }, + .Try => { + const try_node = @fieldParentPtr(ast.Node.Try, "base", base); + try renderToken(tree, stream, try_node.op_token, indent, start_col, Space.Space); + return renderExpression(allocator, stream, tree, indent, start_col, try_node.rhs, space); + }, + .Resume => { + const resume_node = @fieldParentPtr(ast.Node.Resume, "base", base); + try renderToken(tree, stream, resume_node.op_token, indent, start_col, Space.Space); + return renderExpression(allocator, stream, tree, indent, start_col, resume_node.rhs, space); + }, + .Await => { + const await_node = @fieldParentPtr(ast.Node.Await, "base", base); + try renderToken(tree, stream, await_node.op_token, indent, start_col, Space.Space); + return renderExpression(allocator, stream, tree, indent, start_col, await_node.rhs, space); + }, + + .ArrayType => { + const array_type = @fieldParentPtr(ast.Node.ArrayType, "base", base); + return renderArrayType( + allocator, + stream, + tree, + indent, + start_col, + array_type.op_token, + array_type.rhs, + array_type.len_expr, + null, + space, + ); + }, + .ArrayTypeSentinel => { + const array_type = @fieldParentPtr(ast.Node.ArrayTypeSentinel, "base", base); + return renderArrayType( + allocator, + stream, + tree, + indent, + start_col, + array_type.op_token, + array_type.rhs, + array_type.len_expr, + array_type.sentinel, + space, + ); + }, + + .PtrType => { + const ptr_type = @fieldParentPtr(ast.Node.PtrType, "base", base); + const op_tok_id = tree.token_ids[ptr_type.op_token]; + switch (op_tok_id) { + .Asterisk, .AsteriskAsterisk => try stream.writeByte('*'), + .LBracket => if (tree.token_ids[ptr_type.op_token + 2] == .Identifier) + try stream.writeAll("[*c") + else + try stream.writeAll("[*"), + else => unreachable, + } + if (ptr_type.ptr_info.sentinel) |sentinel| { + const colon_token = tree.prevToken(sentinel.firstToken()); + try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : + const sentinel_space = switch (op_tok_id) { + .LBracket => Space.None, + else => Space.Space, + }; + try renderExpression(allocator, stream, tree, indent, start_col, sentinel, sentinel_space); + } + switch (op_tok_id) { + .Asterisk, .AsteriskAsterisk => {}, + .LBracket => try stream.writeByte(']'), + else => unreachable, + } + if (ptr_type.ptr_info.allowzero_token) |allowzero_token| { + try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero + } + if (ptr_type.ptr_info.align_info) |align_info| { + const lparen_token = tree.prevToken(align_info.node.firstToken()); + const align_token = tree.prevToken(lparen_token); + + try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align + try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // ( + + try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None); + + if (align_info.bit_range) |bit_range| { + const colon1 = tree.prevToken(bit_range.start.firstToken()); + const colon2 = tree.prevToken(bit_range.end.firstToken()); + + try renderToken(tree, stream, colon1, indent, start_col, Space.None); // : + try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None); + try renderToken(tree, stream, colon2, indent, start_col, Space.None); // : + try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None); + + const rparen_token = tree.nextToken(bit_range.end.lastToken()); + try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) + } else { + const rparen_token = tree.nextToken(align_info.node.lastToken()); + try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) + } + } + if (ptr_type.ptr_info.const_token) |const_token| { + try renderToken(tree, stream, const_token, indent, start_col, Space.Space); // const + } + if (ptr_type.ptr_info.volatile_token) |volatile_token| { + try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); // volatile + } + return renderExpression(allocator, stream, tree, indent, start_col, ptr_type.rhs, space); + }, + + .SliceType => { + const slice_type = @fieldParentPtr(ast.Node.SliceType, "base", base); + try renderToken(tree, stream, slice_type.op_token, indent, start_col, Space.None); // [ + if (slice_type.ptr_info.sentinel) |sentinel| { + const colon_token = tree.prevToken(sentinel.firstToken()); + try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : + try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); + try renderToken(tree, stream, tree.nextToken(sentinel.lastToken()), indent, start_col, Space.None); // ] + } else { + try renderToken(tree, stream, tree.nextToken(slice_type.op_token), indent, start_col, Space.None); // ] } - return renderExpression(allocator, stream, tree, indent, start_col, prefix_op_node.rhs, space); + if (slice_type.ptr_info.allowzero_token) |allowzero_token| { + try renderToken(tree, stream, allowzero_token, indent, start_col, Space.Space); // allowzero + } + if (slice_type.ptr_info.align_info) |align_info| { + const lparen_token = tree.prevToken(align_info.node.firstToken()); + const align_token = tree.prevToken(lparen_token); + + try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align + try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // ( + + try renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None); + + if (align_info.bit_range) |bit_range| { + const colon1 = tree.prevToken(bit_range.start.firstToken()); + const colon2 = tree.prevToken(bit_range.end.firstToken()); + + try renderToken(tree, stream, colon1, indent, start_col, Space.None); // : + try renderExpression(allocator, stream, tree, indent, start_col, bit_range.start, Space.None); + try renderToken(tree, stream, colon2, indent, start_col, Space.None); // : + try renderExpression(allocator, stream, tree, indent, start_col, bit_range.end, Space.None); + + const rparen_token = tree.nextToken(bit_range.end.lastToken()); + try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) + } else { + const rparen_token = tree.nextToken(align_info.node.lastToken()); + try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // ) + } + } + if (slice_type.ptr_info.const_token) |const_token| { + try renderToken(tree, stream, const_token, indent, start_col, Space.Space); + } + if (slice_type.ptr_info.volatile_token) |volatile_token| { + try renderToken(tree, stream, volatile_token, indent, start_col, Space.Space); + } + return renderExpression(allocator, stream, tree, indent, start_col, slice_type.rhs, space); }, .ArrayInitializer, .ArrayInitializerDot => { @@ -2057,6 +2083,46 @@ fn renderExpression( } } +fn renderArrayType( + allocator: *mem.Allocator, + stream: anytype, + tree: *ast.Tree, + indent: usize, + start_col: *usize, + lbracket: ast.TokenIndex, + rhs: *ast.Node, + len_expr: *ast.Node, + opt_sentinel: ?*ast.Node, + space: Space, +) (@TypeOf(stream).Error || Error)!void { + const rbracket = tree.nextToken(if (opt_sentinel) |sentinel| + sentinel.lastToken() + else + len_expr.lastToken()); + + try renderToken(tree, stream, lbracket, indent, start_col, Space.None); // [ + + const starts_with_comment = tree.token_ids[lbracket + 1] == .LineComment; + const ends_with_comment = tree.token_ids[rbracket - 1] == .LineComment; + const new_indent = if (ends_with_comment) indent + indent_delta else indent; + const new_space = if (ends_with_comment) Space.Newline else Space.None; + try renderExpression(allocator, stream, tree, new_indent, start_col, len_expr, new_space); + if (starts_with_comment) { + try stream.writeByte('\n'); + } + if (ends_with_comment or starts_with_comment) { + try stream.writeByteNTimes(' ', indent); + } + if (opt_sentinel) |sentinel| { + const colon_token = tree.prevToken(sentinel.firstToken()); + try renderToken(tree, stream, colon_token, indent, start_col, Space.None); // : + try renderExpression(allocator, stream, tree, indent, start_col, sentinel, Space.None); + } + try renderToken(tree, stream, rbracket, indent, start_col, Space.None); // ] + + return renderExpression(allocator, stream, tree, indent, start_col, rhs, space); +} + fn renderAsmOutput( allocator: *mem.Allocator, stream: anytype, diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 089e570edabf649556ff6d8a1ecca0ab1b685aff..993313d947f593731d130d1b2eb7bc275806ba55 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1308,10 +1308,18 @@ fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir .ControlFlowExpression => return self.astGenControlFlowExpression(scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)), .If => return self.astGenIf(scope, @fieldParentPtr(ast.Node.If, "base", ast_node)), .InfixOp => return self.astGenInfixOp(scope, @fieldParentPtr(ast.Node.InfixOp, "base", ast_node)), + .BoolNot => return self.astGenBoolNot(scope, @fieldParentPtr(ast.Node.BoolNot, "base", ast_node)), else => return self.failNode(scope, ast_node, "TODO implement astGenExpr for {}", .{@tagName(ast_node.id)}), } } +fn astGenBoolNot(self: *Module, scope: *Scope, node: *ast.Node.BoolNot) InnerError!*zir.Inst { + const operand = try self.astGenExpr(scope, node.rhs); + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + return self.addZIRInst(scope, src, zir.Inst.BoolNot, .{ .operand = operand }, .{}); +} + fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) InnerError!*zir.Inst { switch (infix_node.op) { .Assign => { diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 4ae2d74336bbc47568010def2439880a0f8d89b2..c45befd372c2c2401d89cc155669eb006b165382 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -1561,7 +1561,7 @@ fn transImplicitCastExpr( return maybeSuppressResult(rp, scope, result_used, sub_expr_node); } - const prefix_op = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); + const prefix_op = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); prefix_op.rhs = try transExpr(rp, scope, sub_expr, .used, .r_value); return maybeSuppressResult(rp, scope, result_used, &prefix_op.base); @@ -1673,11 +1673,7 @@ fn isBoolRes(res: *ast.Node) bool { else => {}, }, - .PrefixOp => switch (@fieldParentPtr(ast.Node.PrefixOp, "base", res).op) { - .BoolNot => return true, - - else => {}, - }, + .BoolNot => return true, .BoolLiteral => return true, .GroupedExpression => return isBoolRes(@fieldParentPtr(ast.Node.GroupedExpression, "base", res).expr), else => {}, @@ -2162,21 +2158,16 @@ fn transCreateNodeArrayType( source_loc: ZigClangSourceLocation, ty: *const ZigClangType, len: anytype, -) TransError!*ast.Node { - var node = try transCreateNodePrefixOp( - rp.c, - .{ - .ArrayType = .{ - .len_expr = undefined, - .sentinel = null, - }, - }, - .LBracket, - "[", - ); - node.op.ArrayType.len_expr = try transCreateNodeInt(rp.c, len); +) !*ast.Node { + const node = try rp.c.arena.create(ast.Node.ArrayType); + const op_token = try appendToken(rp.c, .LBracket, "["); + const len_expr = try transCreateNodeInt(rp.c, len); _ = try appendToken(rp.c, .RBracket, "]"); - node.rhs = try transType(rp, ty, source_loc); + node.* = .{ + .op_token = op_token, + .rhs = try transType(rp, ty, source_loc), + .len_expr = len_expr, + }; return &node.base; } @@ -2449,7 +2440,7 @@ fn transDoWhileLoop( }, }; defer cond_scope.deinit(); - const prefix_op = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!"); + const prefix_op = try transCreateNodeSimplePrefixOp(rp.c, .BoolNot, .Bang, "!"); prefix_op.rhs = try transBoolExpr(rp, &cond_scope.base, @ptrCast(*const ZigClangExpr, ZigClangDoStmt_getCond(stmt)), .used, .r_value, true); _ = try appendToken(rp.c, .RParen, ")"); if_node.condition = &prefix_op.base; @@ -3036,7 +3027,7 @@ fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnar else return transCreatePreCrement(rp, scope, stmt, .AssignSub, .MinusEqual, "-=", used), .AddrOf => { - const op_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); + const op_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); op_node.rhs = try transExpr(rp, scope, op_expr, used, .r_value); return &op_node.base; }, @@ -3052,7 +3043,7 @@ fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnar .Plus => return transExpr(rp, scope, op_expr, used, .r_value), .Minus => { if (!qualTypeHasWrappingOverflow(ZigClangExpr_getType(op_expr))) { - const op_node = try transCreateNodePrefixOp(rp.c, .Negation, .Minus, "-"); + const op_node = try transCreateNodeSimplePrefixOp(rp.c, .Negation, .Minus, "-"); op_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); return &op_node.base; } else if (cIsUnsignedInteger(ZigClangExpr_getType(op_expr))) { @@ -3065,12 +3056,12 @@ fn transUnaryOperator(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnar return revertAndWarn(rp, error.UnsupportedTranslation, ZigClangUnaryOperator_getBeginLoc(stmt), "C negation with non float non integer", .{}); }, .Not => { - const op_node = try transCreateNodePrefixOp(rp.c, .BitNot, .Tilde, "~"); + const op_node = try transCreateNodeSimplePrefixOp(rp.c, .BitNot, .Tilde, "~"); op_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); return &op_node.base; }, .LNot => { - const op_node = try transCreateNodePrefixOp(rp.c, .BoolNot, .Bang, "!"); + const op_node = try transCreateNodeSimplePrefixOp(rp.c, .BoolNot, .Bang, "!"); op_node.rhs = try transBoolExpr(rp, scope, op_expr, .used, .r_value, true); return &op_node.base; }, @@ -3116,7 +3107,7 @@ fn transCreatePreCrement( const node = try transCreateNodeVarDecl(rp.c, false, true, ref); node.eq_token = try appendToken(rp.c, .Equal, "="); - const rhs_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); + const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); node.init_node = &rhs_node.base; node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); @@ -3182,7 +3173,7 @@ fn transCreatePostCrement( const node = try transCreateNodeVarDecl(rp.c, false, true, ref); node.eq_token = try appendToken(rp.c, .Equal, "="); - const rhs_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); + const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); node.init_node = &rhs_node.base; node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); @@ -3336,7 +3327,7 @@ fn transCreateCompoundAssign( const node = try transCreateNodeVarDecl(rp.c, false, true, ref); node.eq_token = try appendToken(rp.c, .Equal, "="); - const addr_node = try transCreateNodePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); + const addr_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); addr_node.rhs = try transExpr(rp, scope, lhs, .used, .l_value); node.init_node = &addr_node.base; node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); @@ -3984,16 +3975,15 @@ fn transCreateNodeFieldAccess(c: *Context, container: *ast.Node, field_name: []c return &field_access_node.base; } -fn transCreateNodePrefixOp( +fn transCreateNodeSimplePrefixOp( c: *Context, - op: ast.Node.PrefixOp.Op, + comptime tag: ast.Node.Id, op_tok_id: std.zig.Token.Id, bytes: []const u8, -) !*ast.Node.PrefixOp { - const node = try c.arena.create(ast.Node.PrefixOp); +) !*ast.Node.SimplePrefixOp(tag) { + const node = try c.arena.create(ast.Node.SimplePrefixOp(tag)); node.* = .{ .op_token = try appendToken(c, op_tok_id, bytes), - .op = op, .rhs = undefined, // translate and set afterward }; return node; @@ -4065,8 +4055,8 @@ fn transCreateNodePtrType( is_const: bool, is_volatile: bool, op_tok_id: std.zig.Token.Id, -) !*ast.Node.PrefixOp { - const node = try c.arena.create(ast.Node.PrefixOp); +) !*ast.Node.PtrType { + const node = try c.arena.create(ast.Node.PtrType); const op_token = switch (op_tok_id) { .LBracket => blk: { const lbracket = try appendToken(c, .LBracket, "["); @@ -4086,11 +4076,9 @@ fn transCreateNodePtrType( }; node.* = .{ .op_token = op_token, - .op = .{ - .PtrType = .{ - .const_token = if (is_const) try appendToken(c, .Keyword_const, "const") else null, - .volatile_token = if (is_volatile) try appendToken(c, .Keyword_volatile, "volatile") else null, - }, + .ptr_info = .{ + .const_token = if (is_const) try appendToken(c, .Keyword_const, "const") else null, + .volatile_token = if (is_volatile) try appendToken(c, .Keyword_volatile, "volatile") else null, }, .rhs = undefined, // translate and set afterward }; @@ -4569,12 +4557,12 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour .Pointer => { const child_qt = ZigClangType_getPointeeType(ty); if (qualTypeChildIsFnProto(child_qt)) { - const optional_node = try transCreateNodePrefixOp(rp.c, .OptionalType, .QuestionMark, "?"); + const optional_node = try transCreateNodeSimplePrefixOp(rp.c, .OptionalType, .QuestionMark, "?"); optional_node.rhs = try transQualType(rp, child_qt, source_loc); return &optional_node.base; } if (typeIsOpaque(rp.c, ZigClangQualType_getTypePtr(child_qt), source_loc)) { - const optional_node = try transCreateNodePrefixOp(rp.c, .OptionalType, .QuestionMark, "?"); + const optional_node = try transCreateNodeSimplePrefixOp(rp.c, .OptionalType, .QuestionMark, "?"); const pointer_node = try transCreateNodePtrType( rp.c, ZigClangQualType_isConstQualified(child_qt), @@ -4599,21 +4587,8 @@ fn transType(rp: RestorePoint, ty: *const ZigClangType, source_loc: ZigClangSour const size_ap_int = ZigClangConstantArrayType_getSize(const_arr_ty); const size = ZigClangAPInt_getLimitedValue(size_ap_int, math.maxInt(usize)); - var node = try transCreateNodePrefixOp( - rp.c, - .{ - .ArrayType = .{ - .len_expr = undefined, - .sentinel = null, - }, - }, - .LBracket, - "[", - ); - node.op.ArrayType.len_expr = try transCreateNodeInt(rp.c, size); - _ = try appendToken(rp.c, .RBracket, "]"); - node.rhs = try transQualType(rp, ZigClangConstantArrayType_getElementType(const_arr_ty), source_loc); - return &node.base; + const elem_ty = ZigClangQualType_getTypePtr(ZigClangConstantArrayType_getElementType(const_arr_ty)); + return try transCreateNodeArrayType(rp, source_loc, elem_ty, size); }, .IncompleteArray => { const incomplete_array_ty = @ptrCast(*const ZigClangIncompleteArrayType, ty); @@ -5824,7 +5799,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, if (prev_id == .Keyword_void) { const ptr = try transCreateNodePtrType(c, false, false, .Asterisk); ptr.rhs = node; - const optional_node = try transCreateNodePrefixOp(c, .OptionalType, .QuestionMark, "?"); + const optional_node = try transCreateNodeSimplePrefixOp(c, .OptionalType, .QuestionMark, "?"); optional_node.rhs = &ptr.base; return &optional_node.base; } else { @@ -5993,18 +5968,18 @@ fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, switch (op_tok.id) { .Bang => { - const node = try transCreateNodePrefixOp(c, .BoolNot, .Bang, "!"); + const node = try transCreateNodeSimplePrefixOp(c, .BoolNot, .Bang, "!"); node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope); return &node.base; }, .Minus => { - const node = try transCreateNodePrefixOp(c, .Negation, .Minus, "-"); + const node = try transCreateNodeSimplePrefixOp(c, .Negation, .Minus, "-"); node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope); return &node.base; }, .Plus => return try parseCPrefixOpExpr(c, it, source, source_loc, scope), .Tilde => { - const node = try transCreateNodePrefixOp(c, .BitNot, .Tilde, "~"); + const node = try transCreateNodeSimplePrefixOp(c, .BitNot, .Tilde, "~"); node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope); return &node.base; }, @@ -6013,7 +5988,7 @@ fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, return try transCreateNodePtrDeref(c, node); }, .Ampersand => { - const node = try transCreateNodePrefixOp(c, .AddressOf, .Ampersand, "&"); + const node = try transCreateNodeSimplePrefixOp(c, .AddressOf, .Ampersand, "&"); node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope); return &node.base; }, @@ -6034,29 +6009,49 @@ fn tokenSlice(c: *Context, token: ast.TokenIndex) []u8 { } fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { - if (node.id == .ContainerDecl) { - return node; - } else if (node.id == .PrefixOp) { - return node; - } else if (node.cast(ast.Node.Identifier)) |ident| { - if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { - if (value.cast(ast.Node.VarDecl)) |var_decl| - return getContainer(c, var_decl.init_node.?); - } - } else if (node.cast(ast.Node.InfixOp)) |infix| { - if (infix.op != .Period) - return null; - if (getContainerTypeOf(c, infix.lhs)) |ty_node| { - if (ty_node.cast(ast.Node.ContainerDecl)) |container| { - for (container.fieldsAndDecls()) |field_ref| { - const field = field_ref.cast(ast.Node.ContainerField).?; - const ident = infix.rhs.cast(ast.Node.Identifier).?; - if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) { - return getContainer(c, field.type_expr.?); + switch (node.id) { + .ContainerDecl, + .AddressOf, + .Await, + .BitNot, + .BoolNot, + .OptionalType, + .Negation, + .NegationWrap, + .Resume, + .Try, + .ArrayType, + .ArrayTypeSentinel, + .PtrType, + .SliceType, + => return node, + + .Identifier => { + const ident = node.cast(ast.Node.Identifier).?; + if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { + if (value.cast(ast.Node.VarDecl)) |var_decl| + return getContainer(c, var_decl.init_node.?); + } + }, + + .InfixOp => { + const infix = node.cast(ast.Node.InfixOp).?; + if (infix.op != .Period) + return null; + if (getContainerTypeOf(c, infix.lhs)) |ty_node| { + if (ty_node.cast(ast.Node.ContainerDecl)) |container| { + for (container.fieldsAndDecls()) |field_ref| { + const field = field_ref.cast(ast.Node.ContainerField).?; + const ident = infix.rhs.cast(ast.Node.Identifier).?; + if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) { + return getContainer(c, field.type_expr.?); + } } } } - } + }, + + else => {}, } return null; } @@ -6091,11 +6086,9 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto { const init = if (ref.cast(ast.Node.VarDecl)) |v| v.init_node.? else return null; if (getContainerTypeOf(c, init)) |ty_node| { - if (ty_node.cast(ast.Node.PrefixOp)) |prefix| { - if (prefix.op == .OptionalType) { - if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| { - return fn_proto; - } + if (ty_node.cast(ast.Node.OptionalType)) |prefix| { + if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| { + return fn_proto; } } } -- 2.54.0 From 4f5e065d6ec10cb27589995bf5d6390647f783f2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2020 20:47:47 -0700 Subject: [PATCH 271/295] stage2: add ZIR support for BoolNot --- src-self-hosted/Module.zig | 12 ++++++++++++ src-self-hosted/codegen.zig | 7 +++++++ src-self-hosted/ir.zig | 10 ++++++++++ src-self-hosted/zir.zig | 30 ++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 993313d947f593731d130d1b2eb7bc275806ba55..1ed655a05512c8e3b6153b74a9e2cc1e5e1bc1d6 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2566,6 +2566,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In .condbr => return self.analyzeInstCondBr(scope, old_inst.cast(zir.Inst.CondBr).?), .isnull => return self.analyzeInstIsNull(scope, old_inst.cast(zir.Inst.IsNull).?), .isnonnull => return self.analyzeInstIsNonNull(scope, old_inst.cast(zir.Inst.IsNonNull).?), + .boolnot => return self.analyzeInstBoolNot(scope, old_inst.cast(zir.Inst.BoolNot).?), } } @@ -3243,6 +3244,17 @@ fn analyzeInstCmp(self: *Module, scope: *Scope, inst: *zir.Inst.Cmp) InnerError! return self.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{}); } +fn analyzeInstBoolNot(self: *Module, scope: *Scope, inst: *zir.Inst.BoolNot) InnerError!*Inst { + const uncasted_operand = try self.resolveInst(scope, inst.positionals.operand); + const bool_type = Type.initTag(.bool); + const operand = try self.coerce(scope, bool_type, uncasted_operand); + if (try self.resolveDefinedValue(scope, operand)) |val| { + return self.constBool(scope, inst.base.src, !val.toBool()); + } + const b = try self.requireRuntimeBlock(scope, inst.base.src); + return self.addNewInstArgs(b, inst.base.src, bool_type, Inst.Not, .{ .operand = operand }); +} + fn analyzeInstIsNull(self: *Module, scope: *Scope, inst: *zir.Inst.IsNull) InnerError!*Inst { const operand = try self.resolveInst(scope, inst.positionals.operand); return self.analyzeIsNull(scope, inst.base.src, operand, true); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 143ad787b7ae095305ca360163b783d1d3a19e4e..731c039d9c290f62e2d97d33030597e719fa3780 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -407,6 +407,13 @@ const Function = struct { .retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?, arch), .sub => return self.genSub(inst.cast(ir.Inst.Sub).?, arch), .unreach => return MCValue{ .unreach = {} }, + .not => return self.genNot(inst.cast(ir.Inst.Not).?, arch), + } + } + + fn genNot(self: *Function, inst: *ir.Inst.Not, comptime arch: std.Target.Cpu.Arch) !MCValue { + switch (arch) { + else => return self.fail(inst.base.src, "TODO implement NOT for {}", .{self.target.cpu.arch}), } } diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 74da3430a76f19c94a31ceb64a75b140c23addcb..6c0c80746853cae478536eefd86e89beeab17bbc 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -60,6 +60,7 @@ pub const Inst = struct { retvoid, sub, unreach, + not, }; pub fn cast(base: *Inst, comptime T: type) ?*T { @@ -194,6 +195,15 @@ pub const Inst = struct { false_death_count: u32 = 0, }; + pub const Not = struct { + pub const base_tag = Tag.not; + + base: Inst, + args: struct { + operand: *Inst, + }, + }; + pub const Constant = struct { pub const base_tag = Tag.constant; base: Inst, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 0dfbdd20a0bbd10e79cfbbe4baf6daa53575c655..45ced542550d54ff9a5bb7d4c42cc020c3afad1f 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -56,6 +56,7 @@ pub const Inst = struct { declval, /// Same as declval but the parameter is a `*Module.Decl` rather than a name. declval_in_module, + boolnot, /// String Literal. Makes an anonymous Decl and then takes a pointer to it. str, int, @@ -115,6 +116,7 @@ pub const Inst = struct { .cmp, .isnull, .isnonnull, + .boolnot, => false, .condbr, @@ -143,6 +145,7 @@ pub const Inst = struct { .declval_in_module => DeclValInModule, .compileerror => CompileError, .@"const" => Const, + .boolnot => BoolNot, .str => Str, .int => Int, .inttype => IntType, @@ -299,6 +302,16 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const BoolNot = struct { + pub const base_tag = Tag.boolnot; + base: Inst, + + positionals: struct { + operand: *Inst, + }, + kw_args: struct {}, + }; + pub const Str = struct { pub const base_tag = Tag.str; base: Inst, @@ -762,6 +775,7 @@ const Writer = struct { .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst), .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst), .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst), + .boolnot => return self.writeInstToStreamGeneric(stream, .boolnot, inst), .str => return self.writeInstToStreamGeneric(stream, .str, inst), .int => return self.writeInstToStreamGeneric(stream, .int, inst), .inttype => return self.writeInstToStreamGeneric(stream, .inttype, inst), @@ -1658,6 +1672,22 @@ const EmitZIR = struct { }; for (body.instructions) |inst| { const new_inst = switch (inst.tag) { + .not => blk: { + const old_inst = inst.cast(ir.Inst.Not).?; + assert(inst.ty.zigTypeTag() == .Bool); + const new_inst = try self.arena.allocator.create(Inst.BoolNot); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.BoolNot.base_tag, + }, + .positionals = .{ + .operand = try self.resolveInst(new_body, old_inst.args.operand), + }, + .kw_args = .{}, + }; + break :blk &new_inst.base; + }, .add => blk: { const old_inst = inst.cast(ir.Inst.Add).?; const new_inst = try self.arena.allocator.create(Inst.Add); -- 2.54.0 From 03f14c3102ccd3e1811ccf4fb7cae11a75d3018f Mon Sep 17 00:00:00 2001 From: ~nue Date: Tue, 14 Jul 2020 02:27:58 -0400 Subject: [PATCH 272/295] Added octal formatting fo `fmt` functions. (#5867) * Added octal formatting (specifier "o") to `formatIntValue` function. * Added octal specifier test case in `fmt.zig` (under the "int.specifier" case) --- lib/std/fmt.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 7e288170af454ec13b6859102e8ff1032339c6ab..c9ba3b347060ce03eb85b3a038d3e0020bcfd6df 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -64,6 +64,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool { /// - `e`: output floating point value in scientific notation /// - `d`: output numeric value in decimal notation /// - `b`: output integer value in binary notation +/// - `o`: output integer value in octal notation /// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. /// - `*`: output the address of the value instead of the value itself. /// @@ -543,6 +544,9 @@ pub fn formatIntValue( } else if (comptime std.mem.eql(u8, fmt, "X")) { radix = 16; uppercase = true; + } else if (comptime std.mem.eql(u8, fmt, "o")) { + radix = 8; + uppercase = false; } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } @@ -1240,6 +1244,10 @@ test "int.specifier" { const value: u8 = 0b1100; try testFmt("u8: 0b1100\n", "u8: 0b{b}\n", .{value}); } + { + const value: u16 = 0o1234; + try testFmt("u16: 0o1234\n", "u16: 0o{o}\n", .{value}); + } } test "int.padded" { -- 2.54.0 From 5da5ded74326fce0ca52c6cb00f22824f7beb7eb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2020 23:48:26 -0700 Subject: [PATCH 273/295] stage2: detect unreferenced non-volatile asm and NOT --- src-self-hosted/codegen.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 731c039d9c290f62e2d97d33030597e719fa3780..5753559ce125ac85e02e0001fbbb7c672290f310 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -412,6 +412,9 @@ const Function = struct { } fn genNot(self: *Function, inst: *ir.Inst.Not, comptime arch: std.Target.Cpu.Arch) !MCValue { + // No side effects, so if it's unreferenced, do nothing. + if (inst.base.isUnused()) + return MCValue.dead; switch (arch) { else => return self.fail(inst.base.src, "TODO implement NOT for {}", .{self.target.cpu.arch}), } @@ -819,6 +822,8 @@ const Function = struct { } fn genAsm(self: *Function, inst: *ir.Inst.Assembly, comptime arch: Target.Cpu.Arch) !MCValue { + if (!inst.args.is_volatile and inst.base.isUnused()) + return MCValue.dead; if (arch != .x86_64 and arch != .i386) { return self.fail(inst.base.src, "TODO implement inline asm support for more architectures", .{}); } -- 2.54.0 From 135580c1621513e7cfeed8098c087d1d6941fa97 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2020 23:48:54 -0700 Subject: [PATCH 274/295] stage2: fix liveness analysis of Call instructions --- src-self-hosted/ir.zig | 21 +++++++++++++-------- src-self-hosted/liveness.zig | 35 +++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 6c0c80746853cae478536eefd86e89beeab17bbc..c654bef61128a4963dba300daef8d11b11feb520 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -14,30 +14,35 @@ pub const Inst = struct { tag: Tag, /// Each bit represents the index of an `Inst` parameter in the `args` field. /// If a bit is set, it marks the end of the lifetime of the corresponding - /// instruction parameter. For example, 0b000_00101 means that the first and + /// instruction parameter. For example, 0b101 means that the first and /// third `Inst` parameters' lifetimes end after this instruction, and will /// not have any more following references. /// The most significant bit being set means that the instruction itself is /// never referenced, in other words its lifetime ends as soon as it finishes. - /// If bit 7 (0b1xxx_xxxx) is set, it means this instruction itself is unreferenced. - /// If bit 6 (0bx1xx_xxxx) is set, it means this is a special case and the + /// If bit 15 (0b1xxx_xxxx_xxxx_xxxx) is set, it means this instruction itself is unreferenced. + /// If bit 14 (0bx1xx_xxxx_xxxx_xxxx) is set, it means this is a special case and the /// lifetimes of operands are encoded elsewhere. - deaths: u8 = undefined, + deaths: DeathsInt = undefined, ty: Type, /// Byte offset into the source. src: usize, + pub const DeathsInt = u16; + pub const DeathsBitIndex = std.math.Log2Int(DeathsInt); + pub const unreferenced_bit_index = @typeInfo(DeathsInt).Int.bits - 1; + pub const deaths_bits = unreferenced_bit_index - 1; + pub fn isUnused(self: Inst) bool { - return (self.deaths & 0b1000_0000) != 0; + return (self.deaths & (1 << unreferenced_bit_index)) != 0; } - pub fn operandDies(self: Inst, index: u3) bool { - assert(index < 6); + pub fn operandDies(self: Inst, index: DeathsBitIndex) bool { + assert(index < deaths_bits); return @truncate(u1, self.deaths << index) != 0; } pub fn specialOperandDeaths(self: Inst) bool { - return (self.deaths & 0b1000_0000) != 0; + return (self.deaths & (1 << deaths_bits)) != 0; } pub const Tag = enum { diff --git a/src-self-hosted/liveness.zig b/src-self-hosted/liveness.zig index 797a55a80c5f3037fd49aa33e3b655108f177be5..a06a4dd1d1637b3ce68139a46ccb89442be013bc 100644 --- a/src-self-hosted/liveness.zig +++ b/src-self-hosted/liveness.zig @@ -34,7 +34,7 @@ fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Ins inline for (std.meta.declarations(ir.Inst)) |decl| { switch (decl.data) { .Type => |T| { - if (@hasDecl(T, "base_tag")) { + if (@typeInfo(T) == .Struct and @hasDecl(T, "base_tag")) { if (T.base_tag == base.tag) { return analyzeInst(arena, table, T, @fieldParentPtr(T, "base", base)); } @@ -47,7 +47,13 @@ fn analyzeInstGeneric(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Ins } fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), comptime T: type, inst: *T) error{OutOfMemory}!void { - inst.base.deaths = 0; + if (table.contains(&inst.base)) { + inst.base.deaths = 0; + } else { + // No tombstone for this instruction means it is never referenced, + // and its birth marks its own death. Very metal 🤘 + inst.base.deaths = 1 << ir.Inst.unreferenced_bit_index; + } switch (T) { ir.Inst.Constant => return, @@ -106,15 +112,28 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void // instruction, and the deaths flag for the CondBr instruction will indicate whether the // condition's lifetime ends immediately before entering any branch. }, + ir.Inst.Call => { + // Call instructions have a runtime-known number of operands so we have to handle them ourselves here. + const needed_bits = 1 + inst.args.args.len; + if (needed_bits <= ir.Inst.deaths_bits) { + var bit_i: ir.Inst.DeathsBitIndex = 0; + { + const prev = try table.fetchPut(inst.args.func, {}); + if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i; + bit_i += 1; + } + for (inst.args.args) |arg| { + const prev = try table.fetchPut(arg, {}); + if (prev == null) inst.base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i; + bit_i += 1; + } + } else { + @panic("Handle liveness analysis for function calls with many parameters"); + } + }, else => {}, } - if (!table.contains(&inst.base)) { - // No tombstone for this instruction means it is never referenced, - // and its birth marks its own death. Very metal 🤘 - inst.base.deaths |= 1 << 7; - } - const Args = ir.Inst.Args(T); if (Args == void) { return; -- 2.54.0 From a92990f99312c946b5e527517a27a67a5a5513c0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Jul 2020 02:24:12 -0700 Subject: [PATCH 275/295] stage2: implement enough for assert() function to codegen --- src-self-hosted/Module.zig | 44 +++++++++++--------- src-self-hosted/codegen.zig | 74 ++++++++++++++++++++++++++++++++-- src-self-hosted/type.zig | 19 ++++++++- src-self-hosted/value.zig | 34 +++++++++++----- test/stage2/compare_output.zig | 57 ++++++++++++++++++++++++++ 5 files changed, 192 insertions(+), 36 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 1ed655a05512c8e3b6153b74a9e2cc1e5e1bc1d6..de9e1f23864f69a36bd968b37ffe80a6d5b04728 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1358,17 +1358,19 @@ fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) In const tree = scope.tree(); const src = tree.token_locs[infix_node.op_token].start; + const op: std.math.CompareOperator = switch (infix_node.op) { + .BangEqual => .neq, + .EqualEqual => .eq, + .GreaterThan => .gt, + .GreaterOrEqual => .gte, + .LessThan => .lt, + .LessOrEqual => .lte, + else => unreachable, + }; + return self.addZIRInst(scope, src, zir.Inst.Cmp, .{ .lhs = lhs, - .op = @as(std.math.CompareOperator, switch (infix_node.op) { - .BangEqual => .neq, - .EqualEqual => .eq, - .GreaterThan => .gt, - .GreaterOrEqual => .gte, - .LessThan => .lt, - .LessOrEqual => .lte, - else => unreachable, - }), + .op = op, .rhs = rhs, }, .{}); }, @@ -1415,11 +1417,13 @@ fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir defer then_scope.instructions.deinit(self.gpa); const then_result = try self.astGenExpr(&then_scope.base, if_node.body); - const then_src = tree.token_locs[if_node.body.lastToken()].start; - _ = try self.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ - .block = block, - .operand = then_result, - }, .{}); + if (!then_result.tag.isNoReturn()) { + const then_src = tree.token_locs[if_node.body.lastToken()].start; + _ = try self.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ + .block = block, + .operand = then_result, + }, .{}); + } condbr.positionals.true_body = .{ .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items), }; @@ -1433,11 +1437,13 @@ fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir if (if_node.@"else") |else_node| { const else_result = try self.astGenExpr(&else_scope.base, else_node.body); - const else_src = tree.token_locs[else_node.body.lastToken()].start; - _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ - .block = block, - .operand = else_result, - }, .{}); + if (!else_result.tag.isNoReturn()) { + const else_src = tree.token_locs[else_node.body.lastToken()].start; + _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ + .block = block, + .operand = else_result, + }, .{}); + } } else { // TODO Optimization opportunity: we can avoid an allocation and a memcpy here // by directly allocating the body for this one instruction. diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 5753559ce125ac85e02e0001fbbb7c672290f310..314d497808cf023267a9ac45e2c0002d8b756b5a 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -415,7 +415,46 @@ const Function = struct { // No side effects, so if it's unreferenced, do nothing. if (inst.base.isUnused()) return MCValue.dead; + const operand = try self.resolveInst(inst.args.operand); + switch (operand) { + .dead => unreachable, + .unreach => unreachable, + .compare_flags_unsigned => |op| return MCValue{ + .compare_flags_unsigned = switch (op) { + .gte => .lt, + .gt => .lte, + .neq => .eq, + .lt => .gte, + .lte => .gt, + .eq => .neq, + }, + }, + .compare_flags_signed => |op| return MCValue{ + .compare_flags_signed = switch (op) { + .gte => .lt, + .gt => .lte, + .neq => .eq, + .lt => .gte, + .lte => .gt, + .eq => .neq, + }, + }, + else => {}, + } + switch (arch) { + .x86_64 => { + var imm = ir.Inst.Constant{ + .base = .{ + .tag = .constant, + .deaths = 0, + .ty = inst.args.operand.ty, + .src = inst.args.operand.src, + }, + .val = Value.initTag(.bool_true), + }; + return try self.genX8664BinMath(&inst.base, inst.args.operand, &imm.base, 6, 0x30); + }, else => return self.fail(inst.base.src, "TODO implement NOT for {}", .{self.target.cpu.arch}), } } @@ -444,7 +483,7 @@ const Function = struct { } } - /// ADD, SUB + /// ADD, SUB, XOR, OR, AND fn genX8664BinMath(self: *Function, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, opx: u8, mr: u8) !MCValue { try self.code.ensureCapacity(self.code.items.len + 8); @@ -705,7 +744,7 @@ const Function = struct { fn genCondBr(self: *Function, inst: *ir.Inst.CondBr, comptime arch: std.Target.Cpu.Arch) !MCValue { switch (arch) { - .i386, .x86_64 => { + .x86_64 => { try self.code.ensureCapacity(self.code.items.len + 6); const cond = try self.resolveInst(inst.args.condition); @@ -734,7 +773,20 @@ const Function = struct { }; return self.genX86CondBr(inst, opcode, arch); }, - else => return self.fail(inst.base.src, "TODO implement condbr {} when condition not already in the compare flags", .{self.target.cpu.arch}), + .register => |reg_usize| { + const reg = @intToEnum(Reg(arch), @intCast(u8, reg_usize)); + // test reg, 1 + // TODO detect al, ax, eax + try self.code.ensureCapacity(self.code.items.len + 4); + self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0xf6, + @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()), + 0x01, + }); + return self.genX86CondBr(inst, 0x84, arch); + }, + else => return self.fail(inst.base.src, "TODO implement condbr {} when condition is {}", .{ self.target.cpu.arch, @tagName(cond) }), } }, else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}), @@ -892,7 +944,18 @@ const Function = struct { .none => unreachable, .unreach => unreachable, .compare_flags_unsigned => |op| { - return self.fail(src, "TODO set register with compare flags value (unsigned)", .{}); + try self.code.ensureCapacity(self.code.items.len + 3); + self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); + const opcode: u8 = switch (op) { + .gte => 0x93, + .gt => 0x97, + .neq => 0x95, + .lt => 0x92, + .lte => 0x96, + .eq => 0x94, + }; + const id = @as(u8, reg.id() & 0b111); + self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode, 0xC0 | id }); }, .compare_flags_signed => |op| { return self.fail(src, "TODO set register with compare flags value (signed)", .{}); @@ -1147,6 +1210,9 @@ const Function = struct { } return MCValue{ .immediate = typed_value.val.toUnsignedInt() }; }, + .Bool => { + return MCValue{ .immediate = @boolToInt(typed_value.val.toBool()) }; + }, .ComptimeInt => unreachable, // semantic analysis prevents this .ComptimeFloat => unreachable, // semantic analysis prevents this else => return self.fail(src, "TODO implement const of type '{}'", .{typed_value.ty}), diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 512dd631a75eee974829b8c7e9e2990689e99dc1..1cc1b690f3b84016d5c4144b88885f444f612f10 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -163,6 +163,22 @@ pub const Type = extern union { return sentinel_b == null; } }, + .Fn => { + if (!a.fnReturnType().eql(b.fnReturnType())) + return false; + if (a.fnCallingConvention() != b.fnCallingConvention()) + return false; + const a_param_len = a.fnParamLen(); + const b_param_len = b.fnParamLen(); + if (a_param_len != b_param_len) + return false; + var i: usize = 0; + while (i < a_param_len) : (i += 1) { + if (!a.fnParamType(i).eql(b.fnParamType(i))) + return false; + } + return true; + }, .Float, .Struct, .Optional, @@ -170,14 +186,13 @@ pub const Type = extern union { .ErrorSet, .Enum, .Union, - .Fn, .BoundFn, .Opaque, .Frame, .AnyFrame, .Vector, .EnumLiteral, - => @panic("TODO implement more Type equality comparison"), + => std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ a, b }), } } diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index c1e9a38bd1dfa013ed6025cfa81a359148a44893..a3e1daa383e51184c1507a6e44727b0c8f856c62 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -427,8 +427,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -441,8 +439,11 @@ pub const Value = extern union { .the_one_possible_value, // An integer with one possible value is always zero. .zero, + .bool_false, => return BigIntMutable.init(&space.limbs, 0).toConst(), + .bool_true => return BigIntMutable.init(&space.limbs, 1).toConst(), + .int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(), .int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(), .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(), @@ -493,8 +494,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -507,8 +506,11 @@ pub const Value = extern union { .zero, .the_one_possible_value, // an integer with one possible value is always zero + .bool_false, => return 0, + .bool_true => return 1, + .int_u64 => return self.cast(Payload.Int_u64).?.int, .int_i64 => return @intCast(u64, self.cast(Payload.Int_u64).?.int), .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(u64) catch unreachable, @@ -560,8 +562,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -574,8 +574,11 @@ pub const Value = extern union { .the_one_possible_value, // an integer with one possible value is always zero .zero, + .bool_false, => return 0, + .bool_true => return 1, + .int_u64 => { const x = self.cast(Payload.Int_u64).?.int; if (x == 0) return 0; @@ -632,8 +635,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -646,8 +647,18 @@ pub const Value = extern union { .zero, .undef, .the_one_possible_value, // an integer with one possible value is always zero + .bool_false, => return true, + .bool_true => { + const info = ty.intInfo(target); + if (info.signed) { + return info.bits >= 2; + } else { + return info.bits >= 1; + } + }, + .int_u64 => switch (ty.zigTypeTag()) { .Int => { const x = self.cast(Payload.Int_u64).?.int; @@ -796,8 +807,6 @@ pub const Value = extern union { .fn_ccc_void_no_args_type, .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, - .bool_true, - .bool_false, .null_value, .function, .ref_val, @@ -810,8 +819,11 @@ pub const Value = extern union { .zero, .the_one_possible_value, // an integer with one possible value is always zero + .bool_false, => return .eq, + .bool_true => return .gt, + .int_u64 => return std.math.order(lhs.cast(Payload.Int_u64).?.int, 0), .int_i64 => return std.math.order(lhs.cast(Payload.Int_i64).?.int, 0), .int_big_positive => return lhs.cast(Payload.IntBigPositive).?.asBigInt().orderAgainstScalar(0), @@ -855,7 +867,7 @@ pub const Value = extern union { pub fn toBool(self: Value) bool { return switch (self.tag()) { .bool_true => true, - .bool_false => false, + .bool_false, .zero => false, else => unreachable, }; } diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 27c9f48d72e43c4a1003dad3de8b409dbbe42b94..c4f85bfba4348c9cd208edbb5065f6434a116883 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -170,4 +170,61 @@ pub fn addCases(ctx: *TestContext) !void { "", ); } + { + var case = ctx.exe("assert function", linux_x64); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ add(3, 4); + \\ + \\ exit(); + \\} + \\ + \\fn add(a: u32, b: u32) void { + \\ assert(a + b == 7); + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ add(100, 200); + \\ + \\ exit(); + \\} + \\ + \\fn add(a: u32, b: u32) void { + \\ assert(a + b == 300); + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + } } -- 2.54.0 From 4696cd3e09c4e33519dbb53a41c32bbdfd97f6f6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Jul 2020 14:38:40 -0700 Subject: [PATCH 276/295] fix ability to call methods on enums with pointer-to-self closes #3218 --- src/ir.cpp | 6 ++---- test/stage1/behavior/enum.zig | 19 +++++++++++++++++++ test/stage1/behavior/union.zig | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index dc380b4389c25ae6fa275e13633d49f70d422ed2..0dd8f4e86a3761132a79057a8fe3fd035dd77c38 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -20182,7 +20182,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, } IrInstGen *first_arg; - if (!first_arg_known_bare && handle_is_ptr(ira->codegen, first_arg_ptr->value->type->data.pointer.child_type)) { + if (!first_arg_known_bare) { first_arg = first_arg_ptr; } else { first_arg = ir_get_deref(ira, &first_arg_ptr->base, first_arg_ptr, nullptr); @@ -20522,9 +20522,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, return ira->codegen->invalid_inst_gen; IrInstGen *first_arg; - if (param_type->id == ZigTypeIdPointer && - handle_is_ptr(ira->codegen, first_arg_ptr->value->type->data.pointer.child_type)) - { + if (param_type->id == ZigTypeIdPointer) { first_arg = first_arg_ptr; } else { first_arg = ir_get_deref(ira, &first_arg_ptr->base, first_arg_ptr, nullptr); diff --git a/test/stage1/behavior/enum.zig b/test/stage1/behavior/enum.zig index 765828f5ce14660583cb5b2044fbdcb8073e3a8c..b9bb1db5332fc8c73a3607c1ae7cf3ffd49b831e 100644 --- a/test/stage1/behavior/enum.zig +++ b/test/stage1/behavior/enum.zig @@ -1140,3 +1140,22 @@ test "tagName on enum literals" { expect(mem.eql(u8, @tagName(.FooBar), "FooBar")); comptime expect(mem.eql(u8, @tagName(.FooBar), "FooBar")); } + +test "method call on an enum" { + const S = struct { + const E = enum { + one, + two, + + fn method(self: *E) bool { + return self.* == .two; + } + }; + fn doTheTest() void { + var e = E.two; + expect(e.method()); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} diff --git a/test/stage1/behavior/union.zig b/test/stage1/behavior/union.zig index da898347b96e575079b51df400f51580e3d6c161..cf3412eb5b7e0ee1e33e55b3fdd439204a5b553c 100644 --- a/test/stage1/behavior/union.zig +++ b/test/stage1/behavior/union.zig @@ -669,3 +669,24 @@ test "cast from anonymous struct to union" { S.doTheTest(); comptime S.doTheTest(); } + +test "method call on an empty union" { + const S = struct { + const MyUnion = union(Tag) { + pub const Tag = enum { X1, X2 }; + X1: [0]u8, + X2: [0]u8, + + pub fn useIt(self: *@This()) bool { + return true; + } + }; + + fn doTheTest() void { + var u = MyUnion{ .X1 = [0]u8{} }; + expect(u.useIt()); + } + }; + S.doTheTest(); + comptime S.doTheTest(); +} -- 2.54.0 From a7c3cec65f1639195e787b98900c3f126953f880 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Jul 2020 15:29:07 -0700 Subject: [PATCH 277/295] follow up from previous commit for generic methods --- src/ir.cpp | 2 +- test/stage1/behavior/enum.zig | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index 0dd8f4e86a3761132a79057a8fe3fd035dd77c38..e162125fb47913292fa68263ee654733845e428a 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -20317,7 +20317,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, } IrInstGen *first_arg; - if (!first_arg_known_bare && handle_is_ptr(ira->codegen, first_arg_ptr->value->type->data.pointer.child_type)) { + if (!first_arg_known_bare) { first_arg = first_arg_ptr; } else { first_arg = ir_get_deref(ira, &first_arg_ptr->base, first_arg_ptr, nullptr); diff --git a/test/stage1/behavior/enum.zig b/test/stage1/behavior/enum.zig index b9bb1db5332fc8c73a3607c1ae7cf3ffd49b831e..f569264520594de12757acd4f86520ebcaf0eae9 100644 --- a/test/stage1/behavior/enum.zig +++ b/test/stage1/behavior/enum.zig @@ -1150,10 +1150,15 @@ test "method call on an enum" { fn method(self: *E) bool { return self.* == .two; } + + fn generic_method(self: *E, foo: anytype) bool { + return self.* == .two and foo == bool; + } }; fn doTheTest() void { var e = E.two; expect(e.method()); + expect(e.generic_method(bool)); } }; S.doTheTest(); -- 2.54.0 From eac628024177434563d348272165d4e3a0a281af Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 14 Jul 2020 17:17:37 -0700 Subject: [PATCH 278/295] add std.meta.TrailerFlags API This is useful for saving memory when allocating an object that has many optional components. The optional objects are allocated sequentially in memory, and a single integer is used to represent each optional object and whether it is present based on each corresponding bit. --- lib/std/meta.zig | 1 + lib/std/meta/trailer_flags.zig | 118 +++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 lib/std/meta/trailer_flags.zig diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 2827cfecf419452eca8387fe07c8d5b781111cfb..6340a44fae866ce5ce3fc1f1e2f49aa744b2b17f 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -6,6 +6,7 @@ const math = std.math; const testing = std.testing; pub const trait = @import("meta/trait.zig"); +pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags; const TypeInfo = builtin.TypeInfo; diff --git a/lib/std/meta/trailer_flags.zig b/lib/std/meta/trailer_flags.zig new file mode 100644 index 0000000000000000000000000000000000000000..eb8cd6d98c4b92784c281f34e5180a13e64999c6 --- /dev/null +++ b/lib/std/meta/trailer_flags.zig @@ -0,0 +1,118 @@ +const std = @import("../std.zig"); +const meta = std.meta; +const testing = std.testing; +const mem = std.mem; +const assert = std.debug.assert; + +/// This is useful for saving memory when allocating an object that has many +/// optional components. The optional objects are allocated sequentially in +/// memory, and a single integer is used to represent each optional object +/// and whether it is present based on each corresponding bit. +pub fn TrailerFlags(comptime Fields: type) type { + return struct { + bits: Int, + + pub const Int = @Type(.{ .Int = .{ .bits = bit_count, .is_signed = false } }); + pub const bit_count = @typeInfo(Fields).Struct.fields.len; + + pub const Self = @This(); + + pub fn has(self: Self, comptime name: []const u8) bool { + const field_index = meta.fieldIndex(Fields, name).?; + return (self.bits & (1 << field_index)) != 0; + } + + pub fn get(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) ?Field(name) { + if (!self.has(name)) + return null; + return self.ptrConst(p, name).*; + } + + pub fn setFlag(self: *Self, comptime name: []const u8) void { + const field_index = meta.fieldIndex(Fields, name).?; + self.bits |= 1 << field_index; + } + + pub fn init(comptime names: anytype) Self { + var self: Self = .{ .bits = 0 }; + inline for (@typeInfo(@TypeOf(names)).Struct.fields) |field| { + if (@field(names, field.name)) { + const field_index = meta.fieldIndex(Fields, field.name).?; + self.bits |= 1 << field_index; + } + } + return self; + } + + pub fn set( + self: Self, + p: [*]align(@alignOf(Fields)) u8, + comptime name: []const u8, + value: Field(name), + ) void { + self.ptr(p, name).* = value; + } + + pub fn ptr(self: Self, p: [*]align(@alignOf(Fields)) u8, comptime name: []const u8) *Field(name) { + const off = self.offset(p, name); + return @ptrCast(*Field(name), @alignCast(@alignOf(Field(name)), p + off)); + } + + pub fn ptrConst(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) *const Field(name) { + const off = self.offset(p, name); + return @ptrCast(*const Field(name), @alignCast(@alignOf(Field(name)), p + off)); + } + + pub fn offset(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) usize { + var off: usize = 0; + inline for (@typeInfo(Fields).Struct.fields) |field, i| { + const active = (self.bits & (1 << i)) != 0; + if (comptime mem.eql(u8, field.name, name)) { + assert(active); + return mem.alignForwardGeneric(usize, off, @alignOf(field.field_type)); + } else if (active) { + off = mem.alignForwardGeneric(usize, off, @alignOf(field.field_type)); + off += @sizeOf(field.field_type); + } + } + @compileError("no field named " ++ name ++ " in type " ++ @typeName(Fields)); + } + + pub fn Field(comptime name: []const u8) type { + return meta.fieldInfo(Fields, name).field_type; + } + + pub fn sizeInBytes(self: Self) usize { + var off: usize = 0; + inline for (@typeInfo(Fields).Struct.fields) |field, i| { + if ((self.bits & (1 << i)) != 0) { + off = mem.alignForwardGeneric(usize, off, @alignOf(field.field_type)); + off += @sizeOf(field.field_type); + } + } + return off; + } + }; +} + +test "TrailerFlags" { + const Flags = TrailerFlags(struct { + a: i32, + b: bool, + c: u64, + }); + var flags = Flags.init(.{ + .b = true, + .c = true, + }); + testing.expect(flags.sizeInBytes() == 16); + const slice = try testing.allocator.allocAdvanced(u8, 8, flags.sizeInBytes(), .exact); + defer testing.allocator.free(slice); + + flags.set(slice.ptr, "b", false); + flags.set(slice.ptr, "c", 12345678); + + testing.expect(flags.get(slice.ptr, "a") == null); + testing.expect(!flags.get(slice.ptr, "b").?); + testing.expect(flags.get(slice.ptr, "c").? == 12345678); +} -- 2.54.0 From 804b51b179bf7af689d308a092d1b02e94674570 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Jul 2020 01:38:31 -0700 Subject: [PATCH 279/295] stage2: VarDecl and FnProto take advantage of TrailerFlags API These AST nodes now have a flags field and then a bunch of optional trailing objects. The end result is lower memory usage and consequently better performance. This is part of an ongoing effort to reduce the amount of memory parsed ASTs take up. Running `zig fmt` on the std lib: * cache-misses: 2,554,321 => 2,534,745 * instructions: 3,293,220,119 => 3,302,479,874 * peak memory: 74.0 MiB => 73.0 MiB Holding the entire std lib AST in memory at the same time: 93.9 MiB => 88.5 MiB --- lib/std/meta/trailer_flags.zig | 42 ++- lib/std/zig/ast.zig | 219 ++++++++++----- lib/std/zig/parse.zig | 194 +++++++------- lib/std/zig/parser_test.zig | 59 ++-- lib/std/zig/render.zig | 92 ++++--- src-self-hosted/Module.zig | 22 +- src-self-hosted/translate_c.zig | 462 +++++++++++++++++++------------- test/translate_c.zig | 2 +- 8 files changed, 663 insertions(+), 429 deletions(-) diff --git a/lib/std/meta/trailer_flags.zig b/lib/std/meta/trailer_flags.zig index eb8cd6d98c4b92784c281f34e5180a13e64999c6..e1e216cde64421c64870656e914304f1568b099f 100644 --- a/lib/std/meta/trailer_flags.zig +++ b/lib/std/meta/trailer_flags.zig @@ -33,17 +33,30 @@ pub fn TrailerFlags(comptime Fields: type) type { self.bits |= 1 << field_index; } - pub fn init(comptime names: anytype) Self { + /// `fields` is a struct with each field set to an optional value. + /// Missing fields are assumed to be `null`. + /// Only the non-null bits are observed and are used to set the flag bits. + pub fn init(fields: anytype) Self { var self: Self = .{ .bits = 0 }; - inline for (@typeInfo(@TypeOf(names)).Struct.fields) |field| { - if (@field(names, field.name)) { - const field_index = meta.fieldIndex(Fields, field.name).?; - self.bits |= 1 << field_index; - } + inline for (@typeInfo(@TypeOf(fields)).Struct.fields) |field| { + const opt: ?Field(field.name) = @field(fields, field.name); + const field_index = meta.fieldIndex(Fields, field.name).?; + self.bits |= @as(Int, @boolToInt(opt != null)) << field_index; } return self; } + /// `fields` is a struct with each field set to an optional value (same as `init`). + /// Missing fields are assumed to be `null`. + pub fn setMany(self: Self, p: [*]align(@alignOf(Fields)) u8, fields: anytype) void { + inline for (@typeInfo(@TypeOf(fields)).Struct.fields) |field| { + const opt: ?Field(field.name) = @field(fields, field.name); + if (opt) |value| { + self.set(p, field.name, value); + } + } + } + pub fn set( self: Self, p: [*]align(@alignOf(Fields)) u8, @@ -54,11 +67,15 @@ pub fn TrailerFlags(comptime Fields: type) type { } pub fn ptr(self: Self, p: [*]align(@alignOf(Fields)) u8, comptime name: []const u8) *Field(name) { + if (@sizeOf(Field(name)) == 0) + return undefined; const off = self.offset(p, name); return @ptrCast(*Field(name), @alignCast(@alignOf(Field(name)), p + off)); } pub fn ptrConst(self: Self, p: [*]align(@alignOf(Fields)) const u8, comptime name: []const u8) *const Field(name) { + if (@sizeOf(Field(name)) == 0) + return undefined; const off = self.offset(p, name); return @ptrCast(*const Field(name), @alignCast(@alignOf(Field(name)), p + off)); } @@ -85,6 +102,8 @@ pub fn TrailerFlags(comptime Fields: type) type { pub fn sizeInBytes(self: Self) usize { var off: usize = 0; inline for (@typeInfo(Fields).Struct.fields) |field, i| { + if (@sizeOf(field.field_type) == 0) + continue; if ((self.bits & (1 << i)) != 0) { off = mem.alignForwardGeneric(usize, off, @alignOf(field.field_type)); off += @sizeOf(field.field_type); @@ -103,7 +122,7 @@ test "TrailerFlags" { }); var flags = Flags.init(.{ .b = true, - .c = true, + .c = 1234, }); testing.expect(flags.sizeInBytes() == 16); const slice = try testing.allocator.allocAdvanced(u8, 8, flags.sizeInBytes(), .exact); @@ -115,4 +134,13 @@ test "TrailerFlags" { testing.expect(flags.get(slice.ptr, "a") == null); testing.expect(!flags.get(slice.ptr, "b").?); testing.expect(flags.get(slice.ptr, "c").? == 12345678); + + flags.setMany(slice.ptr, .{ + .b = true, + .c = 5678, + }); + + testing.expect(flags.get(slice.ptr, "a") == null); + testing.expect(flags.get(slice.ptr, "b").?); + testing.expect(flags.get(slice.ptr, "c").? == 5678); } diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 1de00dd0b073f46ac250da982a2e0712358b0e37..7f066f541b2a8832e786fe6ac1bcc94d19b0b2fa 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -675,42 +675,84 @@ pub const Node = struct { } }; + /// Trailed in memory by possibly many things, with each optional thing + /// determined by a bit in `trailer_flags`. pub const VarDecl = struct { base: Node = Node{ .id = .VarDecl }, - doc_comments: ?*DocComment, - visib_token: ?TokenIndex, - thread_local_token: ?TokenIndex, - name_token: TokenIndex, - eq_token: ?TokenIndex, + trailer_flags: TrailerFlags, mut_token: TokenIndex, - comptime_token: ?TokenIndex, - extern_export_token: ?TokenIndex, - lib_name: ?*Node, - type_node: ?*Node, - align_node: ?*Node, - section_node: ?*Node, - init_node: ?*Node, + name_token: TokenIndex, semicolon_token: TokenIndex, + pub const TrailerFlags = std.meta.TrailerFlags(struct { + doc_comments: *DocComment, + visib_token: TokenIndex, + thread_local_token: TokenIndex, + eq_token: TokenIndex, + comptime_token: TokenIndex, + extern_export_token: TokenIndex, + lib_name: *Node, + type_node: *Node, + align_node: *Node, + section_node: *Node, + init_node: *Node, + }); + + pub const RequiredFields = struct { + mut_token: TokenIndex, + name_token: TokenIndex, + semicolon_token: TokenIndex, + }; + + pub fn getTrailer(self: *const VarDecl, comptime name: []const u8) ?TrailerFlags.Field(name) { + const trailers_start = @ptrCast([*]const u8, self) + @sizeOf(VarDecl); + return self.trailer_flags.get(trailers_start, name); + } + + pub fn setTrailer(self: *VarDecl, comptime name: []const u8, value: TrailerFlags.Field(name)) void { + const trailers_start = @ptrCast([*]u8, self) + @sizeOf(VarDecl); + self.trailer_flags.set(trailers_start, name, value); + } + + pub fn create(allocator: *mem.Allocator, required: RequiredFields, trailers: anytype) !*VarDecl { + const trailer_flags = TrailerFlags.init(trailers); + const bytes = try allocator.alignedAlloc(u8, @alignOf(VarDecl), sizeInBytes(trailer_flags)); + const var_decl = @ptrCast(*VarDecl, bytes.ptr); + var_decl.* = .{ + .trailer_flags = trailer_flags, + .mut_token = required.mut_token, + .name_token = required.name_token, + .semicolon_token = required.semicolon_token, + }; + const trailers_start = bytes.ptr + @sizeOf(VarDecl); + trailer_flags.setMany(trailers_start, trailers); + return var_decl; + } + + pub fn destroy(self: *VarDecl, allocator: *mem.Allocator) void { + const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.trailer_flags)]; + allocator.free(bytes); + } + pub fn iterate(self: *const VarDecl, index: usize) ?*Node { var i = index; - if (self.type_node) |type_node| { + if (self.getTrailer("type_node")) |type_node| { if (i < 1) return type_node; i -= 1; } - if (self.align_node) |align_node| { + if (self.getTrailer("align_node")) |align_node| { if (i < 1) return align_node; i -= 1; } - if (self.section_node) |section_node| { + if (self.getTrailer("section_node")) |section_node| { if (i < 1) return section_node; i -= 1; } - if (self.init_node) |init_node| { + if (self.getTrailer("init_node")) |init_node| { if (i < 1) return init_node; i -= 1; } @@ -719,17 +761,21 @@ pub const Node = struct { } pub fn firstToken(self: *const VarDecl) TokenIndex { - if (self.visib_token) |visib_token| return visib_token; - if (self.thread_local_token) |thread_local_token| return thread_local_token; - if (self.comptime_token) |comptime_token| return comptime_token; - if (self.extern_export_token) |extern_export_token| return extern_export_token; - assert(self.lib_name == null); + if (self.getTrailer("visib_token")) |visib_token| return visib_token; + if (self.getTrailer("thread_local_token")) |thread_local_token| return thread_local_token; + if (self.getTrailer("comptime_token")) |comptime_token| return comptime_token; + if (self.getTrailer("extern_export_token")) |extern_export_token| return extern_export_token; + assert(self.getTrailer("lib_name") == null); return self.mut_token; } pub fn lastToken(self: *const VarDecl) TokenIndex { return self.semicolon_token; } + + fn sizeInBytes(trailer_flags: TrailerFlags) usize { + return @sizeOf(VarDecl) + trailer_flags.sizeInBytes(); + } }; pub const Use = struct { @@ -972,25 +1018,34 @@ pub const Node = struct { }; /// The params are directly after the FnProto in memory. - /// TODO have a flags field for the optional nodes, and have them appended - /// before or after the parameters in memory. + /// Next, each optional thing determined by a bit in `trailer_flags`. pub const FnProto = struct { base: Node = Node{ .id = .FnProto }, - doc_comments: ?*DocComment, - visib_token: ?TokenIndex, + trailer_flags: TrailerFlags, fn_token: TokenIndex, - name_token: ?TokenIndex, params_len: NodeIndex, return_type: ReturnType, - var_args_token: ?TokenIndex, - extern_export_inline_token: ?TokenIndex, - body_node: ?*Node, - lib_name: ?*Node, // populated if this is an extern declaration - align_expr: ?*Node, // populated if align(A) is present - section_expr: ?*Node, // populated if linksection(A) is present - callconv_expr: ?*Node, // populated if callconv(A) is present - is_extern_prototype: bool = false, // TODO: Remove once extern fn rewriting is - is_async: bool = false, // TODO: remove once async fn rewriting is + + pub const TrailerFlags = std.meta.TrailerFlags(struct { + doc_comments: *DocComment, + body_node: *Node, + lib_name: *Node, // populated if this is an extern declaration + align_expr: *Node, // populated if align(A) is present + section_expr: *Node, // populated if linksection(A) is present + callconv_expr: *Node, // populated if callconv(A) is present + visib_token: TokenIndex, + name_token: TokenIndex, + var_args_token: TokenIndex, + extern_export_inline_token: TokenIndex, + is_extern_prototype: void, // TODO: Remove once extern fn rewriting is + is_async: void, // TODO: remove once async fn rewriting is + }); + + pub const RequiredFields = struct { + fn_token: TokenIndex, + params_len: NodeIndex, + return_type: ReturnType, + }; pub const ReturnType = union(enum) { Explicit: *Node, @@ -1007,7 +1062,6 @@ pub const Node = struct { pub const ParamType = union(enum) { any_type: *Node, - var_args: TokenIndex, type_expr: *Node, }; @@ -1016,7 +1070,6 @@ pub const Node = struct { if (i < 1) { switch (self.param_type) { - .var_args => return null, .any_type, .type_expr => |node| return node, } } @@ -1030,34 +1083,79 @@ pub const Node = struct { if (self.noalias_token) |noalias_token| return noalias_token; if (self.name_token) |name_token| return name_token; switch (self.param_type) { - .var_args => |tok| return tok, .any_type, .type_expr => |node| return node.firstToken(), } } pub fn lastToken(self: *const ParamDecl) TokenIndex { switch (self.param_type) { - .var_args => |tok| return tok, .any_type, .type_expr => |node| return node.lastToken(), } } }; + /// For debugging purposes. + pub fn dump(self: *const FnProto) void { + const trailers_start = @alignCast( + @alignOf(ParamDecl), + @ptrCast([*]const u8, self) + @sizeOf(FnProto) + @sizeOf(ParamDecl) * self.params_len, + ); + std.debug.print("{*} flags: {b} name_token: {} {*} params_len: {}\n", .{ + self, + self.trailer_flags.bits, + self.getTrailer("name_token"), + self.trailer_flags.ptrConst(trailers_start, "name_token"), + self.params_len, + }); + } + + pub fn getTrailer(self: *const FnProto, comptime name: []const u8) ?TrailerFlags.Field(name) { + const trailers_start = @alignCast( + @alignOf(ParamDecl), + @ptrCast([*]const u8, self) + @sizeOf(FnProto) + @sizeOf(ParamDecl) * self.params_len, + ); + return self.trailer_flags.get(trailers_start, name); + } + + pub fn setTrailer(self: *FnProto, comptime name: []const u8, value: TrailerFlags.Field(name)) void { + const trailers_start = @alignCast( + @alignOf(ParamDecl), + @ptrCast([*]u8, self) + @sizeOf(FnProto) + @sizeOf(ParamDecl) * self.params_len, + ); + self.trailer_flags.set(trailers_start, name, value); + } + /// After this the caller must initialize the params list. - pub fn alloc(allocator: *mem.Allocator, params_len: NodeIndex) !*FnProto { - const bytes = try allocator.alignedAlloc(u8, @alignOf(FnProto), sizeInBytes(params_len)); - return @ptrCast(*FnProto, bytes.ptr); + pub fn create(allocator: *mem.Allocator, required: RequiredFields, trailers: anytype) !*FnProto { + const trailer_flags = TrailerFlags.init(trailers); + const bytes = try allocator.alignedAlloc(u8, @alignOf(FnProto), sizeInBytes( + required.params_len, + trailer_flags, + )); + const fn_proto = @ptrCast(*FnProto, bytes.ptr); + fn_proto.* = .{ + .trailer_flags = trailer_flags, + .fn_token = required.fn_token, + .params_len = required.params_len, + .return_type = required.return_type, + }; + const trailers_start = @alignCast( + @alignOf(ParamDecl), + bytes.ptr + @sizeOf(FnProto) + @sizeOf(ParamDecl) * required.params_len, + ); + trailer_flags.setMany(trailers_start, trailers); + return fn_proto; } - pub fn free(self: *FnProto, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.params_len)]; + pub fn destroy(self: *FnProto, allocator: *mem.Allocator) void { + const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.params_len, self.trailer_flags)]; allocator.free(bytes); } pub fn iterate(self: *const FnProto, index: usize) ?*Node { var i = index; - if (self.lib_name) |lib_name| { + if (self.getTrailer("lib_name")) |lib_name| { if (i < 1) return lib_name; i -= 1; } @@ -1066,23 +1164,21 @@ pub const Node = struct { 0 else switch (self.paramsConst()[self.params_len - 1].param_type) { .any_type, .type_expr => self.params_len, - .var_args => self.params_len - 1, }; if (i < params_len) { switch (self.paramsConst()[i].param_type) { .any_type => |n| return n, - .var_args => unreachable, .type_expr => |n| return n, } } i -= params_len; - if (self.align_expr) |align_expr| { + if (self.getTrailer("align_expr")) |align_expr| { if (i < 1) return align_expr; i -= 1; } - if (self.section_expr) |section_expr| { + if (self.getTrailer("section_expr")) |section_expr| { if (i < 1) return section_expr; i -= 1; } @@ -1095,7 +1191,7 @@ pub const Node = struct { .Invalid => {}, } - if (self.body_node) |body_node| { + if (self.getTrailer("body_node")) |body_node| { if (i < 1) return body_node; i -= 1; } @@ -1104,14 +1200,14 @@ pub const Node = struct { } pub fn firstToken(self: *const FnProto) TokenIndex { - if (self.visib_token) |visib_token| return visib_token; - if (self.extern_export_inline_token) |extern_export_inline_token| return extern_export_inline_token; - assert(self.lib_name == null); + if (self.getTrailer("visib_token")) |visib_token| return visib_token; + if (self.getTrailer("extern_export_inline_token")) |extern_export_inline_token| return extern_export_inline_token; + assert(self.getTrailer("lib_name") == null); return self.fn_token; } pub fn lastToken(self: *const FnProto) TokenIndex { - if (self.body_node) |body_node| return body_node.lastToken(); + if (self.getTrailer("body_node")) |body_node| return body_node.lastToken(); switch (self.return_type) { .Explicit, .InferErrorSet => |node| return node.lastToken(), .Invalid => |tok| return tok, @@ -1119,17 +1215,17 @@ pub const Node = struct { } pub fn params(self: *FnProto) []ParamDecl { - const decls_start = @ptrCast([*]u8, self) + @sizeOf(FnProto); - return @ptrCast([*]ParamDecl, decls_start)[0..self.params_len]; + const params_start = @ptrCast([*]u8, self) + @sizeOf(FnProto); + return @ptrCast([*]ParamDecl, params_start)[0..self.params_len]; } pub fn paramsConst(self: *const FnProto) []const ParamDecl { - const decls_start = @ptrCast([*]const u8, self) + @sizeOf(FnProto); - return @ptrCast([*]const ParamDecl, decls_start)[0..self.params_len]; + const params_start = @ptrCast([*]const u8, self) + @sizeOf(FnProto); + return @ptrCast([*]const ParamDecl, params_start)[0..self.params_len]; } - fn sizeInBytes(params_len: NodeIndex) usize { - return @sizeOf(FnProto) + @sizeOf(ParamDecl) * @as(usize, params_len); + fn sizeInBytes(params_len: NodeIndex, trailer_flags: TrailerFlags) usize { + return @sizeOf(FnProto) + @sizeOf(ParamDecl) * @as(usize, params_len) + trailer_flags.sizeInBytes(); } }; @@ -2829,6 +2925,9 @@ pub const Node = struct { } }; + /// TODO remove from the Node base struct + /// TODO actually maybe remove entirely in favor of iterating backward from Node.firstToken() + /// and forwards to find same-line doc comments. pub const DocComment = struct { base: Node = Node{ .id = .DocComment }, /// Points to the first doc comment token. API users are expected to iterate over the diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index d46833c23f64ee7941b4f8be728202540b3c5a1f..9f2aea390a3baf0ebf21042057ec5a8f2ef71b63 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -150,7 +150,7 @@ const Parser = struct { const visib_token = p.eatToken(.Keyword_pub); - if (p.parseTopLevelDecl() catch |err| switch (err) { + if (p.parseTopLevelDecl(doc_comments, visib_token) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.ParseError => { p.findNextContainerMember(); @@ -160,30 +160,7 @@ const Parser = struct { if (field_state == .seen) { field_state = .{ .end = visib_token orelse node.firstToken() }; } - switch (node.id) { - .FnProto => { - node.cast(Node.FnProto).?.doc_comments = doc_comments; - node.cast(Node.FnProto).?.visib_token = visib_token; - }, - .VarDecl => { - node.cast(Node.VarDecl).?.doc_comments = doc_comments; - node.cast(Node.VarDecl).?.visib_token = visib_token; - }, - .Use => { - node.cast(Node.Use).?.doc_comments = doc_comments; - node.cast(Node.Use).?.visib_token = visib_token; - }, - else => unreachable, - } try list.append(node); - if (try p.parseAppendedDocComment(node.lastToken())) |appended_comment| { - switch (node.id) { - .FnProto => {}, - .VarDecl => node.cast(Node.VarDecl).?.doc_comments = appended_comment, - .Use => node.cast(Node.Use).?.doc_comments = appended_comment, - else => unreachable, - } - } continue; } @@ -417,7 +394,7 @@ const Parser = struct { /// <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / (KEYWORD_inline / KEYWORD_noinline))? FnProto (SEMICOLON / Block) /// / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? VarDecl /// / KEYWORD_usingnamespace Expr SEMICOLON - fn parseTopLevelDecl(p: *Parser) !?*Node { + fn parseTopLevelDecl(p: *Parser, doc_comments: ?*Node.DocComment, visib_token: ?TokenIndex) !?*Node { var lib_name: ?*Node = null; const extern_export_inline_token = blk: { if (p.eatToken(.Keyword_export)) |token| break :blk token; @@ -430,20 +407,12 @@ const Parser = struct { break :blk null; }; - if (try p.parseFnProto()) |node| { - const fn_node = node.cast(Node.FnProto).?; - fn_node.*.extern_export_inline_token = extern_export_inline_token; - fn_node.*.lib_name = lib_name; - if (p.eatToken(.Semicolon)) |_| return node; - - if (try p.expectNodeRecoverable(parseBlock, .{ - // since parseBlock only return error.ParseError on - // a missing '}' we can assume this function was - // supposed to end here. - .ExpectedSemiOrLBrace = .{ .token = p.tok_i }, - })) |body_node| { - fn_node.body_node = body_node; - } + if (try p.parseFnProto(.top_level, .{ + .doc_comments = doc_comments, + .visib_token = visib_token, + .extern_export_inline_token = extern_export_inline_token, + .lib_name = lib_name, + })) |node| { return node; } @@ -460,12 +429,13 @@ const Parser = struct { const thread_local_token = p.eatToken(.Keyword_threadlocal); - if (try p.parseVarDecl()) |node| { - var var_decl = node.cast(Node.VarDecl).?; - var_decl.*.thread_local_token = thread_local_token; - var_decl.*.comptime_token = null; - var_decl.*.extern_export_token = extern_export_inline_token; - var_decl.*.lib_name = lib_name; + if (try p.parseVarDecl(.{ + .doc_comments = doc_comments, + .visib_token = visib_token, + .thread_local_token = thread_local_token, + .extern_export_token = extern_export_inline_token, + .lib_name = lib_name, + })) |node| { return node; } @@ -485,21 +455,41 @@ const Parser = struct { return error.ParseError; } - return p.parseUse(); + const use_token = p.eatToken(.Keyword_usingnamespace) orelse return null; + const expr = try p.expectNode(parseExpr, .{ + .ExpectedExpr = .{ .token = p.tok_i }, + }); + const semicolon_token = try p.expectToken(.Semicolon); + + const node = try p.arena.allocator.create(Node.Use); + node.* = .{ + .doc_comments = doc_comments orelse try p.parseAppendedDocComment(semicolon_token), + .visib_token = visib_token, + .use_token = use_token, + .expr = expr, + .semicolon_token = semicolon_token, + }; + + return &node.base; } /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? EXCLAMATIONMARK? (Keyword_anytype / TypeExpr) - fn parseFnProto(p: *Parser) !?*Node { + fn parseFnProto(p: *Parser, level: enum { top_level, as_type }, fields: struct { + doc_comments: ?*Node.DocComment = null, + visib_token: ?TokenIndex = null, + extern_export_inline_token: ?TokenIndex = null, + lib_name: ?*Node = null, + }) !?*Node { // TODO: Remove once extern/async fn rewriting is - var is_async = false; - var is_extern = false; + var is_async: ?void = null; + var is_extern_prototype: ?void = null; const cc_token: ?TokenIndex = blk: { if (p.eatToken(.Keyword_extern)) |token| { - is_extern = true; + is_extern_prototype = {}; break :blk token; } if (p.eatToken(.Keyword_async)) |token| { - is_async = true; + is_async = {}; break :blk token; } break :blk null; @@ -513,6 +503,7 @@ const Parser = struct { const lparen = try p.expectToken(.LParen); const params = try p.parseParamDeclList(); defer p.gpa.free(params); + const var_args_token = p.eatToken(.Ellipsis3); const rparen = try p.expectToken(.RParen); const align_expr = try p.parseByteAlign(); const section_expr = try p.parseLinkSection(); @@ -535,37 +526,53 @@ const Parser = struct { else R{ .Explicit = return_type_expr.? }; - const var_args_token = if (params.len > 0) blk: { - const param_type = params[params.len - 1].param_type; - break :blk if (param_type == .var_args) param_type.var_args else null; - } else - null; + const body_node: ?*Node = switch (level) { + .top_level => blk: { + if (p.eatToken(.Semicolon)) |_| { + break :blk null; + } + break :blk try p.expectNodeRecoverable(parseBlock, .{ + // Since parseBlock only return error.ParseError on + // a missing '}' we can assume this function was + // supposed to end here. + .ExpectedSemiOrLBrace = .{ .token = p.tok_i }, + }); + }, + .as_type => null, + }; - const fn_proto_node = try Node.FnProto.alloc(&p.arena.allocator, params.len); - fn_proto_node.* = .{ - .doc_comments = null, - .visib_token = null, - .fn_token = fn_token, - .name_token = name_token, + const fn_proto_node = try Node.FnProto.create(&p.arena.allocator, .{ .params_len = params.len, + .fn_token = fn_token, .return_type = return_type, + }, .{ + .doc_comments = fields.doc_comments, + .visib_token = fields.visib_token, + .name_token = name_token, .var_args_token = var_args_token, - .extern_export_inline_token = null, - .body_node = null, - .lib_name = null, + .extern_export_inline_token = fields.extern_export_inline_token, + .body_node = body_node, + .lib_name = fields.lib_name, .align_expr = align_expr, .section_expr = section_expr, .callconv_expr = callconv_expr, - .is_extern_prototype = is_extern, + .is_extern_prototype = is_extern_prototype, .is_async = is_async, - }; + }); std.mem.copy(Node.FnProto.ParamDecl, fn_proto_node.params(), params); return &fn_proto_node.base; } /// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON - fn parseVarDecl(p: *Parser) !?*Node { + fn parseVarDecl(p: *Parser, fields: struct { + doc_comments: ?*Node.DocComment = null, + visib_token: ?TokenIndex = null, + thread_local_token: ?TokenIndex = null, + extern_export_token: ?TokenIndex = null, + lib_name: ?*Node = null, + comptime_token: ?TokenIndex = null, + }) !?*Node { const mut_token = p.eatToken(.Keyword_const) orelse p.eatToken(.Keyword_var) orelse return null; @@ -587,23 +594,25 @@ const Parser = struct { } else null; const semicolon_token = try p.expectToken(.Semicolon); - const node = try p.arena.allocator.create(Node.VarDecl); - node.* = .{ - .doc_comments = null, - .visib_token = null, - .thread_local_token = null, - .name_token = name_token, - .eq_token = eq_token, + const doc_comments = fields.doc_comments orelse try p.parseAppendedDocComment(semicolon_token); + + const node = try Node.VarDecl.create(&p.arena.allocator, .{ .mut_token = mut_token, - .comptime_token = null, - .extern_export_token = null, - .lib_name = null, + .name_token = name_token, + .semicolon_token = semicolon_token, + }, .{ + .doc_comments = doc_comments, + .visib_token = fields.visib_token, + .thread_local_token = fields.thread_local_token, + .eq_token = eq_token, + .comptime_token = fields.comptime_token, + .extern_export_token = fields.extern_export_token, + .lib_name = fields.lib_name, .type_node = type_node, .align_node = align_node, .section_node = section_node, .init_node = init_node, - .semicolon_token = semicolon_token, - }; + }); return &node.base; } @@ -663,10 +672,9 @@ const Parser = struct { fn parseStatement(p: *Parser) Error!?*Node { const comptime_token = p.eatToken(.Keyword_comptime); - const var_decl_node = try p.parseVarDecl(); - if (var_decl_node) |node| { - const var_decl = node.cast(Node.VarDecl).?; - var_decl.comptime_token = comptime_token; + if (try p.parseVarDecl(.{ + .comptime_token = comptime_token, + })) |node| { return node; } @@ -1527,7 +1535,7 @@ const Parser = struct { if (try p.parseAnonLiteral()) |node| return node; if (try p.parseErrorSetDecl()) |node| return node; if (try p.parseFloatLiteral()) |node| return node; - if (try p.parseFnProto()) |node| return node; + if (try p.parseFnProto(.as_type, .{})) |node| return node; if (try p.parseGroupedExpr()) |node| return node; if (try p.parseLabeledTypeExpr()) |node| return node; if (try p.parseIdentifier()) |node| return node; @@ -2028,7 +2036,6 @@ const Parser = struct { // TODO cast from tuple to error union is broken const P = Node.FnProto.ParamDecl.ParamType; if (try p.parseAnyType()) |node| return P{ .any_type = node }; - if (p.eatToken(.Ellipsis3)) |token| return P{ .var_args = token }; if (try p.parseTypeExpr()) |node| return P{ .type_expr = node }; return null; } @@ -3149,21 +3156,6 @@ const Parser = struct { return &node.base; } - fn parseUse(p: *Parser) !?*Node { - const token = p.eatToken(.Keyword_usingnamespace) orelse return null; - const node = try p.arena.allocator.create(Node.Use); - node.* = .{ - .doc_comments = null, - .visib_token = null, - .use_token = token, - .expr = try p.expectNode(parseExpr, .{ - .ExpectedExpr = .{ .token = p.tok_i }, - }), - .semicolon_token = try p.expectToken(.Semicolon), - }; - return &node.base; - } - /// IfPrefix Body (KEYWORD_else Payload? Body)? fn parseIf(p: *Parser, bodyParseFn: NodeParseFn) !?*Node { const node = (try p.parseIfPrefix()) orelse return null; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index da06d93b96e9171e05aa9304f39a7960491bcea5..aa0a76c8a5a14e4bc3008e73c979af82108c1717 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1,4 +1,32 @@ -const builtin = @import("builtin"); +test "zig fmt: convert var to anytype" { + // TODO remove in next release cycle + try testTransform( + \\pub fn main( + \\ a: var, + \\ bar: var, + \\) void {} + , + \\pub fn main( + \\ a: anytype, + \\ bar: anytype, + \\) void {} + \\ + ); +} + +test "zig fmt: noasync to nosuspend" { + // TODO: remove this + try testTransform( + \\pub fn main() void { + \\ noasync call(); + \\} + , + \\pub fn main() void { + \\ nosuspend call(); + \\} + \\ + ); +} test "recovery: top level" { try testError( @@ -3146,20 +3174,6 @@ test "zig fmt: hexadeciaml float literals with underscore separators" { ); } -test "zig fmt: noasync to nosuspend" { - // TODO: remove this - try testTransform( - \\pub fn main() void { - \\ noasync call(); - \\} - , - \\pub fn main() void { - \\ nosuspend call(); - \\} - \\ - ); -} - test "zig fmt: convert async fn into callconv(.Async)" { try testTransform( \\async fn foo() void {} @@ -3180,18 +3194,9 @@ test "zig fmt: convert extern fn proto into callconv(.C)" { ); } -test "zig fmt: convert var to anytype" { - // TODO remove in next release cycle - try testTransform( - \\pub fn main( - \\ a: var, - \\ bar: var, - \\) void {} - , - \\pub fn main( - \\ a: anytype, - \\ bar: anytype, - \\) void {} +test "zig fmt: C var args" { + try testCanonical( + \\pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; \\ ); } diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index b75e6f6ca3e1e2fa21cfcf2c9cfe5da88266b2a5..4f9eb0e4cdf221e84f9c661ef6b14a91662fef28 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -227,9 +227,9 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr .FnProto => { const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); - try renderDocComments(tree, stream, fn_proto, indent, start_col); + try renderDocComments(tree, stream, fn_proto, fn_proto.getTrailer("doc_comments"), indent, start_col); - if (fn_proto.body_node) |body_node| { + if (fn_proto.getTrailer("body_node")) |body_node| { try renderExpression(allocator, stream, tree, indent, start_col, decl, .Space); try renderExpression(allocator, stream, tree, indent, start_col, body_node, space); } else { @@ -252,14 +252,14 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr .VarDecl => { const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", decl); - try renderDocComments(tree, stream, var_decl, indent, start_col); + try renderDocComments(tree, stream, var_decl, var_decl.getTrailer("doc_comments"), indent, start_col); try renderVarDecl(allocator, stream, tree, indent, start_col, var_decl); }, .TestDecl => { const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl); - try renderDocComments(tree, stream, test_decl, indent, start_col); + try renderDocComments(tree, stream, test_decl, test_decl.doc_comments, indent, start_col); try renderToken(tree, stream, test_decl.test_token, indent, start_col, .Space); try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, .Space); try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, space); @@ -268,7 +268,7 @@ fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tr .ContainerField => { const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl); - try renderDocComments(tree, stream, field, indent, start_col); + try renderDocComments(tree, stream, field, field.doc_comments, indent, start_col); if (field.comptime_token) |t| { try renderToken(tree, stream, t, indent, start_col, .Space); // comptime } @@ -1409,7 +1409,7 @@ fn renderExpression( .ErrorTag => { const tag = @fieldParentPtr(ast.Node.ErrorTag, "base", base); - try renderDocComments(tree, stream, tag, indent, start_col); + try renderDocComments(tree, stream, tag, tag.doc_comments, indent, start_col); return renderToken(tree, stream, tag.name_token, indent, start_col, space); // name }, @@ -1483,23 +1483,23 @@ fn renderExpression( .FnProto => { const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", base); - if (fn_proto.visib_token) |visib_token_index| { + if (fn_proto.getTrailer("visib_token")) |visib_token_index| { const visib_token = tree.token_ids[visib_token_index]; assert(visib_token == .Keyword_pub or visib_token == .Keyword_export); try renderToken(tree, stream, visib_token_index, indent, start_col, Space.Space); // pub } - if (fn_proto.extern_export_inline_token) |extern_export_inline_token| { - if (!fn_proto.is_extern_prototype) + if (fn_proto.getTrailer("extern_export_inline_token")) |extern_export_inline_token| { + if (fn_proto.getTrailer("is_extern_prototype") == null) try renderToken(tree, stream, extern_export_inline_token, indent, start_col, Space.Space); // extern/export/inline } - if (fn_proto.lib_name) |lib_name| { + if (fn_proto.getTrailer("lib_name")) |lib_name| { try renderExpression(allocator, stream, tree, indent, start_col, lib_name, Space.Space); } - const lparen = if (fn_proto.name_token) |name_token| blk: { + const lparen = if (fn_proto.getTrailer("name_token")) |name_token| blk: { try renderToken(tree, stream, fn_proto.fn_token, indent, start_col, Space.Space); // fn try renderToken(tree, stream, name_token, indent, start_col, Space.None); // name break :blk tree.nextToken(name_token); @@ -1512,11 +1512,11 @@ fn renderExpression( const rparen = tree.prevToken( // the first token for the annotation expressions is the left // parenthesis, hence the need for two prevToken - if (fn_proto.align_expr) |align_expr| + if (fn_proto.getTrailer("align_expr")) |align_expr| tree.prevToken(tree.prevToken(align_expr.firstToken())) - else if (fn_proto.section_expr) |section_expr| + else if (fn_proto.getTrailer("section_expr")) |section_expr| tree.prevToken(tree.prevToken(section_expr.firstToken())) - else if (fn_proto.callconv_expr) |callconv_expr| + else if (fn_proto.getTrailer("callconv_expr")) |callconv_expr| tree.prevToken(tree.prevToken(callconv_expr.firstToken())) else switch (fn_proto.return_type) { .Explicit => |node| node.firstToken(), @@ -1537,11 +1537,14 @@ fn renderExpression( for (fn_proto.params()) |param_decl, i| { try renderParamDecl(allocator, stream, tree, indent, start_col, param_decl, Space.None); - if (i + 1 < fn_proto.params_len) { + if (i + 1 < fn_proto.params_len or fn_proto.getTrailer("var_args_token") != null) { const comma = tree.nextToken(param_decl.lastToken()); try renderToken(tree, stream, comma, indent, start_col, Space.Space); // , } } + if (fn_proto.getTrailer("var_args_token")) |var_args_token| { + try renderToken(tree, stream, var_args_token, indent, start_col, Space.None); + } } else { // one param per line const new_indent = indent + indent_delta; @@ -1551,12 +1554,16 @@ fn renderExpression( try stream.writeByteNTimes(' ', new_indent); try renderParamDecl(allocator, stream, tree, new_indent, start_col, param_decl, Space.Comma); } + if (fn_proto.getTrailer("var_args_token")) |var_args_token| { + try stream.writeByteNTimes(' ', new_indent); + try renderToken(tree, stream, var_args_token, new_indent, start_col, Space.Comma); + } try stream.writeByteNTimes(' ', indent); } try renderToken(tree, stream, rparen, indent, start_col, Space.Space); // ) - if (fn_proto.align_expr) |align_expr| { + if (fn_proto.getTrailer("align_expr")) |align_expr| { const align_rparen = tree.nextToken(align_expr.lastToken()); const align_lparen = tree.prevToken(align_expr.firstToken()); const align_kw = tree.prevToken(align_lparen); @@ -1567,7 +1574,7 @@ fn renderExpression( try renderToken(tree, stream, align_rparen, indent, start_col, Space.Space); // ) } - if (fn_proto.section_expr) |section_expr| { + if (fn_proto.getTrailer("section_expr")) |section_expr| { const section_rparen = tree.nextToken(section_expr.lastToken()); const section_lparen = tree.prevToken(section_expr.firstToken()); const section_kw = tree.prevToken(section_lparen); @@ -1578,7 +1585,7 @@ fn renderExpression( try renderToken(tree, stream, section_rparen, indent, start_col, Space.Space); // ) } - if (fn_proto.callconv_expr) |callconv_expr| { + if (fn_proto.getTrailer("callconv_expr")) |callconv_expr| { const callconv_rparen = tree.nextToken(callconv_expr.lastToken()); const callconv_lparen = tree.prevToken(callconv_expr.firstToken()); const callconv_kw = tree.prevToken(callconv_lparen); @@ -1587,9 +1594,9 @@ fn renderExpression( try renderToken(tree, stream, callconv_lparen, indent, start_col, Space.None); // ( try renderExpression(allocator, stream, tree, indent, start_col, callconv_expr, Space.None); try renderToken(tree, stream, callconv_rparen, indent, start_col, Space.Space); // ) - } else if (fn_proto.is_extern_prototype) { + } else if (fn_proto.getTrailer("is_extern_prototype") != null) { try stream.writeAll("callconv(.C) "); - } else if (fn_proto.is_async) { + } else if (fn_proto.getTrailer("is_async") != null) { try stream.writeAll("callconv(.Async) "); } @@ -2177,64 +2184,69 @@ fn renderVarDecl( start_col: *usize, var_decl: *ast.Node.VarDecl, ) (@TypeOf(stream).Error || Error)!void { - if (var_decl.visib_token) |visib_token| { + if (var_decl.getTrailer("visib_token")) |visib_token| { try renderToken(tree, stream, visib_token, indent, start_col, Space.Space); // pub } - if (var_decl.extern_export_token) |extern_export_token| { + if (var_decl.getTrailer("extern_export_token")) |extern_export_token| { try renderToken(tree, stream, extern_export_token, indent, start_col, Space.Space); // extern - if (var_decl.lib_name) |lib_name| { + if (var_decl.getTrailer("lib_name")) |lib_name| { try renderExpression(allocator, stream, tree, indent, start_col, lib_name, Space.Space); // "lib" } } - if (var_decl.comptime_token) |comptime_token| { + if (var_decl.getTrailer("comptime_token")) |comptime_token| { try renderToken(tree, stream, comptime_token, indent, start_col, Space.Space); // comptime } - if (var_decl.thread_local_token) |thread_local_token| { + if (var_decl.getTrailer("thread_local_token")) |thread_local_token| { try renderToken(tree, stream, thread_local_token, indent, start_col, Space.Space); // threadlocal } try renderToken(tree, stream, var_decl.mut_token, indent, start_col, Space.Space); // var - const name_space = if (var_decl.type_node == null and (var_decl.align_node != null or - var_decl.section_node != null or var_decl.init_node != null)) Space.Space else Space.None; + const name_space = if (var_decl.getTrailer("type_node") == null and + (var_decl.getTrailer("align_node") != null or + var_decl.getTrailer("section_node") != null or + var_decl.getTrailer("init_node") != null)) + Space.Space + else + Space.None; try renderToken(tree, stream, var_decl.name_token, indent, start_col, name_space); - if (var_decl.type_node) |type_node| { + if (var_decl.getTrailer("type_node")) |type_node| { try renderToken(tree, stream, tree.nextToken(var_decl.name_token), indent, start_col, Space.Space); - const s = if (var_decl.align_node != null or - var_decl.section_node != null or - var_decl.init_node != null) Space.Space else Space.None; + const s = if (var_decl.getTrailer("align_node") != null or + var_decl.getTrailer("section_node") != null or + var_decl.getTrailer("init_node") != null) Space.Space else Space.None; try renderExpression(allocator, stream, tree, indent, start_col, type_node, s); } - if (var_decl.align_node) |align_node| { + if (var_decl.getTrailer("align_node")) |align_node| { const lparen = tree.prevToken(align_node.firstToken()); const align_kw = tree.prevToken(lparen); const rparen = tree.nextToken(align_node.lastToken()); try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( try renderExpression(allocator, stream, tree, indent, start_col, align_node, Space.None); - const s = if (var_decl.section_node != null or var_decl.init_node != null) Space.Space else Space.None; + const s = if (var_decl.getTrailer("section_node") != null or var_decl.getTrailer("init_node") != null) Space.Space else Space.None; try renderToken(tree, stream, rparen, indent, start_col, s); // ) } - if (var_decl.section_node) |section_node| { + if (var_decl.getTrailer("section_node")) |section_node| { const lparen = tree.prevToken(section_node.firstToken()); const section_kw = tree.prevToken(lparen); const rparen = tree.nextToken(section_node.lastToken()); try renderToken(tree, stream, section_kw, indent, start_col, Space.None); // linksection try renderToken(tree, stream, lparen, indent, start_col, Space.None); // ( try renderExpression(allocator, stream, tree, indent, start_col, section_node, Space.None); - const s = if (var_decl.init_node != null) Space.Space else Space.None; + const s = if (var_decl.getTrailer("init_node") != null) Space.Space else Space.None; try renderToken(tree, stream, rparen, indent, start_col, s); // ) } - if (var_decl.init_node) |init_node| { + if (var_decl.getTrailer("init_node")) |init_node| { const s = if (init_node.id == .MultilineStringLiteral) Space.None else Space.Space; - try renderToken(tree, stream, var_decl.eq_token.?, indent, start_col, s); // = + try renderToken(tree, stream, var_decl.getTrailer("eq_token").?, indent, start_col, s); // = try renderExpression(allocator, stream, tree, indent, start_col, init_node, Space.None); } @@ -2250,7 +2262,7 @@ fn renderParamDecl( param_decl: ast.Node.FnProto.ParamDecl, space: Space, ) (@TypeOf(stream).Error || Error)!void { - try renderDocComments(tree, stream, param_decl, indent, start_col); + try renderDocComments(tree, stream, param_decl, param_decl.doc_comments, indent, start_col); if (param_decl.comptime_token) |comptime_token| { try renderToken(tree, stream, comptime_token, indent, start_col, Space.Space); @@ -2263,7 +2275,6 @@ fn renderParamDecl( try renderToken(tree, stream, tree.nextToken(name_token), indent, start_col, Space.Space); // : } switch (param_decl.param_type) { - .var_args => |token| try renderToken(tree, stream, token, indent, start_col, space), .any_type, .type_expr => |node| try renderExpression(allocator, stream, tree, indent, start_col, node, space), } } @@ -2519,10 +2530,11 @@ fn renderDocComments( tree: *ast.Tree, stream: anytype, node: anytype, + doc_comments: ?*ast.Node.DocComment, indent: usize, start_col: *usize, ) (@TypeOf(stream).Error || Error)!void { - const comment = node.doc_comments orelse return; + const comment = doc_comments orelse return; return renderDocCommentsToken(tree, stream, comment, node.firstToken(), indent, start_col); } diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index de9e1f23864f69a36bd968b37ffe80a6d5b04728..0a8a5152cf99c562db5b8827d25b870ed571a993 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1130,7 +1130,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; defer fn_type_scope.instructions.deinit(self.gpa); - const body_node = fn_proto.body_node orelse + const body_node = fn_proto.getTrailer("body_node") orelse return self.failTok(&fn_type_scope.base, fn_proto.fn_token, "TODO implement extern functions", .{}); const param_decls = fn_proto.params(); @@ -1138,21 +1138,23 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { for (param_decls) |param_decl, i| { const param_type_node = switch (param_decl.param_type) { .any_type => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement anytype parameter", .{}), - .var_args => |tok| return self.failTok(&fn_type_scope.base, tok, "TODO implement var args", .{}), .type_expr => |node| node, }; param_types[i] = try self.astGenExpr(&fn_type_scope.base, param_type_node); } - if (fn_proto.lib_name) |lib_name| { + if (fn_proto.getTrailer("var_args_token")) |var_args_token| { + return self.failTok(&fn_type_scope.base, var_args_token, "TODO implement var args", .{}); + } + if (fn_proto.getTrailer("lib_name")) |lib_name| { return self.failNode(&fn_type_scope.base, lib_name, "TODO implement function library name", .{}); } - if (fn_proto.align_expr) |align_expr| { + if (fn_proto.getTrailer("align_expr")) |align_expr| { return self.failNode(&fn_type_scope.base, align_expr, "TODO implement function align expression", .{}); } - if (fn_proto.section_expr) |sect_expr| { + if (fn_proto.getTrailer("section_expr")) |sect_expr| { return self.failNode(&fn_type_scope.base, sect_expr, "TODO implement function section expression", .{}); } - if (fn_proto.callconv_expr) |callconv_expr| { + if (fn_proto.getTrailer("callconv_expr")) |callconv_expr| { return self.failNode( &fn_type_scope.base, callconv_expr, @@ -1265,10 +1267,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { self.bin_file.freeDecl(decl); } - if (fn_proto.extern_export_inline_token) |maybe_export_token| { + if (fn_proto.getTrailer("extern_export_inline_token")) |maybe_export_token| { if (tree.token_ids[maybe_export_token] == .Keyword_export) { const export_src = tree.token_locs[maybe_export_token].start; - const name_loc = tree.token_locs[fn_proto.name_token.?]; + const name_loc = tree.token_locs[fn_proto.getTrailer("name_token").?]; const name = tree.tokenSliceLoc(name_loc); // The scope needs to have the decl in it. try self.analyzeExport(&block_scope.base, export_src, name, decl); @@ -1867,7 +1869,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { for (decls) |src_decl, decl_i| { if (src_decl.cast(ast.Node.FnProto)) |fn_proto| { // We will create a Decl for it regardless of analysis status. - const name_tok = fn_proto.name_token orelse { + const name_tok = fn_proto.getTrailer("name_token") orelse { @panic("TODO missing function name"); }; @@ -1893,7 +1895,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void { } else { const new_decl = try self.createNewDecl(&root_scope.base, name, decl_i, name_hash, contents_hash); root_scope.decls.appendAssumeCapacity(new_decl); - if (fn_proto.extern_export_inline_token) |maybe_export_token| { + if (fn_proto.getTrailer("extern_export_inline_token")) |maybe_export_token| { if (tree.token_ids[maybe_export_token] == .Keyword_export) { self.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl }); } diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index c45befd372c2c2401d89cc155669eb006b165382..b9ab28cc1757c4d25e9312eeb75e56244679d02c 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -586,11 +586,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { for (proto_node.params()) |*param, i| { const param_name = if (param.name_token) |name_tok| tokenSlice(c, name_tok) - else if (param.param_type == .var_args) { - assert(i + 1 == proto_node.params_len); - proto_node.params_len -= 1; - break; - } else + else return failDecl(c, fn_decl_loc, fn_name, "function {} parameter has no name", .{fn_name}); const c_param = ZigClangFunctionDecl_getParamDecl(fn_decl, param_id); @@ -602,10 +598,20 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { if (!is_const) { const bare_arg_name = try std.fmt.allocPrint(c.arena, "arg_{}", .{mangled_param_name}); const arg_name = try block_scope.makeMangledName(c, bare_arg_name); - const node = try transCreateNodeVarDecl(c, false, false, mangled_param_name); - node.eq_token = try appendToken(c, .Equal, "="); - node.init_node = try transCreateNodeIdentifier(c, arg_name); - node.semicolon_token = try appendToken(c, .Semicolon, ";"); + + const mut_tok = try appendToken(c, .Keyword_var, "var"); + const name_tok = try appendIdentifier(c, mangled_param_name); + const eq_token = try appendToken(c, .Equal, "="); + const init_node = try transCreateNodeIdentifier(c, arg_name); + const semicolon_token = try appendToken(c, .Semicolon, ";"); + const node = try ast.Node.VarDecl.create(c.arena, .{ + .mut_token = mut_tok, + .name_token = name_tok, + .semicolon_token = semicolon_token, + }, .{ + .eq_token = eq_token, + .init_node = init_node, + }); try block_scope.statements.append(&node.base); param.name_token = try appendIdentifier(c, arg_name); _ = try appendToken(c, .Colon, ":"); @@ -622,7 +628,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { => return failDecl(c, fn_decl_loc, fn_name, "unable to translate function", .{}), }; const body_node = try block_scope.complete(rp.c); - proto_node.body_node = &body_node.base; + proto_node.setTrailer("body_node", &body_node.base); return addTopLevelDecl(c, fn_name, &proto_node.base); } @@ -725,23 +731,20 @@ fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void { break :blk null; }; - const node = try c.arena.create(ast.Node.VarDecl); - node.* = .{ - .doc_comments = null, + const node = try ast.Node.VarDecl.create(c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = try appendToken(c, .Semicolon, ";"), + }, .{ .visib_token = visib_tok, .thread_local_token = thread_local_token, - .name_token = name_tok, .eq_token = eq_tok, - .mut_token = mut_tok, - .comptime_token = null, .extern_export_token = extern_tok, - .lib_name = null, .type_node = type_node, .align_node = align_expr, .section_node = linksection_expr, .init_node = init_node, - .semicolon_token = try appendToken(c, .Semicolon, ";"), - }; + }); return addTopLevelDecl(c, checked_name, &node.base); } @@ -795,26 +798,41 @@ fn transTypeDef(c: *Context, typedef_decl: *const ZigClangTypedefNameDecl, top_l _ = try c.decl_table.put(@ptrToInt(ZigClangTypedefNameDecl_getCanonicalDecl(typedef_decl)), checked_name); const node = (try transCreateNodeTypedef(rp, typedef_decl, true, checked_name)) orelse return null; - try addTopLevelDecl(c, checked_name, &node.base); + try addTopLevelDecl(c, checked_name, node); return transCreateNodeIdentifier(c, checked_name); } -fn transCreateNodeTypedef(rp: RestorePoint, typedef_decl: *const ZigClangTypedefNameDecl, toplevel: bool, checked_name: []const u8) Error!?*ast.Node.VarDecl { - const node = try transCreateNodeVarDecl(rp.c, toplevel, true, checked_name); - node.eq_token = try appendToken(rp.c, .Equal, "="); - +fn transCreateNodeTypedef( + rp: RestorePoint, + typedef_decl: *const ZigClangTypedefNameDecl, + toplevel: bool, + checked_name: []const u8, +) Error!?*ast.Node { + const visib_tok = if (toplevel) try appendToken(rp.c, .Keyword_pub, "pub") else null; + const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(rp.c, checked_name); + const eq_token = try appendToken(rp.c, .Equal, "="); const child_qt = ZigClangTypedefNameDecl_getUnderlyingType(typedef_decl); const typedef_loc = ZigClangTypedefNameDecl_getLocation(typedef_decl); - node.init_node = transQualType(rp, child_qt, typedef_loc) catch |err| switch (err) { + const init_node = transQualType(rp, child_qt, typedef_loc) catch |err| switch (err) { error.UnsupportedType => { try failDecl(rp.c, typedef_loc, checked_name, "unable to resolve typedef child type", .{}); return null; }, error.OutOfMemory => |e| return e, }; + const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); - node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); - return node; + const node = try ast.Node.VarDecl.create(rp.c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .visib_token = visib_tok, + .eq_token = eq_token, + .init_node = init_node, + }); + return &node.base; } fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?*ast.Node { @@ -847,12 +865,14 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?* const name = try std.fmt.allocPrint(c.arena, "{}_{}", .{ container_kind_name, bare_name }); _ = try c.decl_table.put(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)), name); - const node = try transCreateNodeVarDecl(c, !is_unnamed, true, name); + const visib_tok = if (!is_unnamed) try appendToken(c, .Keyword_pub, "pub") else null; + const mut_tok = try appendToken(c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(c, name); - node.eq_token = try appendToken(c, .Equal, "="); + const eq_token = try appendToken(c, .Equal, "="); var semicolon: ast.TokenIndex = undefined; - node.init_node = blk: { + const init_node = blk: { const rp = makeRestorePoint(c); const record_def = ZigClangRecordDecl_getDefinition(record_decl) orelse { const opaque = try transCreateNodeOpaqueType(c); @@ -959,7 +979,16 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?* semicolon = try appendToken(c, .Semicolon, ";"); break :blk &container_node.base; }; - node.semicolon_token = semicolon; + + const node = try ast.Node.VarDecl.create(c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon, + }, .{ + .visib_token = visib_tok, + .eq_token = eq_token, + .init_node = init_node, + }); try addTopLevelDecl(c, name, &node.base); if (!is_unnamed) @@ -982,10 +1011,13 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No const name = try std.fmt.allocPrint(c.arena, "enum_{}", .{bare_name}); _ = try c.decl_table.put(@ptrToInt(ZigClangEnumDecl_getCanonicalDecl(enum_decl)), name); - const node = try transCreateNodeVarDecl(c, !is_unnamed, true, name); - node.eq_token = try appendToken(c, .Equal, "="); - node.init_node = if (ZigClangEnumDecl_getDefinition(enum_decl)) |enum_def| blk: { + const visib_tok = if (!is_unnamed) try appendToken(c, .Keyword_pub, "pub") else null; + const mut_tok = try appendToken(c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(c, name); + const eq_token = try appendToken(c, .Equal, "="); + + const init_node = if (ZigClangEnumDecl_getDefinition(enum_decl)) |enum_def| blk: { var pure_enum = true; var it = ZigClangEnumDecl_enumerator_begin(enum_def); var end_it = ZigClangEnumDecl_enumerator_end(enum_def); @@ -1063,8 +1095,10 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No // In C each enum value is in the global namespace. So we put them there too. // At this point we can rely on the enum emitting successfully. - const tld_node = try transCreateNodeVarDecl(c, true, true, enum_val_name); - tld_node.eq_token = try appendToken(c, .Equal, "="); + const tld_visib_tok = try appendToken(c, .Keyword_pub, "pub"); + const tld_mut_tok = try appendToken(c, .Keyword_const, "const"); + const tld_name_tok = try appendIdentifier(c, enum_val_name); + const tld_eq_token = try appendToken(c, .Equal, "="); const cast_node = try rp.c.createBuiltinCall("@enumToInt", 1); const enum_ident = try transCreateNodeIdentifier(c, name); const period_tok = try appendToken(c, .Period, "."); @@ -1078,8 +1112,17 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No }; cast_node.params()[0] = &field_access_node.base; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); - tld_node.init_node = &cast_node.base; - tld_node.semicolon_token = try appendToken(c, .Semicolon, ";"); + const tld_init_node = &cast_node.base; + const tld_semicolon_token = try appendToken(c, .Semicolon, ";"); + const tld_node = try ast.Node.VarDecl.create(c.arena, .{ + .name_token = tld_name_tok, + .mut_token = tld_mut_tok, + .semicolon_token = tld_semicolon_token, + }, .{ + .visib_token = tld_visib_tok, + .eq_token = tld_eq_token, + .init_node = tld_init_node, + }); try addTopLevelDecl(c, field_name, &tld_node.base); } // make non exhaustive @@ -1109,7 +1152,16 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No } else try transCreateNodeOpaqueType(c); - node.semicolon_token = try appendToken(c, .Semicolon, ";"); + const semicolon_token = try appendToken(c, .Semicolon, ";"); + const node = try ast.Node.VarDecl.create(c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .visib_token = visib_tok, + .eq_token = eq_token, + .init_node = init_node, + }); try addTopLevelDecl(c, name, &node.base); if (!is_unnamed) @@ -1118,10 +1170,22 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No } fn createAlias(c: *Context, alias: anytype) !void { - const node = try transCreateNodeVarDecl(c, true, true, alias.alias); - node.eq_token = try appendToken(c, .Equal, "="); - node.init_node = try transCreateNodeIdentifier(c, alias.name); - node.semicolon_token = try appendToken(c, .Semicolon, ";"); + const visib_tok = try appendToken(c, .Keyword_pub, "pub"); + const mut_tok = try appendToken(c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(c, alias.alias); + const eq_token = try appendToken(c, .Equal, "="); + const init_node = try transCreateNodeIdentifier(c, alias.name); + const semicolon_token = try appendToken(c, .Semicolon, ";"); + + const node = try ast.Node.VarDecl.create(c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .visib_token = visib_tok, + .eq_token = eq_token, + .init_node = init_node, + }); return addTopLevelDecl(c, alias.alias, &node.base); } @@ -1461,13 +1525,17 @@ fn transDeclStmtOne( @ptrCast(*const ZigClangNamedDecl, var_decl), )); const mangled_name = try block_scope.makeMangledName(c, name); - const node = try transCreateNodeVarDecl(c, false, ZigClangQualType_isConstQualified(qual_type), mangled_name); + const mut_tok = if (ZigClangQualType_isConstQualified(qual_type)) + try appendToken(c, .Keyword_const, "const") + else + try appendToken(c, .Keyword_var, "var"); + const name_tok = try appendIdentifier(c, mangled_name); _ = try appendToken(c, .Colon, ":"); const loc = ZigClangDecl_getLocation(decl); - node.type_node = try transQualType(rp, qual_type, loc); + const type_node = try transQualType(rp, qual_type, loc); - node.eq_token = try appendToken(c, .Equal, "="); + const eq_token = try appendToken(c, .Equal, "="); var init_node = if (ZigClangVarDecl_getInit(var_decl)) |expr| try transExprCoercing(rp, scope, expr, .used, .r_value) else @@ -1478,8 +1546,17 @@ fn transDeclStmtOne( builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); init_node = &builtin_node.base; } - node.init_node = init_node; - node.semicolon_token = try appendToken(c, .Semicolon, ";"); + const semicolon_token = try appendToken(c, .Semicolon, ";"); + const node = try ast.Node.VarDecl.create(c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .thread_local_token = thread_local_token, + .eq_token = eq_token, + .type_node = type_node, + .init_node = init_node, + }); return &node.base; }, .Typedef => { @@ -1494,7 +1571,7 @@ fn transDeclStmtOne( const mangled_name = try block_scope.makeMangledName(c, name); const node = (try transCreateNodeTypedef(rp, typedef_decl, false, mangled_name)) orelse return error.UnsupportedTranslation; - return &node.base; + return node; }, else => |kind| return revertAndWarn( rp, @@ -3105,12 +3182,21 @@ fn transCreatePreCrement( defer block_scope.deinit(); const ref = try block_scope.makeMangledName(rp.c, "ref"); - const node = try transCreateNodeVarDecl(rp.c, false, true, ref); - node.eq_token = try appendToken(rp.c, .Equal, "="); + const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(rp.c, ref); + const eq_token = try appendToken(rp.c, .Equal, "="); const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); - node.init_node = &rhs_node.base; - node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const init_node = &rhs_node.base; + const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const node = try ast.Node.VarDecl.create(rp.c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .eq_token = eq_token, + .init_node = init_node, + }); try block_scope.statements.append(&node.base); const lhs_node = try transCreateNodeIdentifier(rp.c, ref); @@ -3171,12 +3257,21 @@ fn transCreatePostCrement( defer block_scope.deinit(); const ref = try block_scope.makeMangledName(rp.c, "ref"); - const node = try transCreateNodeVarDecl(rp.c, false, true, ref); - node.eq_token = try appendToken(rp.c, .Equal, "="); + const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(rp.c, ref); + const eq_token = try appendToken(rp.c, .Equal, "="); const rhs_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); rhs_node.rhs = try transExpr(rp, scope, op_expr, .used, .r_value); - node.init_node = &rhs_node.base; - node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const init_node = &rhs_node.base; + const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const node = try ast.Node.VarDecl.create(rp.c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .eq_token = eq_token, + .init_node = init_node, + }); try block_scope.statements.append(&node.base); const lhs_node = try transCreateNodeIdentifier(rp.c, ref); @@ -3184,10 +3279,19 @@ fn transCreatePostCrement( _ = try appendToken(rp.c, .Semicolon, ";"); const tmp = try block_scope.makeMangledName(rp.c, "tmp"); - const tmp_node = try transCreateNodeVarDecl(rp.c, false, true, tmp); - tmp_node.eq_token = try appendToken(rp.c, .Equal, "="); - tmp_node.init_node = ref_node; - tmp_node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const tmp_mut_tok = try appendToken(rp.c, .Keyword_const, "const"); + const tmp_name_tok = try appendIdentifier(rp.c, tmp); + const tmp_eq_token = try appendToken(rp.c, .Equal, "="); + const tmp_init_node = ref_node; + const tmp_semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const tmp_node = try ast.Node.VarDecl.create(rp.c.arena, .{ + .name_token = tmp_name_tok, + .mut_token = tmp_mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .eq_token = tmp_eq_token, + .init_node = tmp_init_node, + }); try block_scope.statements.append(&tmp_node.base); const token = try appendToken(rp.c, op_tok_id, bytes); @@ -3325,12 +3429,21 @@ fn transCreateCompoundAssign( defer block_scope.deinit(); const ref = try block_scope.makeMangledName(rp.c, "ref"); - const node = try transCreateNodeVarDecl(rp.c, false, true, ref); - node.eq_token = try appendToken(rp.c, .Equal, "="); + const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(rp.c, ref); + const eq_token = try appendToken(rp.c, .Equal, "="); const addr_node = try transCreateNodeSimplePrefixOp(rp.c, .AddressOf, .Ampersand, "&"); addr_node.rhs = try transExpr(rp, scope, lhs, .used, .l_value); - node.init_node = &addr_node.base; - node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const init_node = &addr_node.base; + const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const node = try ast.Node.VarDecl.create(rp.c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .eq_token = eq_token, + .init_node = init_node, + }); try block_scope.statements.append(&node.base); const lhs_node = try transCreateNodeIdentifier(rp.c, ref); @@ -3375,8 +3488,8 @@ fn transCreateCompoundAssign( const rhs_bin = try transCreateNodeInfixOp(rp, scope, ref_node, bin_op, bin_token, rhs_node, .used, false); _ = try appendToken(rp.c, .Semicolon, ";"); - const eq_token = try appendToken(rp.c, .Equal, "="); - const assign = try transCreateNodeInfixOp(rp, scope, ref_node, .Assign, eq_token, rhs_bin, .used, false); + const ass_eq_token = try appendToken(rp.c, .Equal, "="); + const assign = try transCreateNodeInfixOp(rp, scope, ref_node, .Assign, ass_eq_token, rhs_bin, .used, false); try block_scope.statements.append(assign); } @@ -3494,10 +3607,19 @@ fn transBinaryConditionalOperator(rp: RestorePoint, scope: *Scope, stmt: *const defer block_scope.deinit(); const mangled_name = try block_scope.makeMangledName(rp.c, "cond_temp"); - const tmp_var = try transCreateNodeVarDecl(rp.c, false, true, mangled_name); - tmp_var.eq_token = try appendToken(rp.c, .Equal, "="); - tmp_var.init_node = try transExpr(rp, &block_scope.base, cond_expr, .used, .r_value); - tmp_var.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(rp.c, mangled_name); + const eq_token = try appendToken(rp.c, .Equal, "="); + const init_node = try transExpr(rp, &block_scope.base, cond_expr, .used, .r_value); + const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const tmp_var = try ast.Node.VarDecl.create(rp.c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .eq_token = eq_token, + .init_node = init_node, + }); try block_scope.statements.append(&tmp_var.base); const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label); @@ -3932,9 +4054,9 @@ fn transCreateNodeAssign( defer block_scope.deinit(); const tmp = try block_scope.makeMangledName(rp.c, "tmp"); - - const node = try transCreateNodeVarDecl(rp.c, false, true, tmp); - node.eq_token = try appendToken(rp.c, .Equal, "="); + const mut_tok = try appendToken(rp.c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(rp.c, tmp); + const eq_token = try appendToken(rp.c, .Equal, "="); var rhs_node = try transExpr(rp, &block_scope.base, rhs, .used, .r_value); if (!exprIsBooleanType(lhs) and isBoolRes(rhs_node)) { const builtin_node = try rp.c.createBuiltinCall("@boolToInt", 1); @@ -3942,16 +4064,24 @@ fn transCreateNodeAssign( builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); rhs_node = &builtin_node.base; } - node.init_node = rhs_node; - node.semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const init_node = rhs_node; + const semicolon_token = try appendToken(rp.c, .Semicolon, ";"); + const node = try ast.Node.VarDecl.create(rp.c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .eq_token = eq_token, + .init_node = init_node, + }); try block_scope.statements.append(&node.base); const lhs_node = try transExpr(rp, &block_scope.base, lhs, .used, .l_value); - const eq_token = try appendToken(rp.c, .Equal, "="); + const lhs_eq_token = try appendToken(rp.c, .Equal, "="); const ident = try transCreateNodeIdentifier(rp.c, tmp); _ = try appendToken(rp.c, .Semicolon, ";"); - const assign = try transCreateNodeInfixOp(rp, &block_scope.base, lhs_node, .Assign, eq_token, ident, .used, false); + const assign = try transCreateNodeInfixOp(rp, &block_scope.base, lhs_node, .Assign, lhs_eq_token, ident, .used, false); try block_scope.statements.append(assign); const break_node = try transCreateNodeBreak(rp.c, label_name); @@ -4232,28 +4362,10 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a _ = try appendToken(c, .RParen, ")"); - const fn_proto = try ast.Node.FnProto.alloc(c.arena, fn_params.items.len); - fn_proto.* = .{ - .doc_comments = null, - .visib_token = pub_tok, - .fn_token = fn_tok, - .name_token = name_tok, - .params_len = fn_params.items.len, - .return_type = proto_alias.return_type, - .var_args_token = null, - .extern_export_inline_token = inline_tok, - .body_node = null, - .lib_name = null, - .align_expr = null, - .section_expr = null, - .callconv_expr = null, - }; - mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items); - const block_lbrace = try appendToken(c, .LBrace, "{"); const return_expr = try transCreateNodeReturnExpr(c); - const unwrap_expr = try transCreateNodeUnwrapNull(c, ref.cast(ast.Node.VarDecl).?.init_node.?); + const unwrap_expr = try transCreateNodeUnwrapNull(c, ref.cast(ast.Node.VarDecl).?.getTrailer("init_node").?); const call_expr = try c.createCall(unwrap_expr, fn_params.items.len); const call_params = call_expr.params(); @@ -4277,7 +4389,18 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a .rbrace = try appendToken(c, .RBrace, "}"), }; block.statements()[0] = &return_expr.base; - fn_proto.body_node = &block.base; + + const fn_proto = try ast.Node.FnProto.create(c.arena, .{ + .params_len = fn_params.items.len, + .fn_token = fn_tok, + .return_type = proto_alias.return_type, + }, .{ + .visib_token = pub_tok, + .name_token = name_tok, + .extern_export_inline_token = inline_tok, + .body_node = &block.base, + }); + mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items); return &fn_proto.base; } @@ -4356,31 +4479,6 @@ fn transCreateNodeBreak(c: *Context, label: ?[]const u8) !*ast.Node.ControlFlowE return node; } -fn transCreateNodeVarDecl(c: *Context, is_pub: bool, is_const: bool, name: []const u8) !*ast.Node.VarDecl { - const visib_tok = if (is_pub) try appendToken(c, .Keyword_pub, "pub") else null; - const mut_tok = if (is_const) try appendToken(c, .Keyword_const, "const") else try appendToken(c, .Keyword_var, "var"); - const name_tok = try appendIdentifier(c, name); - - const node = try c.arena.create(ast.Node.VarDecl); - node.* = .{ - .doc_comments = null, - .visib_token = visib_tok, - .thread_local_token = null, - .name_token = name_tok, - .eq_token = undefined, - .mut_token = mut_tok, - .comptime_token = null, - .extern_export_token = null, - .lib_name = null, - .type_node = null, - .align_node = null, - .section_node = null, - .init_node = null, - .semicolon_token = undefined, - }; - return node; -} - fn transCreateNodeWhile(c: *Context) !*ast.Node.While { const while_tok = try appendToken(c, .Keyword_while, "while"); _ = try appendToken(c, .LParen, "("); @@ -4782,19 +4880,12 @@ fn finishTransFnProto( } } - if (is_var_args) { + const var_args_token: ?ast.TokenIndex = if (is_var_args) blk: { if (param_count > 0) { _ = try appendToken(rp.c, .Comma, ","); } - - fn_params.addOneAssumeCapacity().* = .{ - .doc_comments = null, - .comptime_token = null, - .noalias_token = null, - .name_token = null, - .param_type = .{ .var_args = try appendToken(rp.c, .Ellipsis3, "...") }, - }; - } + break :blk try appendToken(rp.c, .Ellipsis3, "..."); + } else null; const rparen_tok = try appendToken(rp.c, .RParen, ")"); @@ -4860,22 +4951,31 @@ fn finishTransFnProto( } }; - const fn_proto = try ast.Node.FnProto.alloc(rp.c.arena, fn_params.items.len); - fn_proto.* = .{ - .doc_comments = null, - .visib_token = pub_tok, - .fn_token = fn_tok, - .name_token = name_tok, + // We need to reserve an undefined (but non-null) body node to set later. + var body_node: ?*ast.Node = null; + if (fn_decl_context) |ctx| { + if (ctx.has_body) { + // TODO: we should be able to use undefined here but + // it causes a bug. This is undefined without zig language + // being aware of it. + body_node = @intToPtr(*ast.Node, 0x08); + } + } + + const fn_proto = try ast.Node.FnProto.create(rp.c.arena, .{ .params_len = fn_params.items.len, .return_type = .{ .Explicit = return_type_node }, - .var_args_token = null, // TODO this field is broken in the AST data model + .fn_token = fn_tok, + }, .{ + .visib_token = pub_tok, + .name_token = name_tok, .extern_export_inline_token = extern_export_inline_tok, - .body_node = null, - .lib_name = null, .align_expr = align_expr, .section_expr = linksection_expr, .callconv_expr = callconv_expr, - }; + .body_node = body_node, + .var_args_token = var_args_token, + }); mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items); return fn_proto; } @@ -4923,23 +5023,15 @@ pub fn failDecl(c: *Context, loc: ZigClangSourceLocation, name: []const u8, comp }; call_node.params()[0] = &msg_node.base; - const var_decl_node = try c.arena.create(ast.Node.VarDecl); - var_decl_node.* = .{ - .doc_comments = null, - .visib_token = pub_tok, - .thread_local_token = null, + const var_decl_node = try ast.Node.VarDecl.create(c.arena, .{ .name_token = name_tok, - .eq_token = eq_tok, .mut_token = const_tok, - .comptime_token = null, - .extern_export_token = null, - .lib_name = null, - .type_node = null, - .align_node = null, - .section_node = null, - .init_node = &call_node.base, .semicolon_token = semi_tok, - }; + }, .{ + .visib_token = pub_tok, + .eq_token = eq_tok, + .init_node = &call_node.base, + }); try addTopLevelDecl(c, name, &var_decl_node.base); } @@ -5132,10 +5224,12 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { const scope = &c.global_scope.base; - const node = try transCreateNodeVarDecl(c, true, true, name); - node.eq_token = try appendToken(c, .Equal, "="); + const visib_tok = try appendToken(c, .Keyword_pub, "pub"); + const mut_tok = try appendToken(c, .Keyword_const, "const"); + const name_tok = try appendIdentifier(c, name); + const eq_token = try appendToken(c, .Equal, "="); - node.init_node = try parseCExpr(c, it, source, source_loc, scope); + const init_node = try parseCExpr(c, it, source, source_loc, scope); const last = it.next().?; if (last.id != .Eof and last.id != .Nl) return failDecl( @@ -5146,7 +5240,16 @@ fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, n .{@tagName(last.id)}, ); - node.semicolon_token = try appendToken(c, .Semicolon, ";"); + const semicolon_token = try appendToken(c, .Semicolon, ";"); + const node = try ast.Node.VarDecl.create(c.arena, .{ + .name_token = name_tok, + .mut_token = mut_tok, + .semicolon_token = semicolon_token, + }, .{ + .visib_token = visib_tok, + .eq_token = eq_token, + .init_node = init_node, + }); _ = try c.global_scope.macro_table.put(name, &node.base); } @@ -5223,24 +5326,6 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, const type_of = try c.createBuiltinCall("@TypeOf", 1); - const fn_proto = try ast.Node.FnProto.alloc(c.arena, fn_params.items.len); - fn_proto.* = .{ - .visib_token = pub_tok, - .extern_export_inline_token = inline_tok, - .fn_token = fn_tok, - .name_token = name_tok, - .params_len = fn_params.items.len, - .return_type = .{ .Explicit = &type_of.base }, - .doc_comments = null, - .var_args_token = null, - .body_node = null, - .lib_name = null, - .align_expr = null, - .section_expr = null, - .callconv_expr = null, - }; - mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items); - const return_expr = try transCreateNodeReturnExpr(c); const expr = try parseCExpr(c, it, source, source_loc, scope); const last = it.next().?; @@ -5266,7 +5351,18 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, try block_scope.statements.append(&return_expr.base); const block_node = try block_scope.complete(c); - fn_proto.body_node = &block_node.base; + const fn_proto = try ast.Node.FnProto.create(c.arena, .{ + .fn_token = fn_tok, + .params_len = fn_params.items.len, + .return_type = .{ .Explicit = &type_of.base }, + }, .{ + .visib_token = pub_tok, + .extern_export_inline_token = inline_tok, + .name_token = name_tok, + .body_node = &block_node.base, + }); + mem.copy(ast.Node.FnProto.ParamDecl, fn_proto.params(), fn_params.items); + _ = try c.global_scope.macro_table.put(name, &fn_proto.base); } @@ -6030,7 +6126,7 @@ fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { const ident = node.cast(ast.Node.Identifier).?; if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { if (value.cast(ast.Node.VarDecl)) |var_decl| - return getContainer(c, var_decl.init_node.?); + return getContainer(c, var_decl.getTrailer("init_node").?); } }, @@ -6060,7 +6156,7 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { if (ref.cast(ast.Node.Identifier)) |ident| { if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { if (value.cast(ast.Node.VarDecl)) |var_decl| { - if (var_decl.type_node) |ty| + if (var_decl.getTrailer("type_node")) |ty| return getContainer(c, ty); } } @@ -6084,7 +6180,7 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { } fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto { - const init = if (ref.cast(ast.Node.VarDecl)) |v| v.init_node.? else return null; + const init = if (ref.cast(ast.Node.VarDecl)) |v| v.getTrailer("init_node").? else return null; if (getContainerTypeOf(c, init)) |ty_node| { if (ty_node.cast(ast.Node.OptionalType)) |prefix| { if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| { diff --git a/test/translate_c.zig b/test/translate_c.zig index 738f9523ad3a5b4222f8931c2cf0073231152105..905c74bca2de451acab4edde2017a40051f9dc7e 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -2797,7 +2797,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub fn a() callconv(.C) void {} \\pub fn b() callconv(.C) void {} \\pub export fn c() void {} - \\pub fn foo() callconv(.C) void {} + \\pub fn foo(...) callconv(.C) void {} }); cases.add("casting away const and volatile", -- 2.54.0 From c5b7322319a9559423fcda5c20c3b2e22338b2b4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Jul 2020 04:04:04 -0700 Subject: [PATCH 280/295] TrailerFlags test: fix bad alignment assumption on 32-bit --- lib/std/meta/trailer_flags.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/std/meta/trailer_flags.zig b/lib/std/meta/trailer_flags.zig index e1e216cde64421c64870656e914304f1568b099f..0da7ca815a1f31b849063bc7d218bc44b98549dd 100644 --- a/lib/std/meta/trailer_flags.zig +++ b/lib/std/meta/trailer_flags.zig @@ -124,7 +124,6 @@ test "TrailerFlags" { .b = true, .c = 1234, }); - testing.expect(flags.sizeInBytes() == 16); const slice = try testing.allocator.allocAdvanced(u8, 8, flags.sizeInBytes(), .exact); defer testing.allocator.free(slice); -- 2.54.0 From e70d6d19f5a937504ce0e2f79d03a6275b6ff357 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Jul 2020 15:42:02 -0700 Subject: [PATCH 281/295] stage2: extract AST=>ZIR code to separate file --- src-self-hosted/Module.zig | 486 ++----------------------------------- src-self-hosted/astgen.zig | 476 ++++++++++++++++++++++++++++++++++++ 2 files changed, 490 insertions(+), 472 deletions(-) create mode 100644 src-self-hosted/astgen.zig diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 0a8a5152cf99c562db5b8827d25b870ed571a993..902efbe33975dceff06dbd6d0d8bcfdb389a787e 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -19,6 +19,7 @@ const Body = ir.Body; const ast = std.zig.ast; const trace = @import("tracy.zig").trace; const liveness = @import("liveness.zig"); +const astgen = @import("astgen.zig"); /// General-purpose allocator. Used for both temporary and long-term storage. gpa: *Allocator, @@ -76,6 +77,8 @@ deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, keep_source_files_loaded: bool, +pub const InnerError = error{ OutOfMemory, AnalysisFail }; + const WorkItem = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Decl, @@ -944,8 +947,6 @@ pub fn getAllErrorsAlloc(self: *Module) !AllErrors { }; } -const InnerError = error{ OutOfMemory, AnalysisFail }; - pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { while (self.work_queue.readItem()) |work_item| switch (work_item) { .codegen_decl => |decl| switch (decl.analysis) { @@ -1140,7 +1141,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .any_type => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement anytype parameter", .{}), .type_expr => |node| node, }; - param_types[i] = try self.astGenExpr(&fn_type_scope.base, param_type_node); + param_types[i] = try astgen.expr(self, &fn_type_scope.base, param_type_node); } if (fn_proto.getTrailer("var_args_token")) |var_args_token| { return self.failTok(&fn_type_scope.base, var_args_token, "TODO implement var args", .{}); @@ -1168,7 +1169,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .Invalid => |tok| return self.failTok(&fn_type_scope.base, tok, "unable to parse return type", .{}), }; - const return_type_inst = try self.astGenExpr(&fn_type_scope.base, return_type_expr); + const return_type_inst = try astgen.expr(self, &fn_type_scope.base, return_type_expr); const fn_src = tree.token_locs[fn_proto.fn_token].start; const fn_type_inst = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.FnType, .{ .return_type = return_type_inst, @@ -1209,7 +1210,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const body_block = body_node.cast(ast.Node.Block).?; - try self.astGenBlock(&gen_scope.base, body_block); + try astgen.blockExpr(self, &gen_scope.base, body_block); if (!fn_type.fnReturnType().isNoReturn() and (gen_scope.instructions.items.len == 0 or !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) @@ -1298,465 +1299,6 @@ fn analyzeBodyValueAsType(self: *Module, block_scope: *Scope.Block, body: zir.Mo unreachable; } -fn astGenExpr(self: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir.Inst { - switch (ast_node.id) { - .Identifier => return self.astGenIdent(scope, @fieldParentPtr(ast.Node.Identifier, "base", ast_node)), - .Asm => return self.astGenAsm(scope, @fieldParentPtr(ast.Node.Asm, "base", ast_node)), - .StringLiteral => return self.astGenStringLiteral(scope, @fieldParentPtr(ast.Node.StringLiteral, "base", ast_node)), - .IntegerLiteral => return self.astGenIntegerLiteral(scope, @fieldParentPtr(ast.Node.IntegerLiteral, "base", ast_node)), - .BuiltinCall => return self.astGenBuiltinCall(scope, @fieldParentPtr(ast.Node.BuiltinCall, "base", ast_node)), - .Call => return self.astGenCall(scope, @fieldParentPtr(ast.Node.Call, "base", ast_node)), - .Unreachable => return self.astGenUnreachable(scope, @fieldParentPtr(ast.Node.Unreachable, "base", ast_node)), - .ControlFlowExpression => return self.astGenControlFlowExpression(scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)), - .If => return self.astGenIf(scope, @fieldParentPtr(ast.Node.If, "base", ast_node)), - .InfixOp => return self.astGenInfixOp(scope, @fieldParentPtr(ast.Node.InfixOp, "base", ast_node)), - .BoolNot => return self.astGenBoolNot(scope, @fieldParentPtr(ast.Node.BoolNot, "base", ast_node)), - else => return self.failNode(scope, ast_node, "TODO implement astGenExpr for {}", .{@tagName(ast_node.id)}), - } -} - -fn astGenBoolNot(self: *Module, scope: *Scope, node: *ast.Node.BoolNot) InnerError!*zir.Inst { - const operand = try self.astGenExpr(scope, node.rhs); - const tree = scope.tree(); - const src = tree.token_locs[node.op_token].start; - return self.addZIRInst(scope, src, zir.Inst.BoolNot, .{ .operand = operand }, .{}); -} - -fn astGenInfixOp(self: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) InnerError!*zir.Inst { - switch (infix_node.op) { - .Assign => { - if (infix_node.lhs.id == .Identifier) { - const ident = @fieldParentPtr(ast.Node.Identifier, "base", infix_node.lhs); - const tree = scope.tree(); - const ident_name = tree.tokenSlice(ident.token); - if (std.mem.eql(u8, ident_name, "_")) { - return self.astGenExpr(scope, infix_node.rhs); - } else { - return self.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); - } - } else { - return self.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); - } - }, - .Add => { - const lhs = try self.astGenExpr(scope, infix_node.lhs); - const rhs = try self.astGenExpr(scope, infix_node.rhs); - - const tree = scope.tree(); - const src = tree.token_locs[infix_node.op_token].start; - - return self.addZIRInst(scope, src, zir.Inst.Add, .{ .lhs = lhs, .rhs = rhs }, .{}); - }, - .BangEqual, - .EqualEqual, - .GreaterThan, - .GreaterOrEqual, - .LessThan, - .LessOrEqual, - => { - const lhs = try self.astGenExpr(scope, infix_node.lhs); - const rhs = try self.astGenExpr(scope, infix_node.rhs); - - const tree = scope.tree(); - const src = tree.token_locs[infix_node.op_token].start; - - const op: std.math.CompareOperator = switch (infix_node.op) { - .BangEqual => .neq, - .EqualEqual => .eq, - .GreaterThan => .gt, - .GreaterOrEqual => .gte, - .LessThan => .lt, - .LessOrEqual => .lte, - else => unreachable, - }; - - return self.addZIRInst(scope, src, zir.Inst.Cmp, .{ - .lhs = lhs, - .op = op, - .rhs = rhs, - }, .{}); - }, - else => |op| { - return self.failNode(scope, &infix_node.base, "TODO implement infix operator {}", .{op}); - }, - } -} - -fn astGenIf(self: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.Inst { - if (if_node.payload) |payload| { - return self.failNode(scope, payload, "TODO implement astGenIf for optionals", .{}); - } - if (if_node.@"else") |else_node| { - if (else_node.payload) |payload| { - return self.failNode(scope, payload, "TODO implement astGenIf for error unions", .{}); - } - } - var block_scope: Scope.GenZIR = .{ - .decl = scope.decl().?, - .arena = scope.arena(), - .instructions = .{}, - }; - defer block_scope.instructions.deinit(self.gpa); - - const cond = try self.astGenExpr(&block_scope.base, if_node.condition); - - const tree = scope.tree(); - const if_src = tree.token_locs[if_node.if_token].start; - const condbr = try self.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{ - .condition = cond, - .true_body = undefined, // populated below - .false_body = undefined, // populated below - }, .{}); - - const block = try self.addZIRInstBlock(scope, if_src, .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), - }); - var then_scope: Scope.GenZIR = .{ - .decl = block_scope.decl, - .arena = block_scope.arena, - .instructions = .{}, - }; - defer then_scope.instructions.deinit(self.gpa); - - const then_result = try self.astGenExpr(&then_scope.base, if_node.body); - if (!then_result.tag.isNoReturn()) { - const then_src = tree.token_locs[if_node.body.lastToken()].start; - _ = try self.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ - .block = block, - .operand = then_result, - }, .{}); - } - condbr.positionals.true_body = .{ - .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items), - }; - - var else_scope: Scope.GenZIR = .{ - .decl = block_scope.decl, - .arena = block_scope.arena, - .instructions = .{}, - }; - defer else_scope.instructions.deinit(self.gpa); - - if (if_node.@"else") |else_node| { - const else_result = try self.astGenExpr(&else_scope.base, else_node.body); - if (!else_result.tag.isNoReturn()) { - const else_src = tree.token_locs[else_node.body.lastToken()].start; - _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ - .block = block, - .operand = else_result, - }, .{}); - } - } else { - // TODO Optimization opportunity: we can avoid an allocation and a memcpy here - // by directly allocating the body for this one instruction. - const else_src = tree.token_locs[if_node.lastToken()].start; - _ = try self.addZIRInst(&else_scope.base, else_src, zir.Inst.BreakVoid, .{ - .block = block, - }, .{}); - } - condbr.positionals.false_body = .{ - .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), - }; - - return &block.base; -} - -fn astGenControlFlowExpression( - self: *Module, - scope: *Scope, - cfe: *ast.Node.ControlFlowExpression, -) InnerError!*zir.Inst { - switch (cfe.kind) { - .Break => return self.failNode(scope, &cfe.base, "TODO implement astGenExpr for Break", .{}), - .Continue => return self.failNode(scope, &cfe.base, "TODO implement astGenExpr for Continue", .{}), - .Return => {}, - } - const tree = scope.tree(); - const src = tree.token_locs[cfe.ltoken].start; - if (cfe.rhs) |rhs_node| { - const operand = try self.astGenExpr(scope, rhs_node); - return self.addZIRInst(scope, src, zir.Inst.Return, .{ .operand = operand }, .{}); - } else { - return self.addZIRInst(scope, src, zir.Inst.ReturnVoid, .{}, .{}); - } -} - -fn astGenIdent(self: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerError!*zir.Inst { - const tree = scope.tree(); - const ident_name = tree.tokenSlice(ident.token); - const src = tree.token_locs[ident.token].start; - if (mem.eql(u8, ident_name, "_")) { - return self.failNode(scope, &ident.base, "TODO implement '_' identifier", .{}); - } - - if (getSimplePrimitiveValue(ident_name)) |typed_value| { - return self.addZIRInstConst(scope, src, typed_value); - } - - if (ident_name.len >= 2) integer: { - const first_c = ident_name[0]; - if (first_c == 'i' or first_c == 'u') { - const is_signed = first_c == 'i'; - const bit_count = std.fmt.parseInt(u16, ident_name[1..], 10) catch |err| switch (err) { - error.Overflow => return self.failNode( - scope, - &ident.base, - "primitive integer type '{}' exceeds maximum bit width of 65535", - .{ident_name}, - ), - error.InvalidCharacter => break :integer, - }; - const val = switch (bit_count) { - 8 => if (is_signed) Value.initTag(.i8_type) else Value.initTag(.u8_type), - 16 => if (is_signed) Value.initTag(.i16_type) else Value.initTag(.u16_type), - 32 => if (is_signed) Value.initTag(.i32_type) else Value.initTag(.u32_type), - 64 => if (is_signed) Value.initTag(.i64_type) else Value.initTag(.u64_type), - else => return self.failNode(scope, &ident.base, "TODO implement arbitrary integer bitwidth types", .{}), - }; - return self.addZIRInstConst(scope, src, .{ - .ty = Type.initTag(.type), - .val = val, - }); - } - } - - if (self.lookupDeclName(scope, ident_name)) |decl| { - return try self.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); - } - - // Function parameter - if (scope.decl()) |decl| { - if (tree.root_node.decls()[decl.src_index].cast(ast.Node.FnProto)) |fn_proto| { - for (fn_proto.params()) |param, i| { - const param_name = tree.tokenSlice(param.name_token.?); - if (mem.eql(u8, param_name, ident_name)) { - return try self.addZIRInst(scope, src, zir.Inst.Arg, .{ .index = i }, .{}); - } - } - } - } - - return self.failNode(scope, &ident.base, "TODO implement local variable identifier lookup", .{}); -} - -fn astGenStringLiteral(self: *Module, scope: *Scope, str_lit: *ast.Node.StringLiteral) InnerError!*zir.Inst { - const tree = scope.tree(); - const unparsed_bytes = tree.tokenSlice(str_lit.token); - const arena = scope.arena(); - - var bad_index: usize = undefined; - const bytes = std.zig.parseStringLiteral(arena, unparsed_bytes, &bad_index) catch |err| switch (err) { - error.InvalidCharacter => { - const bad_byte = unparsed_bytes[bad_index]; - const src = tree.token_locs[str_lit.token].start; - return self.fail(scope, src + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte}); - }, - else => |e| return e, - }; - - const src = tree.token_locs[str_lit.token].start; - return self.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); -} - -fn astGenIntegerLiteral(self: *Module, scope: *Scope, int_lit: *ast.Node.IntegerLiteral) InnerError!*zir.Inst { - const arena = scope.arena(); - const tree = scope.tree(); - const prefixed_bytes = tree.tokenSlice(int_lit.token); - const base = if (mem.startsWith(u8, prefixed_bytes, "0x")) - 16 - else if (mem.startsWith(u8, prefixed_bytes, "0o")) - 8 - else if (mem.startsWith(u8, prefixed_bytes, "0b")) - 2 - else - @as(u8, 10); - - const bytes = if (base == 10) - prefixed_bytes - else - prefixed_bytes[2..]; - - if (std.fmt.parseInt(u64, bytes, base)) |small_int| { - const int_payload = try arena.create(Value.Payload.Int_u64); - int_payload.* = .{ .int = small_int }; - const src = tree.token_locs[int_lit.token].start; - return self.addZIRInstConst(scope, src, .{ - .ty = Type.initTag(.comptime_int), - .val = Value.initPayload(&int_payload.base), - }); - } else |err| { - return self.failTok(scope, int_lit.token, "TODO implement int literals that don't fit in a u64", .{}); - } -} - -fn astGenBlock(self: *Module, scope: *Scope, block_node: *ast.Node.Block) !void { - const tracy = trace(@src()); - defer tracy.end(); - - if (block_node.label) |label| { - return self.failTok(scope, label, "TODO implement labeled blocks", .{}); - } - for (block_node.statements()) |statement| { - _ = try self.astGenExpr(scope, statement); - } -} - -fn astGenAsm(self: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zir.Inst { - if (asm_node.outputs.len != 0) { - return self.failNode(scope, &asm_node.base, "TODO implement asm with an output", .{}); - } - const arena = scope.arena(); - const tree = scope.tree(); - - const inputs = try arena.alloc(*zir.Inst, asm_node.inputs.len); - const args = try arena.alloc(*zir.Inst, asm_node.inputs.len); - - for (asm_node.inputs) |input, i| { - // TODO semantically analyze constraints - inputs[i] = try self.astGenExpr(scope, input.constraint); - args[i] = try self.astGenExpr(scope, input.expr); - } - - const src = tree.token_locs[asm_node.asm_token].start; - const return_type = try self.addZIRInstConst(scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.void_type), - }); - const asm_inst = try self.addZIRInst(scope, src, zir.Inst.Asm, .{ - .asm_source = try self.astGenExpr(scope, asm_node.template), - .return_type = return_type, - }, .{ - .@"volatile" = asm_node.volatile_token != null, - //.clobbers = TODO handle clobbers - .inputs = inputs, - .args = args, - }); - return asm_inst; -} - -fn astGenBuiltinCall(self: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { - const tree = scope.tree(); - const builtin_name = tree.tokenSlice(call.builtin_token); - const src = tree.token_locs[call.builtin_token].start; - - inline for (std.meta.declarations(zir.Inst)) |inst| { - if (inst.data != .Type) continue; - const T = inst.data.Type; - if (!@hasDecl(T, "builtin_name")) continue; - if (std.mem.eql(u8, builtin_name, T.builtin_name)) { - var value: T = undefined; - const positionals = @typeInfo(std.meta.fieldInfo(T, "positionals").field_type).Struct; - if (positionals.fields.len == 0) { - return self.addZIRInst(scope, src, T, value.positionals, value.kw_args); - } - const arg_count: ?usize = if (positionals.fields[0].field_type == []*zir.Inst) null else positionals.fields.len; - if (arg_count) |some| { - if (call.params_len != some) { - return self.failTok( - scope, - call.builtin_token, - "expected {} parameter{}, found {}", - .{ some, if (some == 1) "" else "s", call.params_len }, - ); - } - const params = call.params(); - inline for (positionals.fields) |p, i| { - @field(value.positionals, p.name) = try self.astGenExpr(scope, params[i]); - } - } else { - return self.failTok(scope, call.builtin_token, "TODO var args builtin '{}'", .{builtin_name}); - } - - return self.addZIRInst(scope, src, T, value.positionals, .{}); - } - } - return self.failTok(scope, call.builtin_token, "TODO implement builtin call for '{}'", .{builtin_name}); -} - -fn astGenCall(self: *Module, scope: *Scope, call: *ast.Node.Call) InnerError!*zir.Inst { - const tree = scope.tree(); - const lhs = try self.astGenExpr(scope, call.lhs); - - const param_nodes = call.params(); - const args = try scope.cast(Scope.GenZIR).?.arena.alloc(*zir.Inst, param_nodes.len); - for (param_nodes) |param_node, i| { - args[i] = try self.astGenExpr(scope, param_node); - } - - const src = tree.token_locs[call.lhs.firstToken()].start; - return self.addZIRInst(scope, src, zir.Inst.Call, .{ - .func = lhs, - .args = args, - }, .{}); -} - -fn astGenUnreachable(self: *Module, scope: *Scope, unreach_node: *ast.Node.Unreachable) InnerError!*zir.Inst { - const tree = scope.tree(); - const src = tree.token_locs[unreach_node.token].start; - return self.addZIRInst(scope, src, zir.Inst.Unreachable, .{}, .{}); -} - -fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { - const simple_types = std.ComptimeStringMap(Value.Tag, .{ - .{ "u8", .u8_type }, - .{ "i8", .i8_type }, - .{ "isize", .isize_type }, - .{ "usize", .usize_type }, - .{ "c_short", .c_short_type }, - .{ "c_ushort", .c_ushort_type }, - .{ "c_int", .c_int_type }, - .{ "c_uint", .c_uint_type }, - .{ "c_long", .c_long_type }, - .{ "c_ulong", .c_ulong_type }, - .{ "c_longlong", .c_longlong_type }, - .{ "c_ulonglong", .c_ulonglong_type }, - .{ "c_longdouble", .c_longdouble_type }, - .{ "f16", .f16_type }, - .{ "f32", .f32_type }, - .{ "f64", .f64_type }, - .{ "f128", .f128_type }, - .{ "c_void", .c_void_type }, - .{ "bool", .bool_type }, - .{ "void", .void_type }, - .{ "type", .type_type }, - .{ "anyerror", .anyerror_type }, - .{ "comptime_int", .comptime_int_type }, - .{ "comptime_float", .comptime_float_type }, - .{ "noreturn", .noreturn_type }, - }); - if (simple_types.get(name)) |tag| { - return TypedValue{ - .ty = Type.initTag(.type), - .val = Value.initTag(tag), - }; - } - if (mem.eql(u8, name, "null")) { - return TypedValue{ - .ty = Type.initTag(.@"null"), - .val = Value.initTag(.null_value), - }; - } - if (mem.eql(u8, name, "undefined")) { - return TypedValue{ - .ty = Type.initTag(.@"undefined"), - .val = Value.initTag(.undef), - }; - } - if (mem.eql(u8, name, "true")) { - return TypedValue{ - .ty = Type.initTag(.bool), - .val = Value.initTag(.bool_true), - }; - } - if (mem.eql(u8, name, "false")) { - return TypedValue{ - .ty = Type.initTag(.bool), - .val = Value.initTag(.bool_false), - }; - } - return null; -} - fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void { try depender.dependencies.ensureCapacity(self.gpa, depender.dependencies.items().len + 1); try dependee.dependants.ensureCapacity(self.gpa, dependee.dependants.items().len + 1); @@ -2368,7 +1910,7 @@ fn newZIRInst( return inst; } -fn addZIRInstSpecial( +pub fn addZIRInstSpecial( self: *Module, scope: *Scope, src: usize, @@ -2383,7 +1925,7 @@ fn addZIRInstSpecial( return inst; } -fn addZIRInst( +pub fn addZIRInst( self: *Module, scope: *Scope, src: usize, @@ -2396,13 +1938,13 @@ fn addZIRInst( } /// TODO The existence of this function is a workaround for a bug in stage1. -fn addZIRInstConst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst { +pub fn addZIRInstConst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst { const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type; return self.addZIRInst(scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); } /// TODO The existence of this function is a workaround for a bug in stage1. -fn addZIRInstBlock(self: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block { +pub fn addZIRInstBlock(self: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block { const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type; return self.addZIRInstSpecial(scope, src, zir.Inst.Block, P{ .body = body }, .{}); } @@ -2637,7 +2179,7 @@ fn getNextAnonNameIndex(self: *Module) usize { return @atomicRmw(usize, &self.next_anon_name_index, .Add, 1, .Monotonic); } -fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*Decl { +pub fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*Decl { const namespace = scope.namespace(); const name_hash = namespace.fullyQualifiedNameHash(ident_name); return self.decl_table.get(name_hash); @@ -3646,13 +3188,13 @@ fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *I return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{}); } -fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: anytype) InnerError { +pub fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: anytype) InnerError { @setCold(true); const err_msg = try ErrorMsg.create(self.gpa, src, format, args); return self.failWithOwnedErrorMsg(scope, src, err_msg); } -fn failTok( +pub fn failTok( self: *Module, scope: *Scope, token_index: ast.TokenIndex, @@ -3664,7 +3206,7 @@ fn failTok( return self.fail(scope, src, format, args); } -fn failNode( +pub fn failNode( self: *Module, scope: *Scope, ast_node: *ast.Node, diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig new file mode 100644 index 0000000000000000000000000000000000000000..04541f2329dabf3823a885f66ceab69dd904df28 --- /dev/null +++ b/src-self-hosted/astgen.zig @@ -0,0 +1,476 @@ +const std = @import("std"); +const mem = std.mem; +const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; +const TypedValue = @import("TypedValue.zig"); +const assert = std.debug.assert; +const zir = @import("zir.zig"); +const Module = @import("Module.zig"); +const ast = std.zig.ast; +const trace = @import("tracy.zig").trace; +const Scope = Module.Scope; +const InnerError = Module.InnerError; + +pub fn expr(mod: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir.Inst { + switch (ast_node.id) { + .Identifier => return identifier(mod, scope, @fieldParentPtr(ast.Node.Identifier, "base", ast_node)), + .Asm => return assembly(mod, scope, @fieldParentPtr(ast.Node.Asm, "base", ast_node)), + .StringLiteral => return stringLiteral(mod, scope, @fieldParentPtr(ast.Node.StringLiteral, "base", ast_node)), + .IntegerLiteral => return integerLiteral(mod, scope, @fieldParentPtr(ast.Node.IntegerLiteral, "base", ast_node)), + .BuiltinCall => return builtinCall(mod, scope, @fieldParentPtr(ast.Node.BuiltinCall, "base", ast_node)), + .Call => return callExpr(mod, scope, @fieldParentPtr(ast.Node.Call, "base", ast_node)), + .Unreachable => return unreach(mod, scope, @fieldParentPtr(ast.Node.Unreachable, "base", ast_node)), + .ControlFlowExpression => return controlFlowExpr(mod, scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)), + .If => return ifExpr(mod, scope, @fieldParentPtr(ast.Node.If, "base", ast_node)), + .InfixOp => return infixOp(mod, scope, @fieldParentPtr(ast.Node.InfixOp, "base", ast_node)), + .BoolNot => return boolNot(mod, scope, @fieldParentPtr(ast.Node.BoolNot, "base", ast_node)), + .VarDecl => return varDecl(mod, scope, @fieldParentPtr(ast.Node.VarDecl, "base", ast_node)), + else => return mod.failNode(scope, ast_node, "TODO implement astgen.Expr for {}", .{@tagName(ast_node.id)}), + } +} + +pub fn blockExpr(mod: *Module, scope: *Scope, block_node: *ast.Node.Block) !void { + const tracy = trace(@src()); + defer tracy.end(); + + if (block_node.label) |label| { + return mod.failTok(scope, label, "TODO implement labeled blocks", .{}); + } + for (block_node.statements()) |statement| { + _ = try expr(mod, scope, statement); + } +} + +fn varDecl(mod: *Module, scope: *Scope, node: *ast.Node.VarDecl) InnerError!*zir.Inst { + return mod.failNode(scope, &node.base, "TODO implement var decls", .{}); +} + +fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.BoolNot) InnerError!*zir.Inst { + const operand = try expr(mod, scope, node.rhs); + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + return mod.addZIRInst(scope, src, zir.Inst.BoolNot, .{ .operand = operand }, .{}); +} + +fn infixOp(mod: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) InnerError!*zir.Inst { + switch (infix_node.op) { + .Assign => { + if (infix_node.lhs.id == .Identifier) { + const ident = @fieldParentPtr(ast.Node.Identifier, "base", infix_node.lhs); + const tree = scope.tree(); + const ident_name = tree.tokenSlice(ident.token); + if (std.mem.eql(u8, ident_name, "_")) { + return expr(mod, scope, infix_node.rhs); + } else { + return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); + } + } else { + return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); + } + }, + .Add => { + const lhs = try expr(mod, scope, infix_node.lhs); + const rhs = try expr(mod, scope, infix_node.rhs); + + const tree = scope.tree(); + const src = tree.token_locs[infix_node.op_token].start; + + return mod.addZIRInst(scope, src, zir.Inst.Add, .{ .lhs = lhs, .rhs = rhs }, .{}); + }, + .BangEqual, + .EqualEqual, + .GreaterThan, + .GreaterOrEqual, + .LessThan, + .LessOrEqual, + => { + const lhs = try expr(mod, scope, infix_node.lhs); + const rhs = try expr(mod, scope, infix_node.rhs); + + const tree = scope.tree(); + const src = tree.token_locs[infix_node.op_token].start; + + const op: std.math.CompareOperator = switch (infix_node.op) { + .BangEqual => .neq, + .EqualEqual => .eq, + .GreaterThan => .gt, + .GreaterOrEqual => .gte, + .LessThan => .lt, + .LessOrEqual => .lte, + else => unreachable, + }; + + return mod.addZIRInst(scope, src, zir.Inst.Cmp, .{ + .lhs = lhs, + .op = op, + .rhs = rhs, + }, .{}); + }, + else => |op| { + return mod.failNode(scope, &infix_node.base, "TODO implement infix operator {}", .{op}); + }, + } +} + +fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.Inst { + if (if_node.payload) |payload| { + return mod.failNode(scope, payload, "TODO implement astgen.IfExpr for optionals", .{}); + } + if (if_node.@"else") |else_node| { + if (else_node.payload) |payload| { + return mod.failNode(scope, payload, "TODO implement astgen.IfExpr for error unions", .{}); + } + } + var block_scope: Scope.GenZIR = .{ + .decl = scope.decl().?, + .arena = scope.arena(), + .instructions = .{}, + }; + defer block_scope.instructions.deinit(mod.gpa); + + const cond = try expr(mod, &block_scope.base, if_node.condition); + + const tree = scope.tree(); + const if_src = tree.token_locs[if_node.if_token].start; + const condbr = try mod.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{ + .condition = cond, + .true_body = undefined, // populated below + .false_body = undefined, // populated below + }, .{}); + + const block = try mod.addZIRInstBlock(scope, if_src, .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), + }); + var then_scope: Scope.GenZIR = .{ + .decl = block_scope.decl, + .arena = block_scope.arena, + .instructions = .{}, + }; + defer then_scope.instructions.deinit(mod.gpa); + + const then_result = try expr(mod, &then_scope.base, if_node.body); + if (!then_result.tag.isNoReturn()) { + const then_src = tree.token_locs[if_node.body.lastToken()].start; + _ = try mod.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ + .block = block, + .operand = then_result, + }, .{}); + } + condbr.positionals.true_body = .{ + .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items), + }; + + var else_scope: Scope.GenZIR = .{ + .decl = block_scope.decl, + .arena = block_scope.arena, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + + if (if_node.@"else") |else_node| { + const else_result = try expr(mod, &else_scope.base, else_node.body); + if (!else_result.tag.isNoReturn()) { + const else_src = tree.token_locs[else_node.body.lastToken()].start; + _ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ + .block = block, + .operand = else_result, + }, .{}); + } + } else { + // TODO Optimization opportunity: we can avoid an allocation and a memcpy here + // by directly allocating the body for this one instruction. + const else_src = tree.token_locs[if_node.lastToken()].start; + _ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.BreakVoid, .{ + .block = block, + }, .{}); + } + condbr.positionals.false_body = .{ + .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), + }; + + return &block.base; +} + +fn controlFlowExpr( + mod: *Module, + scope: *Scope, + cfe: *ast.Node.ControlFlowExpression, +) InnerError!*zir.Inst { + switch (cfe.kind) { + .Break => return mod.failNode(scope, &cfe.base, "TODO implement astgen.Expr for Break", .{}), + .Continue => return mod.failNode(scope, &cfe.base, "TODO implement astgen.Expr for Continue", .{}), + .Return => {}, + } + const tree = scope.tree(); + const src = tree.token_locs[cfe.ltoken].start; + if (cfe.rhs) |rhs_node| { + const operand = try expr(mod, scope, rhs_node); + return mod.addZIRInst(scope, src, zir.Inst.Return, .{ .operand = operand }, .{}); + } else { + return mod.addZIRInst(scope, src, zir.Inst.ReturnVoid, .{}, .{}); + } +} + +fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerError!*zir.Inst { + const tree = scope.tree(); + const ident_name = tree.tokenSlice(ident.token); + const src = tree.token_locs[ident.token].start; + if (mem.eql(u8, ident_name, "_")) { + return mod.failNode(scope, &ident.base, "TODO implement '_' identifier", .{}); + } + + if (getSimplePrimitiveValue(ident_name)) |typed_value| { + return mod.addZIRInstConst(scope, src, typed_value); + } + + if (ident_name.len >= 2) integer: { + const first_c = ident_name[0]; + if (first_c == 'i' or first_c == 'u') { + const is_signed = first_c == 'i'; + const bit_count = std.fmt.parseInt(u16, ident_name[1..], 10) catch |err| switch (err) { + error.Overflow => return mod.failNode( + scope, + &ident.base, + "primitive integer type '{}' exceeds maximum bit width of 65535", + .{ident_name}, + ), + error.InvalidCharacter => break :integer, + }; + const val = switch (bit_count) { + 8 => if (is_signed) Value.initTag(.i8_type) else Value.initTag(.u8_type), + 16 => if (is_signed) Value.initTag(.i16_type) else Value.initTag(.u16_type), + 32 => if (is_signed) Value.initTag(.i32_type) else Value.initTag(.u32_type), + 64 => if (is_signed) Value.initTag(.i64_type) else Value.initTag(.u64_type), + else => return mod.failNode(scope, &ident.base, "TODO implement arbitrary integer bitwidth types", .{}), + }; + return mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.type), + .val = val, + }); + } + } + + if (mod.lookupDeclName(scope, ident_name)) |decl| { + return try mod.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); + } + + // Function parameter + if (scope.decl()) |decl| { + if (tree.root_node.decls()[decl.src_index].cast(ast.Node.FnProto)) |fn_proto| { + for (fn_proto.params()) |param, i| { + const param_name = tree.tokenSlice(param.name_token.?); + if (mem.eql(u8, param_name, ident_name)) { + return try mod.addZIRInst(scope, src, zir.Inst.Arg, .{ .index = i }, .{}); + } + } + } + } + + return mod.failNode(scope, &ident.base, "TODO implement local variable identifier lookup", .{}); +} + +fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.StringLiteral) InnerError!*zir.Inst { + const tree = scope.tree(); + const unparsed_bytes = tree.tokenSlice(str_lit.token); + const arena = scope.arena(); + + var bad_index: usize = undefined; + const bytes = std.zig.parseStringLiteral(arena, unparsed_bytes, &bad_index) catch |err| switch (err) { + error.InvalidCharacter => { + const bad_byte = unparsed_bytes[bad_index]; + const src = tree.token_locs[str_lit.token].start; + return mod.fail(scope, src + bad_index, "invalid string literal character: '{c}'\n", .{bad_byte}); + }, + else => |e| return e, + }; + + const src = tree.token_locs[str_lit.token].start; + return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); +} + +fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.IntegerLiteral) InnerError!*zir.Inst { + const arena = scope.arena(); + const tree = scope.tree(); + const prefixed_bytes = tree.tokenSlice(int_lit.token); + const base = if (mem.startsWith(u8, prefixed_bytes, "0x")) + 16 + else if (mem.startsWith(u8, prefixed_bytes, "0o")) + 8 + else if (mem.startsWith(u8, prefixed_bytes, "0b")) + 2 + else + @as(u8, 10); + + const bytes = if (base == 10) + prefixed_bytes + else + prefixed_bytes[2..]; + + if (std.fmt.parseInt(u64, bytes, base)) |small_int| { + const int_payload = try arena.create(Value.Payload.Int_u64); + int_payload.* = .{ .int = small_int }; + const src = tree.token_locs[int_lit.token].start; + return mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.comptime_int), + .val = Value.initPayload(&int_payload.base), + }); + } else |err| { + return mod.failTok(scope, int_lit.token, "TODO implement int literals that don't fit in a u64", .{}); + } +} + +fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zir.Inst { + if (asm_node.outputs.len != 0) { + return mod.failNode(scope, &asm_node.base, "TODO implement asm with an output", .{}); + } + const arena = scope.arena(); + const tree = scope.tree(); + + const inputs = try arena.alloc(*zir.Inst, asm_node.inputs.len); + const args = try arena.alloc(*zir.Inst, asm_node.inputs.len); + + for (asm_node.inputs) |input, i| { + // TODO semantically analyze constraints + inputs[i] = try expr(mod, scope, input.constraint); + args[i] = try expr(mod, scope, input.expr); + } + + const src = tree.token_locs[asm_node.asm_token].start; + const return_type = try mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.void_type), + }); + const asm_inst = try mod.addZIRInst(scope, src, zir.Inst.Asm, .{ + .asm_source = try expr(mod, scope, asm_node.template), + .return_type = return_type, + }, .{ + .@"volatile" = asm_node.volatile_token != null, + //.clobbers = TODO handle clobbers + .inputs = inputs, + .args = args, + }); + return asm_inst; +} + +fn builtinCall(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + const tree = scope.tree(); + const builtin_name = tree.tokenSlice(call.builtin_token); + const src = tree.token_locs[call.builtin_token].start; + + inline for (std.meta.declarations(zir.Inst)) |inst| { + if (inst.data != .Type) continue; + const T = inst.data.Type; + if (!@hasDecl(T, "builtin_name")) continue; + if (std.mem.eql(u8, builtin_name, T.builtin_name)) { + var value: T = undefined; + const positionals = @typeInfo(std.meta.fieldInfo(T, "positionals").field_type).Struct; + if (positionals.fields.len == 0) { + return mod.addZIRInst(scope, src, T, value.positionals, value.kw_args); + } + const arg_count: ?usize = if (positionals.fields[0].field_type == []*zir.Inst) null else positionals.fields.len; + if (arg_count) |some| { + if (call.params_len != some) { + return mod.failTok( + scope, + call.builtin_token, + "expected {} parameter{}, found {}", + .{ some, if (some == 1) "" else "s", call.params_len }, + ); + } + const params = call.params(); + inline for (positionals.fields) |p, i| { + @field(value.positionals, p.name) = try expr(mod, scope, params[i]); + } + } else { + return mod.failTok(scope, call.builtin_token, "TODO var args builtin '{}'", .{builtin_name}); + } + + return mod.addZIRInst(scope, src, T, value.positionals, .{}); + } + } + return mod.failTok(scope, call.builtin_token, "TODO implement builtin call for '{}'", .{builtin_name}); +} + +fn callExpr(mod: *Module, scope: *Scope, node: *ast.Node.Call) InnerError!*zir.Inst { + const tree = scope.tree(); + const lhs = try expr(mod, scope, node.lhs); + + const param_nodes = node.params(); + const args = try scope.cast(Scope.GenZIR).?.arena.alloc(*zir.Inst, param_nodes.len); + for (param_nodes) |param_node, i| { + args[i] = try expr(mod, scope, param_node); + } + + const src = tree.token_locs[node.lhs.firstToken()].start; + return mod.addZIRInst(scope, src, zir.Inst.Call, .{ + .func = lhs, + .args = args, + }, .{}); +} + +fn unreach(mod: *Module, scope: *Scope, unreach_node: *ast.Node.Unreachable) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[unreach_node.token].start; + return mod.addZIRInst(scope, src, zir.Inst.Unreachable, .{}, .{}); +} + +fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { + const simple_types = std.ComptimeStringMap(Value.Tag, .{ + .{ "u8", .u8_type }, + .{ "i8", .i8_type }, + .{ "isize", .isize_type }, + .{ "usize", .usize_type }, + .{ "c_short", .c_short_type }, + .{ "c_ushort", .c_ushort_type }, + .{ "c_int", .c_int_type }, + .{ "c_uint", .c_uint_type }, + .{ "c_long", .c_long_type }, + .{ "c_ulong", .c_ulong_type }, + .{ "c_longlong", .c_longlong_type }, + .{ "c_ulonglong", .c_ulonglong_type }, + .{ "c_longdouble", .c_longdouble_type }, + .{ "f16", .f16_type }, + .{ "f32", .f32_type }, + .{ "f64", .f64_type }, + .{ "f128", .f128_type }, + .{ "c_void", .c_void_type }, + .{ "bool", .bool_type }, + .{ "void", .void_type }, + .{ "type", .type_type }, + .{ "anyerror", .anyerror_type }, + .{ "comptime_int", .comptime_int_type }, + .{ "comptime_float", .comptime_float_type }, + .{ "noreturn", .noreturn_type }, + }); + if (simple_types.get(name)) |tag| { + return TypedValue{ + .ty = Type.initTag(.type), + .val = Value.initTag(tag), + }; + } + if (mem.eql(u8, name, "null")) { + return TypedValue{ + .ty = Type.initTag(.@"null"), + .val = Value.initTag(.null_value), + }; + } + if (mem.eql(u8, name, "undefined")) { + return TypedValue{ + .ty = Type.initTag(.@"undefined"), + .val = Value.initTag(.undef), + }; + } + if (mem.eql(u8, name, "true")) { + return TypedValue{ + .ty = Type.initTag(.bool), + .val = Value.initTag(.bool_true), + }; + } + if (mem.eql(u8, name, "false")) { + return TypedValue{ + .ty = Type.initTag(.bool), + .val = Value.initTag(.bool_false), + }; + } + return null; +} -- 2.54.0 From f11909227312882f29dbfc484dc79ab622792787 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Jul 2020 18:15:59 -0700 Subject: [PATCH 282/295] stage2: breaking AST memory layout modifications ast.Node.Id => ast.Node.Tag, matching recent style conventions. Now multiple different AST node tags can map to the same AST node data structures. In this commit, simple prefix operators now all map top SimplePrefixOp. `ast.Node.castTag` is now preferred over `ast.Node.cast`. Upcoming: InfixOp flattened out. --- lib/std/zig/ast.zig | 313 +++++++++++++++++++------------- lib/std/zig/parse.zig | 149 ++++----------- lib/std/zig/render.zig | 93 ++++------ src-self-hosted/Module.zig | 44 ++++- src-self-hosted/astgen.zig | 144 +++++++++++++-- src-self-hosted/translate_c.zig | 27 +-- 6 files changed, 452 insertions(+), 318 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 7f066f541b2a8832e786fe6ac1bcc94d19b0b2fa..16173c423701a0f21bbf0ed40ed41e30afb1cd03 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -323,8 +323,8 @@ pub const Error = union(enum) { node: *Node, pub fn render(self: *const ExpectedCall, tokens: []const Token.Id, stream: anytype) !void { - return stream.print("expected " ++ @tagName(Node.Id.Call) ++ ", found {}", .{ - @tagName(self.node.id), + return stream.print("expected " ++ @tagName(Node.Tag.Call) ++ ", found {}", .{ + @tagName(self.node.tag), }); } }; @@ -333,8 +333,8 @@ pub const Error = union(enum) { node: *Node, pub fn render(self: *const ExpectedCallOrFnProto, tokens: []const Token.Id, stream: anytype) !void { - return stream.print("expected " ++ @tagName(Node.Id.Call) ++ " or " ++ - @tagName(Node.Id.FnProto) ++ ", found {}", .{@tagName(self.node.id)}); + return stream.print("expected " ++ @tagName(Node.Tag.Call) ++ " or " ++ + @tagName(Node.Tag.FnProto) ++ ", found {}", .{@tagName(self.node.tag)}); } }; @@ -396,9 +396,9 @@ pub const Error = union(enum) { }; pub const Node = struct { - id: Id, + tag: Tag, - pub const Id = enum { + pub const Tag = enum { // Top level Root, Use, @@ -484,49 +484,129 @@ pub const Node = struct { ContainerField, ErrorTag, FieldInitializer, + + pub fn Type(tag: Tag) type { + return switch (tag) { + .Root => Root, + .Use => Use, + .TestDecl => TestDecl, + .VarDecl => VarDecl, + .Defer => Defer, + .InfixOp => InfixOp, + + .AddressOf, + .Await, + .BitNot, + .BoolNot, + .OptionalType, + .Negation, + .NegationWrap, + .Resume, + .Try, + => SimplePrefixOp, + + .ArrayType => ArrayType, + .ArrayTypeSentinel => ArrayTypeSentinel, + .PtrType => PtrType, + .SliceType => SliceType, + .SuffixOp => SuffixOp, + .ArrayInitializer => ArrayInitializer, + .ArrayInitializerDot => ArrayInitializerDot, + .StructInitializer => StructInitializer, + .StructInitializerDot => StructInitializerDot, + .Call => Call, + .Switch => Switch, + .While => While, + .For => For, + .If => If, + .ControlFlowExpression => ControlFlowExpression, + .Suspend => Suspend, + .AnyType => AnyType, + .ErrorType => ErrorType, + .FnProto => FnProto, + .AnyFrameType => AnyFrameType, + .IntegerLiteral => IntegerLiteral, + .FloatLiteral => FloatLiteral, + .EnumLiteral => EnumLiteral, + .StringLiteral => StringLiteral, + .MultilineStringLiteral => MultilineStringLiteral, + .CharLiteral => CharLiteral, + .BoolLiteral => BoolLiteral, + .NullLiteral => NullLiteral, + .UndefinedLiteral => UndefinedLiteral, + .Unreachable => Unreachable, + .Identifier => Identifier, + .GroupedExpression => GroupedExpression, + .BuiltinCall => BuiltinCall, + .ErrorSetDecl => ErrorSetDecl, + .ContainerDecl => ContainerDecl, + .Asm => Asm, + .Comptime => Comptime, + .Nosuspend => Nosuspend, + .Block => Block, + .DocComment => DocComment, + .SwitchCase => SwitchCase, + .SwitchElse => SwitchElse, + .Else => Else, + .Payload => Payload, + .PointerPayload => PointerPayload, + .PointerIndexPayload => PointerIndexPayload, + .ContainerField => ContainerField, + .ErrorTag => ErrorTag, + .FieldInitializer => FieldInitializer, + }; + } }; + /// Prefer `castTag` to this. pub fn cast(base: *Node, comptime T: type) ?*T { - if (base.id == comptime typeToId(T)) { - return @fieldParentPtr(T, "base", base); + if (std.meta.fieldInfo(T, "base").default_value) |default_base| { + return base.castTag(default_base.tag); + } + inline for (@typeInfo(Tag).Enum.fields) |field| { + const tag = @intToEnum(Tag, field.value); + if (base.tag == tag) { + if (T == tag.Type()) { + return @fieldParentPtr(T, "base", base); + } + return null; + } + } + unreachable; + } + + pub fn castTag(base: *Node, comptime tag: Tag) ?*tag.Type() { + if (base.tag == tag) { + return @fieldParentPtr(tag.Type(), "base", base); } return null; } pub fn iterate(base: *Node, index: usize) ?*Node { - inline for (@typeInfo(Id).Enum.fields) |f| { - if (base.id == @field(Id, f.name)) { - const T = @field(Node, f.name); - return @fieldParentPtr(T, "base", base).iterate(index); + inline for (@typeInfo(Tag).Enum.fields) |field| { + const tag = @intToEnum(Tag, field.value); + if (base.tag == tag) { + return @fieldParentPtr(tag.Type(), "base", base).iterate(index); } } unreachable; } pub fn firstToken(base: *const Node) TokenIndex { - inline for (@typeInfo(Id).Enum.fields) |f| { - if (base.id == @field(Id, f.name)) { - const T = @field(Node, f.name); - return @fieldParentPtr(T, "base", base).firstToken(); + inline for (@typeInfo(Tag).Enum.fields) |field| { + const tag = @intToEnum(Tag, field.value); + if (base.tag == tag) { + return @fieldParentPtr(tag.Type(), "base", base).firstToken(); } } unreachable; } pub fn lastToken(base: *const Node) TokenIndex { - inline for (@typeInfo(Id).Enum.fields) |f| { - if (base.id == @field(Id, f.name)) { - const T = @field(Node, f.name); - return @fieldParentPtr(T, "base", base).lastToken(); - } - } - unreachable; - } - - pub fn typeToId(comptime T: type) Id { - inline for (@typeInfo(Id).Enum.fields) |f| { - if (T == @field(Node, f.name)) { - return @field(Id, f.name); + inline for (@typeInfo(Tag).Enum.fields) |field| { + const tag = @intToEnum(Tag, field.value); + if (base.tag == tag) { + return @fieldParentPtr(tag.Type(), "base", base).lastToken(); } } unreachable; @@ -535,7 +615,7 @@ pub const Node = struct { pub fn requireSemiColon(base: *const Node) bool { var n = base; while (true) { - switch (n.id) { + switch (n.tag) { .Root, .ContainerField, .Block, @@ -556,7 +636,7 @@ pub const Node = struct { continue; } - return while_node.body.id != .Block; + return while_node.body.tag != .Block; }, .For => { const for_node = @fieldParentPtr(For, "base", n); @@ -565,7 +645,7 @@ pub const Node = struct { continue; } - return for_node.body.id != .Block; + return for_node.body.tag != .Block; }, .If => { const if_node = @fieldParentPtr(If, "base", n); @@ -574,7 +654,7 @@ pub const Node = struct { continue; } - return if_node.body.id != .Block; + return if_node.body.tag != .Block; }, .Else => { const else_node = @fieldParentPtr(Else, "base", n); @@ -583,23 +663,23 @@ pub const Node = struct { }, .Defer => { const defer_node = @fieldParentPtr(Defer, "base", n); - return defer_node.expr.id != .Block; + return defer_node.expr.tag != .Block; }, .Comptime => { const comptime_node = @fieldParentPtr(Comptime, "base", n); - return comptime_node.expr.id != .Block; + return comptime_node.expr.tag != .Block; }, .Suspend => { const suspend_node = @fieldParentPtr(Suspend, "base", n); if (suspend_node.body) |body| { - return body.id != .Block; + return body.tag != .Block; } return true; }, .Nosuspend => { const nosuspend_node = @fieldParentPtr(Nosuspend, "base", n); - return nosuspend_node.expr.id != .Block; + return nosuspend_node.expr.tag != .Block; }, else => return true, } @@ -613,7 +693,7 @@ pub const Node = struct { std.debug.warn(" ", .{}); } } - std.debug.warn("{}\n", .{@tagName(self.id)}); + std.debug.warn("{}\n", .{@tagName(self.tag)}); var child_i: usize = 0; while (self.iterate(child_i)) |child| : (child_i += 1) { @@ -623,7 +703,7 @@ pub const Node = struct { /// The decls data follows this struct in memory as an array of Node pointers. pub const Root = struct { - base: Node = Node{ .id = .Root }, + base: Node = Node{ .tag = .Root }, eof_token: TokenIndex, decls_len: NodeIndex, @@ -678,7 +758,7 @@ pub const Node = struct { /// Trailed in memory by possibly many things, with each optional thing /// determined by a bit in `trailer_flags`. pub const VarDecl = struct { - base: Node = Node{ .id = .VarDecl }, + base: Node = Node{ .tag = .VarDecl }, trailer_flags: TrailerFlags, mut_token: TokenIndex, name_token: TokenIndex, @@ -779,7 +859,7 @@ pub const Node = struct { }; pub const Use = struct { - base: Node = Node{ .id = .Use }, + base: Node = Node{ .tag = .Use }, doc_comments: ?*DocComment, visib_token: ?TokenIndex, use_token: TokenIndex, @@ -806,7 +886,7 @@ pub const Node = struct { }; pub const ErrorSetDecl = struct { - base: Node = Node{ .id = .ErrorSetDecl }, + base: Node = Node{ .tag = .ErrorSetDecl }, error_token: TokenIndex, rbrace_token: TokenIndex, decls_len: NodeIndex, @@ -856,7 +936,7 @@ pub const Node = struct { /// The fields and decls Node pointers directly follow this struct in memory. pub const ContainerDecl = struct { - base: Node = Node{ .id = .ContainerDecl }, + base: Node = Node{ .tag = .ContainerDecl }, kind_token: TokenIndex, layout_token: ?TokenIndex, lbrace_token: TokenIndex, @@ -925,7 +1005,7 @@ pub const Node = struct { }; pub const ContainerField = struct { - base: Node = Node{ .id = .ContainerField }, + base: Node = Node{ .tag = .ContainerField }, doc_comments: ?*DocComment, comptime_token: ?TokenIndex, name_token: TokenIndex, @@ -976,7 +1056,7 @@ pub const Node = struct { }; pub const ErrorTag = struct { - base: Node = Node{ .id = .ErrorTag }, + base: Node = Node{ .tag = .ErrorTag }, doc_comments: ?*DocComment, name_token: TokenIndex, @@ -1001,7 +1081,7 @@ pub const Node = struct { }; pub const Identifier = struct { - base: Node = Node{ .id = .Identifier }, + base: Node = Node{ .tag = .Identifier }, token: TokenIndex, pub fn iterate(self: *const Identifier, index: usize) ?*Node { @@ -1020,7 +1100,7 @@ pub const Node = struct { /// The params are directly after the FnProto in memory. /// Next, each optional thing determined by a bit in `trailer_flags`. pub const FnProto = struct { - base: Node = Node{ .id = .FnProto }, + base: Node = Node{ .tag = .FnProto }, trailer_flags: TrailerFlags, fn_token: TokenIndex, params_len: NodeIndex, @@ -1230,7 +1310,7 @@ pub const Node = struct { }; pub const AnyFrameType = struct { - base: Node = Node{ .id = .AnyFrameType }, + base: Node = Node{ .tag = .AnyFrameType }, anyframe_token: TokenIndex, result: ?Result, @@ -1262,7 +1342,7 @@ pub const Node = struct { /// The statements of the block follow Block directly in memory. pub const Block = struct { - base: Node = Node{ .id = .Block }, + base: Node = Node{ .tag = .Block }, statements_len: NodeIndex, lbrace: TokenIndex, rbrace: TokenIndex, @@ -1316,7 +1396,7 @@ pub const Node = struct { }; pub const Defer = struct { - base: Node = Node{ .id = .Defer }, + base: Node = Node{ .tag = .Defer }, defer_token: TokenIndex, payload: ?*Node, expr: *Node, @@ -1340,7 +1420,7 @@ pub const Node = struct { }; pub const Comptime = struct { - base: Node = Node{ .id = .Comptime }, + base: Node = Node{ .tag = .Comptime }, doc_comments: ?*DocComment, comptime_token: TokenIndex, expr: *Node, @@ -1364,7 +1444,7 @@ pub const Node = struct { }; pub const Nosuspend = struct { - base: Node = Node{ .id = .Nosuspend }, + base: Node = Node{ .tag = .Nosuspend }, nosuspend_token: TokenIndex, expr: *Node, @@ -1387,7 +1467,7 @@ pub const Node = struct { }; pub const Payload = struct { - base: Node = Node{ .id = .Payload }, + base: Node = Node{ .tag = .Payload }, lpipe: TokenIndex, error_symbol: *Node, rpipe: TokenIndex, @@ -1411,7 +1491,7 @@ pub const Node = struct { }; pub const PointerPayload = struct { - base: Node = Node{ .id = .PointerPayload }, + base: Node = Node{ .tag = .PointerPayload }, lpipe: TokenIndex, ptr_token: ?TokenIndex, value_symbol: *Node, @@ -1436,7 +1516,7 @@ pub const Node = struct { }; pub const PointerIndexPayload = struct { - base: Node = Node{ .id = .PointerIndexPayload }, + base: Node = Node{ .tag = .PointerIndexPayload }, lpipe: TokenIndex, ptr_token: ?TokenIndex, value_symbol: *Node, @@ -1467,7 +1547,7 @@ pub const Node = struct { }; pub const Else = struct { - base: Node = Node{ .id = .Else }, + base: Node = Node{ .tag = .Else }, else_token: TokenIndex, payload: ?*Node, body: *Node, @@ -1498,7 +1578,7 @@ pub const Node = struct { /// The cases node pointers are found in memory after Switch. /// They must be SwitchCase or SwitchElse nodes. pub const Switch = struct { - base: Node = Node{ .id = .Switch }, + base: Node = Node{ .tag = .Switch }, switch_token: TokenIndex, rbrace: TokenIndex, cases_len: NodeIndex, @@ -1552,7 +1632,7 @@ pub const Node = struct { /// Items sub-nodes appear in memory directly following SwitchCase. pub const SwitchCase = struct { - base: Node = Node{ .id = .SwitchCase }, + base: Node = Node{ .tag = .SwitchCase }, arrow_token: TokenIndex, payload: ?*Node, expr: *Node, @@ -1610,7 +1690,7 @@ pub const Node = struct { }; pub const SwitchElse = struct { - base: Node = Node{ .id = .SwitchElse }, + base: Node = Node{ .tag = .SwitchElse }, token: TokenIndex, pub fn iterate(self: *const SwitchElse, index: usize) ?*Node { @@ -1627,7 +1707,7 @@ pub const Node = struct { }; pub const While = struct { - base: Node = Node{ .id = .While }, + base: Node = Node{ .tag = .While }, label: ?TokenIndex, inline_token: ?TokenIndex, while_token: TokenIndex, @@ -1686,7 +1766,7 @@ pub const Node = struct { }; pub const For = struct { - base: Node = Node{ .id = .For }, + base: Node = Node{ .tag = .For }, label: ?TokenIndex, inline_token: ?TokenIndex, for_token: TokenIndex, @@ -1737,7 +1817,7 @@ pub const Node = struct { }; pub const If = struct { - base: Node = Node{ .id = .If }, + base: Node = Node{ .tag = .If }, if_token: TokenIndex, condition: *Node, payload: ?*Node, @@ -1779,8 +1859,9 @@ pub const Node = struct { } }; + /// TODO split up and make every op its own AST Node tag pub const InfixOp = struct { - base: Node = Node{ .id = .InfixOp }, + base: Node = Node{ .tag = .InfixOp }, op_token: TokenIndex, lhs: *Node, op: Op, @@ -1906,41 +1987,29 @@ pub const Node = struct { } }; - pub const AddressOf = SimplePrefixOp(.AddressOf); - pub const Await = SimplePrefixOp(.Await); - pub const BitNot = SimplePrefixOp(.BitNot); - pub const BoolNot = SimplePrefixOp(.BoolNot); - pub const OptionalType = SimplePrefixOp(.OptionalType); - pub const Negation = SimplePrefixOp(.Negation); - pub const NegationWrap = SimplePrefixOp(.NegationWrap); - pub const Resume = SimplePrefixOp(.Resume); - pub const Try = SimplePrefixOp(.Try); + pub const SimplePrefixOp = struct { + base: Node, + op_token: TokenIndex, + rhs: *Node, - pub fn SimplePrefixOp(comptime tag: Id) type { - return struct { - base: Node = Node{ .id = tag }, - op_token: TokenIndex, - rhs: *Node, + const Self = @This(); - const Self = @This(); + pub fn iterate(self: *const Self, index: usize) ?*Node { + if (index == 0) return self.rhs; + return null; + } - pub fn iterate(self: *const Self, index: usize) ?*Node { - if (index == 0) return self.rhs; - return null; - } + pub fn firstToken(self: *const Self) TokenIndex { + return self.op_token; + } - pub fn firstToken(self: *const Self) TokenIndex { - return self.op_token; - } - - pub fn lastToken(self: *const Self) TokenIndex { - return self.rhs.lastToken(); - } - }; - } + pub fn lastToken(self: *const Self) TokenIndex { + return self.rhs.lastToken(); + } + }; pub const ArrayType = struct { - base: Node = Node{ .id = .ArrayType }, + base: Node = Node{ .tag = .ArrayType }, op_token: TokenIndex, rhs: *Node, len_expr: *Node, @@ -1967,7 +2036,7 @@ pub const Node = struct { }; pub const ArrayTypeSentinel = struct { - base: Node = Node{ .id = .ArrayTypeSentinel }, + base: Node = Node{ .tag = .ArrayTypeSentinel }, op_token: TokenIndex, rhs: *Node, len_expr: *Node, @@ -1998,7 +2067,7 @@ pub const Node = struct { }; pub const PtrType = struct { - base: Node = Node{ .id = .PtrType }, + base: Node = Node{ .tag = .PtrType }, op_token: TokenIndex, rhs: *Node, /// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents @@ -2034,7 +2103,7 @@ pub const Node = struct { }; pub const SliceType = struct { - base: Node = Node{ .id = .SliceType }, + base: Node = Node{ .tag = .SliceType }, op_token: TokenIndex, rhs: *Node, /// TODO Add a u8 flags field to Node where it would otherwise be padding, and each bit represents @@ -2070,7 +2139,7 @@ pub const Node = struct { }; pub const FieldInitializer = struct { - base: Node = Node{ .id = .FieldInitializer }, + base: Node = Node{ .tag = .FieldInitializer }, period_token: TokenIndex, name_token: TokenIndex, expr: *Node, @@ -2095,7 +2164,7 @@ pub const Node = struct { /// Elements occur directly in memory after ArrayInitializer. pub const ArrayInitializer = struct { - base: Node = Node{ .id = .ArrayInitializer }, + base: Node = Node{ .tag = .ArrayInitializer }, rtoken: TokenIndex, list_len: NodeIndex, lhs: *Node, @@ -2148,7 +2217,7 @@ pub const Node = struct { /// Elements occur directly in memory after ArrayInitializerDot. pub const ArrayInitializerDot = struct { - base: Node = Node{ .id = .ArrayInitializerDot }, + base: Node = Node{ .tag = .ArrayInitializerDot }, dot: TokenIndex, rtoken: TokenIndex, list_len: NodeIndex, @@ -2198,7 +2267,7 @@ pub const Node = struct { /// Elements occur directly in memory after StructInitializer. pub const StructInitializer = struct { - base: Node = Node{ .id = .StructInitializer }, + base: Node = Node{ .tag = .StructInitializer }, rtoken: TokenIndex, list_len: NodeIndex, lhs: *Node, @@ -2251,7 +2320,7 @@ pub const Node = struct { /// Elements occur directly in memory after StructInitializerDot. pub const StructInitializerDot = struct { - base: Node = Node{ .id = .StructInitializerDot }, + base: Node = Node{ .tag = .StructInitializerDot }, dot: TokenIndex, rtoken: TokenIndex, list_len: NodeIndex, @@ -2301,7 +2370,7 @@ pub const Node = struct { /// Parameter nodes directly follow Call in memory. pub const Call = struct { - base: Node = Node{ .id = .Call }, + base: Node = Node{ .tag = .Call }, lhs: *Node, rtoken: TokenIndex, params_len: NodeIndex, @@ -2355,7 +2424,7 @@ pub const Node = struct { }; pub const SuffixOp = struct { - base: Node = Node{ .id = .SuffixOp }, + base: Node = Node{ .tag = .SuffixOp }, op: Op, lhs: *Node, rtoken: TokenIndex, @@ -2415,7 +2484,7 @@ pub const Node = struct { }; pub const GroupedExpression = struct { - base: Node = Node{ .id = .GroupedExpression }, + base: Node = Node{ .tag = .GroupedExpression }, lparen: TokenIndex, expr: *Node, rparen: TokenIndex, @@ -2441,7 +2510,7 @@ pub const Node = struct { /// TODO break this into separate Break, Continue, Return AST Nodes to save memory. /// Could be further broken into LabeledBreak, LabeledContinue, and ReturnVoid to save even more. pub const ControlFlowExpression = struct { - base: Node = Node{ .id = .ControlFlowExpression }, + base: Node = Node{ .tag = .ControlFlowExpression }, ltoken: TokenIndex, kind: Kind, rhs: ?*Node, @@ -2496,7 +2565,7 @@ pub const Node = struct { }; pub const Suspend = struct { - base: Node = Node{ .id = .Suspend }, + base: Node = Node{ .tag = .Suspend }, suspend_token: TokenIndex, body: ?*Node, @@ -2525,7 +2594,7 @@ pub const Node = struct { }; pub const IntegerLiteral = struct { - base: Node = Node{ .id = .IntegerLiteral }, + base: Node = Node{ .tag = .IntegerLiteral }, token: TokenIndex, pub fn iterate(self: *const IntegerLiteral, index: usize) ?*Node { @@ -2542,7 +2611,7 @@ pub const Node = struct { }; pub const EnumLiteral = struct { - base: Node = Node{ .id = .EnumLiteral }, + base: Node = Node{ .tag = .EnumLiteral }, dot: TokenIndex, name: TokenIndex, @@ -2560,7 +2629,7 @@ pub const Node = struct { }; pub const FloatLiteral = struct { - base: Node = Node{ .id = .FloatLiteral }, + base: Node = Node{ .tag = .FloatLiteral }, token: TokenIndex, pub fn iterate(self: *const FloatLiteral, index: usize) ?*Node { @@ -2578,7 +2647,7 @@ pub const Node = struct { /// Parameters are in memory following BuiltinCall. pub const BuiltinCall = struct { - base: Node = Node{ .id = .BuiltinCall }, + base: Node = Node{ .tag = .BuiltinCall }, params_len: NodeIndex, builtin_token: TokenIndex, rparen_token: TokenIndex, @@ -2627,7 +2696,7 @@ pub const Node = struct { }; pub const StringLiteral = struct { - base: Node = Node{ .id = .StringLiteral }, + base: Node = Node{ .tag = .StringLiteral }, token: TokenIndex, pub fn iterate(self: *const StringLiteral, index: usize) ?*Node { @@ -2645,7 +2714,7 @@ pub const Node = struct { /// The string literal tokens appear directly in memory after MultilineStringLiteral. pub const MultilineStringLiteral = struct { - base: Node = Node{ .id = .MultilineStringLiteral }, + base: Node = Node{ .tag = .MultilineStringLiteral }, lines_len: TokenIndex, /// After this the caller must initialize the lines list. @@ -2687,7 +2756,7 @@ pub const Node = struct { }; pub const CharLiteral = struct { - base: Node = Node{ .id = .CharLiteral }, + base: Node = Node{ .tag = .CharLiteral }, token: TokenIndex, pub fn iterate(self: *const CharLiteral, index: usize) ?*Node { @@ -2704,7 +2773,7 @@ pub const Node = struct { }; pub const BoolLiteral = struct { - base: Node = Node{ .id = .BoolLiteral }, + base: Node = Node{ .tag = .BoolLiteral }, token: TokenIndex, pub fn iterate(self: *const BoolLiteral, index: usize) ?*Node { @@ -2721,7 +2790,7 @@ pub const Node = struct { }; pub const NullLiteral = struct { - base: Node = Node{ .id = .NullLiteral }, + base: Node = Node{ .tag = .NullLiteral }, token: TokenIndex, pub fn iterate(self: *const NullLiteral, index: usize) ?*Node { @@ -2738,7 +2807,7 @@ pub const Node = struct { }; pub const UndefinedLiteral = struct { - base: Node = Node{ .id = .UndefinedLiteral }, + base: Node = Node{ .tag = .UndefinedLiteral }, token: TokenIndex, pub fn iterate(self: *const UndefinedLiteral, index: usize) ?*Node { @@ -2755,7 +2824,7 @@ pub const Node = struct { }; pub const Asm = struct { - base: Node = Node{ .id = .Asm }, + base: Node = Node{ .tag = .Asm }, asm_token: TokenIndex, rparen: TokenIndex, volatile_token: ?TokenIndex, @@ -2875,7 +2944,7 @@ pub const Node = struct { }; pub const Unreachable = struct { - base: Node = Node{ .id = .Unreachable }, + base: Node = Node{ .tag = .Unreachable }, token: TokenIndex, pub fn iterate(self: *const Unreachable, index: usize) ?*Node { @@ -2892,7 +2961,7 @@ pub const Node = struct { }; pub const ErrorType = struct { - base: Node = Node{ .id = .ErrorType }, + base: Node = Node{ .tag = .ErrorType }, token: TokenIndex, pub fn iterate(self: *const ErrorType, index: usize) ?*Node { @@ -2909,7 +2978,7 @@ pub const Node = struct { }; pub const AnyType = struct { - base: Node = Node{ .id = .AnyType }, + base: Node = Node{ .tag = .AnyType }, token: TokenIndex, pub fn iterate(self: *const AnyType, index: usize) ?*Node { @@ -2929,7 +2998,7 @@ pub const Node = struct { /// TODO actually maybe remove entirely in favor of iterating backward from Node.firstToken() /// and forwards to find same-line doc comments. pub const DocComment = struct { - base: Node = Node{ .id = .DocComment }, + base: Node = Node{ .tag = .DocComment }, /// Points to the first doc comment token. API users are expected to iterate over the /// tokens array, looking for more doc comments, ignoring line comments, and stopping /// at the first other token. @@ -2951,7 +3020,7 @@ pub const Node = struct { }; pub const TestDecl = struct { - base: Node = Node{ .id = .TestDecl }, + base: Node = Node{ .tag = .TestDecl }, doc_comments: ?*DocComment, test_token: TokenIndex, name: *Node, @@ -2996,7 +3065,7 @@ pub const PtrInfo = struct { test "iterate" { var root = Node.Root{ - .base = Node{ .id = Node.Id.Root }, + .base = Node{ .tag = Node.Tag.Root }, .decls_len = 0, .eof_token = 0, }; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 9f2aea390a3baf0ebf21042057ec5a8f2ef71b63..6eb3742c0e74202b2ac7dc2b880abec968265f95 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1128,8 +1128,9 @@ const Parser = struct { const expr_node = try p.expectNode(parseExpr, .{ .ExpectedExpr = .{ .token = p.tok_i }, }); - const node = try p.arena.allocator.create(Node.Resume); + const node = try p.arena.allocator.create(Node.SimplePrefixOp); node.* = .{ + .base = .{ .tag = .Resume }, .op_token = token, .rhs = expr_node, }; @@ -1439,7 +1440,7 @@ const Parser = struct { }); while (try p.parseSuffixOp()) |node| { - switch (node.id) { + switch (node.tag) { .SuffixOp => node.cast(Node.SuffixOp).?.lhs = res, .InfixOp => node.cast(Node.InfixOp).?.lhs = res, else => unreachable, @@ -1470,7 +1471,7 @@ const Parser = struct { while (true) { if (try p.parseSuffixOp()) |node| { - switch (node.id) { + switch (node.tag) { .SuffixOp => node.cast(Node.SuffixOp).?.lhs = res, .InfixOp => node.cast(Node.InfixOp).?.lhs = res, else => unreachable, @@ -1660,7 +1661,7 @@ const Parser = struct { } if (try p.parseLoopTypeExpr()) |node| { - switch (node.id) { + switch (node.tag) { .For => node.cast(Node.For).?.label = label, .While => node.cast(Node.While).?.label = label, else => unreachable, @@ -2434,9 +2435,10 @@ const Parser = struct { } } - fn allocSimplePrefixOp(p: *Parser, comptime tag: Node.Id, token: TokenIndex) !?*Node { - const node = try p.arena.allocator.create(Node.SimplePrefixOp(tag)); + fn allocSimplePrefixOp(p: *Parser, comptime tag: Node.Tag, token: TokenIndex) !?*Node { + const node = try p.arena.allocator.create(Node.SimplePrefixOp); node.* = .{ + .base = .{ .tag = tag }, .op_token = token, .rhs = undefined, // set by caller }; @@ -2457,8 +2459,9 @@ const Parser = struct { /// / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* fn parsePrefixTypeOp(p: *Parser) !?*Node { if (p.eatToken(.QuestionMark)) |token| { - const node = try p.arena.allocator.create(Node.OptionalType); + const node = try p.arena.allocator.create(Node.SimplePrefixOp); node.* = .{ + .base = .{ .tag = .OptionalType }, .op_token = token, .rhs = undefined, // set by caller }; @@ -3072,7 +3075,6 @@ const Parser = struct { fn createLiteral(p: *Parser, comptime T: type, token: TokenIndex) !*Node { const result = try p.arena.allocator.create(T); result.* = T{ - .base = Node{ .id = Node.typeToId(T) }, .token = token, }; return &result.base; @@ -3148,8 +3150,9 @@ const Parser = struct { fn parseTry(p: *Parser) !?*Node { const token = p.eatToken(.Keyword_try) orelse return null; - const node = try p.arena.allocator.create(Node.Try); + const node = try p.arena.allocator.create(Node.SimplePrefixOp); node.* = .{ + .base = .{ .tag = .Try }, .op_token = token, .rhs = undefined, // set by caller }; @@ -3213,58 +3216,19 @@ const Parser = struct { if (try opParseFn(p)) |first_op| { var rightmost_op = first_op; while (true) { - switch (rightmost_op.id) { - .AddressOf => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.AddressOf).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .Await => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.Await).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .BitNot => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.BitNot).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .BoolNot => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.BoolNot).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .OptionalType => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.OptionalType).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .Negation => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.Negation).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .NegationWrap => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.NegationWrap).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .Resume => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.Resume).?.rhs = rhs; - rightmost_op = rhs; - } else break; - }, - .Try => { - if (try opParseFn(p)) |rhs| { - rightmost_op.cast(Node.Try).?.rhs = rhs; + switch (rightmost_op.tag) { + .AddressOf, + .Await, + .BitNot, + .BoolNot, + .OptionalType, + .Negation, + .NegationWrap, + .Resume, + .Try, + => { + if (try opParseFn(p)) |rhs| { + rightmost_op.cast(Node.SimplePrefixOp).?.rhs = rhs; rightmost_op = rhs; } else break; }, @@ -3310,57 +3274,18 @@ const Parser = struct { } // If any prefix op existed, a child node on the RHS is required - switch (rightmost_op.id) { - .AddressOf => { - const prefix_op = rightmost_op.cast(Node.AddressOf).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .Await => { - const prefix_op = rightmost_op.cast(Node.Await).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .BitNot => { - const prefix_op = rightmost_op.cast(Node.BitNot).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .BoolNot => { - const prefix_op = rightmost_op.cast(Node.BoolNot).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .OptionalType => { - const prefix_op = rightmost_op.cast(Node.OptionalType).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .Negation => { - const prefix_op = rightmost_op.cast(Node.Negation).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .NegationWrap => { - const prefix_op = rightmost_op.cast(Node.NegationWrap).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .Resume => { - const prefix_op = rightmost_op.cast(Node.Resume).?; - prefix_op.rhs = try p.expectNode(childParseFn, .{ - .InvalidToken = .{ .token = p.tok_i }, - }); - }, - .Try => { - const prefix_op = rightmost_op.cast(Node.Try).?; + switch (rightmost_op.tag) { + .AddressOf, + .Await, + .BitNot, + .BoolNot, + .OptionalType, + .Negation, + .NegationWrap, + .Resume, + .Try, + => { + const prefix_op = rightmost_op.cast(Node.SimplePrefixOp).?; prefix_op.rhs = try p.expectNode(childParseFn, .{ .InvalidToken = .{ .token = p.tok_i }, }); diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 4f9eb0e4cdf221e84f9c661ef6b14a91662fef28..67d73c92a6ce14d94e53e8eb39a7405014cce429 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -223,7 +223,7 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tre } fn renderContainerDecl(allocator: *mem.Allocator, stream: anytype, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node, space: Space) (@TypeOf(stream).Error || Error)!void { - switch (decl.id) { + switch (decl.tag) { .FnProto => { const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl); @@ -365,7 +365,7 @@ fn renderExpression( base: *ast.Node, space: Space, ) (@TypeOf(stream).Error || Error)!void { - switch (base.id) { + switch (base.tag) { .Identifier => { const identifier = @fieldParentPtr(ast.Node.Identifier, "base", base); return renderToken(tree, stream, identifier.token, indent, start_col, space); @@ -468,50 +468,25 @@ fn renderExpression( return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space); }, - .BitNot => { - const bit_not = @fieldParentPtr(ast.Node.BitNot, "base", base); - try renderToken(tree, stream, bit_not.op_token, indent, start_col, Space.None); - return renderExpression(allocator, stream, tree, indent, start_col, bit_not.rhs, space); - }, - .BoolNot => { - const bool_not = @fieldParentPtr(ast.Node.BoolNot, "base", base); - try renderToken(tree, stream, bool_not.op_token, indent, start_col, Space.None); - return renderExpression(allocator, stream, tree, indent, start_col, bool_not.rhs, space); - }, - .Negation => { - const negation = @fieldParentPtr(ast.Node.Negation, "base", base); - try renderToken(tree, stream, negation.op_token, indent, start_col, Space.None); - return renderExpression(allocator, stream, tree, indent, start_col, negation.rhs, space); - }, - .NegationWrap => { - const negation_wrap = @fieldParentPtr(ast.Node.NegationWrap, "base", base); - try renderToken(tree, stream, negation_wrap.op_token, indent, start_col, Space.None); - return renderExpression(allocator, stream, tree, indent, start_col, negation_wrap.rhs, space); - }, - .OptionalType => { - const opt_type = @fieldParentPtr(ast.Node.OptionalType, "base", base); - try renderToken(tree, stream, opt_type.op_token, indent, start_col, Space.None); - return renderExpression(allocator, stream, tree, indent, start_col, opt_type.rhs, space); - }, - .AddressOf => { - const addr_of = @fieldParentPtr(ast.Node.AddressOf, "base", base); - try renderToken(tree, stream, addr_of.op_token, indent, start_col, Space.None); - return renderExpression(allocator, stream, tree, indent, start_col, addr_of.rhs, space); - }, - .Try => { - const try_node = @fieldParentPtr(ast.Node.Try, "base", base); - try renderToken(tree, stream, try_node.op_token, indent, start_col, Space.Space); - return renderExpression(allocator, stream, tree, indent, start_col, try_node.rhs, space); - }, - .Resume => { - const resume_node = @fieldParentPtr(ast.Node.Resume, "base", base); - try renderToken(tree, stream, resume_node.op_token, indent, start_col, Space.Space); - return renderExpression(allocator, stream, tree, indent, start_col, resume_node.rhs, space); - }, - .Await => { - const await_node = @fieldParentPtr(ast.Node.Await, "base", base); - try renderToken(tree, stream, await_node.op_token, indent, start_col, Space.Space); - return renderExpression(allocator, stream, tree, indent, start_col, await_node.rhs, space); + .BitNot, + .BoolNot, + .Negation, + .NegationWrap, + .OptionalType, + .AddressOf, + => { + const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); + try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.None); + return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space); + }, + + .Try, + .Resume, + .Await, + => { + const casted_node = @fieldParentPtr(ast.Node.SimplePrefixOp, "base", base); + try renderToken(tree, stream, casted_node.op_token, indent, start_col, Space.Space); + return renderExpression(allocator, stream, tree, indent, start_col, casted_node.rhs, space); }, .ArrayType => { @@ -659,7 +634,7 @@ fn renderExpression( .ArrayInitializer, .ArrayInitializerDot => { var rtoken: ast.TokenIndex = undefined; var exprs: []*ast.Node = undefined; - const lhs: union(enum) { dot: ast.TokenIndex, node: *ast.Node } = switch (base.id) { + const lhs: union(enum) { dot: ast.TokenIndex, node: *ast.Node } = switch (base.tag) { .ArrayInitializerDot => blk: { const casted = @fieldParentPtr(ast.Node.ArrayInitializerDot, "base", base); rtoken = casted.rtoken; @@ -793,14 +768,14 @@ fn renderExpression( } try renderExtraNewline(tree, stream, start_col, next_expr); - if (next_expr.id != .MultilineStringLiteral) { + if (next_expr.tag != .MultilineStringLiteral) { try stream.writeByteNTimes(' ', new_indent); } } else { try renderExpression(allocator, stream, tree, new_indent, start_col, expr, Space.Comma); // , } } - if (exprs[exprs.len - 1].id != .MultilineStringLiteral) { + if (exprs[exprs.len - 1].tag != .MultilineStringLiteral) { try stream.writeByteNTimes(' ', indent); } return renderToken(tree, stream, rtoken, indent, start_col, space); @@ -823,7 +798,7 @@ fn renderExpression( .StructInitializer, .StructInitializerDot => { var rtoken: ast.TokenIndex = undefined; var field_inits: []*ast.Node = undefined; - const lhs: union(enum) { dot: ast.TokenIndex, node: *ast.Node } = switch (base.id) { + const lhs: union(enum) { dot: ast.TokenIndex, node: *ast.Node } = switch (base.tag) { .StructInitializerDot => blk: { const casted = @fieldParentPtr(ast.Node.StructInitializerDot, "base", base); rtoken = casted.rtoken; @@ -877,7 +852,7 @@ fn renderExpression( if (field_inits.len == 1) blk: { const field_init = field_inits[0].cast(ast.Node.FieldInitializer).?; - switch (field_init.expr.id) { + switch (field_init.expr.tag) { .StructInitializer, .StructInitializerDot, => break :blk, @@ -974,7 +949,7 @@ fn renderExpression( const params = call.params(); for (params) |param_node, i| { - const param_node_new_indent = if (param_node.id == .MultilineStringLiteral) blk: { + const param_node_new_indent = if (param_node.tag == .MultilineStringLiteral) blk: { break :blk indent; } else blk: { try stream.writeByteNTimes(' ', new_indent); @@ -1284,7 +1259,7 @@ fn renderExpression( // declarations inside are fields const src_has_only_fields = blk: { for (fields_and_decls) |decl| { - if (decl.id != .ContainerField) break :blk false; + if (decl.tag != .ContainerField) break :blk false; } break :blk true; }; @@ -1831,7 +1806,7 @@ fn renderExpression( const rparen = tree.nextToken(for_node.array_expr.lastToken()); - const body_is_block = for_node.body.id == .Block; + const body_is_block = for_node.body.tag == .Block; const src_one_line_to_body = !body_is_block and tree.tokensOnSameLine(rparen, for_node.body.firstToken()); const body_on_same_line = body_is_block or src_one_line_to_body; @@ -1874,7 +1849,7 @@ fn renderExpression( try renderExpression(allocator, stream, tree, indent, start_col, if_node.condition, Space.None); // condition - const body_is_if_block = if_node.body.id == .If; + const body_is_if_block = if_node.body.tag == .If; const body_is_block = nodeIsBlock(if_node.body); if (body_is_if_block) { @@ -1978,7 +1953,7 @@ fn renderExpression( const indent_once = indent + indent_delta; - if (asm_node.template.id == .MultilineStringLiteral) { + if (asm_node.template.tag == .MultilineStringLiteral) { // After rendering a multiline string literal the cursor is // already offset by indent try stream.writeByteNTimes(' ', indent_delta); @@ -2245,7 +2220,7 @@ fn renderVarDecl( } if (var_decl.getTrailer("init_node")) |init_node| { - const s = if (init_node.id == .MultilineStringLiteral) Space.None else Space.Space; + const s = if (init_node.tag == .MultilineStringLiteral) Space.None else Space.Space; try renderToken(tree, stream, var_decl.getTrailer("eq_token").?, indent, start_col, s); // = try renderExpression(allocator, stream, tree, indent, start_col, init_node, Space.None); } @@ -2287,7 +2262,7 @@ fn renderStatement( start_col: *usize, base: *ast.Node, ) (@TypeOf(stream).Error || Error)!void { - switch (base.id) { + switch (base.tag) { .VarDecl => { const var_decl = @fieldParentPtr(ast.Node.VarDecl, "base", base); try renderVarDecl(allocator, stream, tree, indent, start_col, var_decl); @@ -2566,7 +2541,7 @@ fn renderDocCommentsToken( } fn nodeIsBlock(base: *const ast.Node) bool { - return switch (base.id) { + return switch (base.tag) { .Block, .If, .For, diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 902efbe33975dceff06dbd6d0d8bcfdb389a787e..050588f3dffc207432317d6f64ecfff99181e8f2 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -212,6 +212,7 @@ pub const Decl = struct { }, .block => unreachable, .gen_zir => unreachable, + .local_var => unreachable, .decl => unreachable, } } @@ -307,6 +308,7 @@ pub const Scope = struct { .block => return self.cast(Block).?.arena, .decl => return &self.cast(DeclAnalysis).?.arena.allocator, .gen_zir => return self.cast(GenZIR).?.arena, + .local_var => return self.cast(LocalVar).?.gen_zir.arena, .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, .file => unreachable, } @@ -318,6 +320,7 @@ pub const Scope = struct { return switch (self.tag) { .block => self.cast(Block).?.decl, .gen_zir => self.cast(GenZIR).?.decl, + .local_var => return self.cast(LocalVar).?.gen_zir.decl, .decl => self.cast(DeclAnalysis).?.decl, .zir_module => null, .file => null, @@ -330,6 +333,7 @@ pub const Scope = struct { switch (self.tag) { .block => return self.cast(Block).?.decl.scope, .gen_zir => return self.cast(GenZIR).?.decl.scope, + .local_var => return self.cast(LocalVar).?.gen_zir.decl.scope, .decl => return self.cast(DeclAnalysis).?.decl.scope, .zir_module, .file => return self, } @@ -342,6 +346,7 @@ pub const Scope = struct { switch (self.tag) { .block => unreachable, .gen_zir => unreachable, + .local_var => unreachable, .decl => unreachable, .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name), .file => return self.cast(File).?.fullyQualifiedNameHash(name), @@ -356,9 +361,22 @@ pub const Scope = struct { .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(File).?.contents.tree, .block => return self.cast(Block).?.decl.scope.cast(File).?.contents.tree, .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(File).?.contents.tree, + .local_var => return self.cast(LocalVar).?.gen_zir.decl.scope.cast(File).?.contents.tree, } } + /// Asserts the scope is a child of a `GenZIR` and returns it. + pub fn getGenZIR(self: *Scope) *GenZIR { + return switch (self.tag) { + .block => unreachable, + .gen_zir => self.cast(GenZIR).?, + .local_var => return self.cast(LocalVar).?.gen_zir, + .decl => unreachable, + .zir_module => unreachable, + .file => unreachable, + }; + } + pub fn dumpInst(self: *Scope, inst: *Inst) void { const zir_module = self.namespace(); const loc = std.zig.findLineColumn(zir_module.source.bytes, inst.src); @@ -379,6 +397,7 @@ pub const Scope = struct { .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path, .block => unreachable, .gen_zir => unreachable, + .local_var => unreachable, .decl => unreachable, } } @@ -389,6 +408,7 @@ pub const Scope = struct { .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(gpa), .block => unreachable, .gen_zir => unreachable, + .local_var => unreachable, .decl => unreachable, } } @@ -398,6 +418,7 @@ pub const Scope = struct { .file => return @fieldParentPtr(File, "base", base).getSource(module), .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module), .gen_zir => unreachable, + .local_var => unreachable, .block => unreachable, .decl => unreachable, } @@ -410,6 +431,7 @@ pub const Scope = struct { .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child), .block => unreachable, .gen_zir => unreachable, + .local_var => unreachable, .decl => unreachable, } } @@ -429,6 +451,7 @@ pub const Scope = struct { }, .block => unreachable, .gen_zir => unreachable, + .local_var => unreachable, .decl => unreachable, } } @@ -449,6 +472,7 @@ pub const Scope = struct { block, decl, gen_zir, + local_var, }; pub const File = struct { @@ -680,6 +704,18 @@ pub const Scope = struct { arena: *Allocator, instructions: std.ArrayListUnmanaged(*zir.Inst) = .{}, }; + + /// This structure lives as long as the AST generation of the Block + /// node that contains the variable. This struct's parents can be + /// other `LocalVar` and finally a `GenZIR` at the top. + pub const LocalVar = struct { + pub const base_tag: Tag = .local_var; + base: Scope = Scope{ .tag = base_tag }, + gen_zir: *GenZIR, + parent: *Scope, + name: []const u8, + inst: *zir.Inst, + }; }; pub const AllErrors = struct { @@ -1114,7 +1150,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const file_scope = decl.scope.cast(Scope.File).?; const tree = try self.getAstTree(file_scope); const ast_node = tree.root_node.decls()[decl.src_index]; - switch (ast_node.id) { + switch (ast_node.tag) { .FnProto => { const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", ast_node); @@ -3247,6 +3283,12 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err gen_zir.decl.generation = self.generation; self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); }, + .local_var => { + const gen_zir = scope.cast(Scope.LocalVar).?.gen_zir; + gen_zir.decl.analysis = .sema_failure; + gen_zir.decl.generation = self.generation; + self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); + }, .zir_module => { const zir_module = scope.cast(Scope.ZIRModule).?; zir_module.status = .loaded_sema_failure; diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 04541f2329dabf3823a885f66ceab69dd904df28..f75097575d50c72c2b95bd427552e48faee54310 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -11,8 +11,11 @@ const trace = @import("tracy.zig").trace; const Scope = Module.Scope; const InnerError = Module.InnerError; +/// Turn Zig AST into untyped ZIR istructions. pub fn expr(mod: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir.Inst { - switch (ast_node.id) { + switch (ast_node.tag) { + .VarDecl => unreachable, // Handled in `blockExpr`. + .Identifier => return identifier(mod, scope, @fieldParentPtr(ast.Node.Identifier, "base", ast_node)), .Asm => return assembly(mod, scope, @fieldParentPtr(ast.Node.Asm, "base", ast_node)), .StringLiteral => return stringLiteral(mod, scope, @fieldParentPtr(ast.Node.StringLiteral, "base", ast_node)), @@ -23,29 +26,72 @@ pub fn expr(mod: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir.In .ControlFlowExpression => return controlFlowExpr(mod, scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)), .If => return ifExpr(mod, scope, @fieldParentPtr(ast.Node.If, "base", ast_node)), .InfixOp => return infixOp(mod, scope, @fieldParentPtr(ast.Node.InfixOp, "base", ast_node)), - .BoolNot => return boolNot(mod, scope, @fieldParentPtr(ast.Node.BoolNot, "base", ast_node)), - .VarDecl => return varDecl(mod, scope, @fieldParentPtr(ast.Node.VarDecl, "base", ast_node)), - else => return mod.failNode(scope, ast_node, "TODO implement astgen.Expr for {}", .{@tagName(ast_node.id)}), + .BoolNot => return boolNot(mod, scope, @fieldParentPtr(ast.Node.SimplePrefixOp, "base", ast_node)), + else => return mod.failNode(scope, ast_node, "TODO implement astgen.Expr for {}", .{@tagName(ast_node.tag)}), } } -pub fn blockExpr(mod: *Module, scope: *Scope, block_node: *ast.Node.Block) !void { +pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block) !void { const tracy = trace(@src()); defer tracy.end(); if (block_node.label) |label| { - return mod.failTok(scope, label, "TODO implement labeled blocks", .{}); + return mod.failTok(parent_scope, label, "TODO implement labeled blocks", .{}); } + + var block_arena = std.heap.ArenaAllocator.init(mod.gpa); + defer block_arena.deinit(); + + var scope = parent_scope; for (block_node.statements()) |statement| { - _ = try expr(mod, scope, statement); + switch (statement.tag) { + .VarDecl => { + const sub_scope = try block_arena.allocator.create(Scope.LocalVar); + const var_decl_node = @fieldParentPtr(ast.Node.VarDecl, "base", statement); + sub_scope.* = try varDecl(mod, scope, var_decl_node); + scope = &sub_scope.base; + }, + else => _ = try expr(mod, scope, statement), + } } } -fn varDecl(mod: *Module, scope: *Scope, node: *ast.Node.VarDecl) InnerError!*zir.Inst { - return mod.failNode(scope, &node.base, "TODO implement var decls", .{}); +fn varDecl(mod: *Module, scope: *Scope, node: *ast.Node.VarDecl) InnerError!Scope.LocalVar { + if (node.getTrailer("comptime_token")) |comptime_token| { + return mod.failTok(scope, comptime_token, "TODO implement comptime locals", .{}); + } + if (node.getTrailer("align_node")) |align_node| { + return mod.failNode(scope, align_node, "TODO implement alignment on locals", .{}); + } + if (node.getTrailer("type_node")) |type_node| { + return mod.failNode(scope, type_node, "TODO implement typed locals", .{}); + } + const tree = scope.tree(); + switch (tree.token_ids[node.mut_token]) { + .Keyword_const => {}, + .Keyword_var => { + return mod.failTok(scope, node.mut_token, "TODO implement mutable locals", .{}); + }, + else => unreachable, + } + // Depending on the type of AST the initialization expression is, we may need an lvalue + // or an rvalue as a result location. If it is an rvalue, we can use the instruction as + // the variable, no memory location needed. + const init_node = node.getTrailer("init_node").?; + if (nodeNeedsMemoryLocation(init_node)) { + return mod.failNode(scope, init_node, "TODO implement result locations", .{}); + } + const init_inst = try expr(mod, scope, init_node); + const ident_name = tree.tokenSlice(node.name_token); // TODO support @"aoeu" identifiers + return Scope.LocalVar{ + .parent = scope, + .gen_zir = scope.getGenZIR(), + .name = ident_name, + .inst = init_inst, + }; } -fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.BoolNot) InnerError!*zir.Inst { +fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { const operand = try expr(mod, scope, node.rhs); const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; @@ -55,7 +101,7 @@ fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.BoolNot) InnerError!*zir fn infixOp(mod: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) InnerError!*zir.Inst { switch (infix_node.op) { .Assign => { - if (infix_node.lhs.id == .Identifier) { + if (infix_node.lhs.tag == .Identifier) { const ident = @fieldParentPtr(ast.Node.Identifier, "base", infix_node.lhs); const tree = scope.tree(); const ident_name = tree.tokenSlice(ident.token); @@ -474,3 +520,79 @@ fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { } return null; } + +fn nodeNeedsMemoryLocation(node: *ast.Node) bool { + return switch (node.tag) { + .Root, + .Use, + .TestDecl, + .DocComment, + .SwitchCase, + .SwitchElse, + .Else, + .Payload, + .PointerPayload, + .PointerIndexPayload, + .ContainerField, + .ErrorTag, + .FieldInitializer, + => unreachable, + + .ControlFlowExpression, + .BitNot, + .BoolNot, + .VarDecl, + .Defer, + .AddressOf, + .OptionalType, + .Negation, + .NegationWrap, + .Resume, + .ArrayType, + .ArrayTypeSentinel, + .PtrType, + .SliceType, + .Suspend, + .AnyType, + .ErrorType, + .FnProto, + .AnyFrameType, + .IntegerLiteral, + .FloatLiteral, + .EnumLiteral, + .StringLiteral, + .MultilineStringLiteral, + .CharLiteral, + .BoolLiteral, + .NullLiteral, + .UndefinedLiteral, + .Unreachable, + .Identifier, + .ErrorSetDecl, + .ContainerDecl, + .Asm, + => false, + + .ArrayInitializer, + .ArrayInitializerDot, + .StructInitializer, + .StructInitializerDot, + => true, + + .GroupedExpression => nodeNeedsMemoryLocation(node.cast(ast.Node.GroupedExpression).?.expr), + + .InfixOp => @panic("TODO nodeNeedsMemoryLocation for InfixOp"), + .Await => @panic("TODO nodeNeedsMemoryLocation for Await"), + .Try => @panic("TODO nodeNeedsMemoryLocation for Try"), + .If => @panic("TODO nodeNeedsMemoryLocation for If"), + .SuffixOp => @panic("TODO nodeNeedsMemoryLocation for SuffixOp"), + .Call => @panic("TODO nodeNeedsMemoryLocation for Call"), + .Switch => @panic("TODO nodeNeedsMemoryLocation for Switch"), + .While => @panic("TODO nodeNeedsMemoryLocation for While"), + .For => @panic("TODO nodeNeedsMemoryLocation for For"), + .BuiltinCall => @panic("TODO nodeNeedsMemoryLocation for BuiltinCall"), + .Comptime => @panic("TODO nodeNeedsMemoryLocation for Comptime"), + .Nosuspend => @panic("TODO nodeNeedsMemoryLocation for Nosuspend"), + .Block => @panic("TODO nodeNeedsMemoryLocation for Block"), + }; +} diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index b9ab28cc1757c4d25e9312eeb75e56244679d02c..b04528a3d39b4ac8c19959d1fda6022331329324 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -1219,7 +1219,7 @@ fn transStmt( .StringLiteralClass => return transStringLiteral(rp, scope, @ptrCast(*const ZigClangStringLiteral, stmt), result_used), .ParenExprClass => { const expr = try transExpr(rp, scope, ZigClangParenExpr_getSubExpr(@ptrCast(*const ZigClangParenExpr, stmt)), .used, lrvalue); - if (expr.id == .GroupedExpression) return maybeSuppressResult(rp, scope, result_used, expr); + if (expr.tag == .GroupedExpression) return maybeSuppressResult(rp, scope, result_used, expr); const node = try rp.c.arena.create(ast.Node.GroupedExpression); node.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), @@ -1264,7 +1264,7 @@ fn transStmt( .OpaqueValueExprClass => { const source_expr = ZigClangOpaqueValueExpr_getSourceExpr(@ptrCast(*const ZigClangOpaqueValueExpr, stmt)).?; const expr = try transExpr(rp, scope, source_expr, .used, lrvalue); - if (expr.id == .GroupedExpression) return maybeSuppressResult(rp, scope, result_used, expr); + if (expr.tag == .GroupedExpression) return maybeSuppressResult(rp, scope, result_used, expr); const node = try rp.c.arena.create(ast.Node.GroupedExpression); node.* = .{ .lparen = try appendToken(rp.c, .LParen, "("), @@ -1693,7 +1693,7 @@ fn transBoolExpr( var res = try transExpr(rp, scope, expr, used, lrvalue); if (isBoolRes(res)) { - if (!grouped and res.id == .GroupedExpression) { + if (!grouped and res.tag == .GroupedExpression) { const group = @fieldParentPtr(ast.Node.GroupedExpression, "base", res); res = group.expr; // get zig fmt to work properly @@ -1736,7 +1736,7 @@ fn exprIsStringLiteral(expr: *const ZigClangExpr) bool { } fn isBoolRes(res: *ast.Node) bool { - switch (res.id) { + switch (res.tag) { .InfixOp => switch (@fieldParentPtr(ast.Node.InfixOp, "base", res).op) { .BoolOr, .BoolAnd, @@ -4107,12 +4107,13 @@ fn transCreateNodeFieldAccess(c: *Context, container: *ast.Node, field_name: []c fn transCreateNodeSimplePrefixOp( c: *Context, - comptime tag: ast.Node.Id, + comptime tag: ast.Node.Tag, op_tok_id: std.zig.Token.Id, bytes: []const u8, -) !*ast.Node.SimplePrefixOp(tag) { - const node = try c.arena.create(ast.Node.SimplePrefixOp(tag)); +) !*ast.Node.SimplePrefixOp { + const node = try c.arena.create(ast.Node.SimplePrefixOp); node.* = .{ + .base = .{ .tag = tag }, .op_token = try appendToken(c, op_tok_id, bytes), .rhs = undefined, // translate and set afterward }; @@ -5338,10 +5339,10 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, .{@tagName(last.id)}, ); _ = try appendToken(c, .Semicolon, ";"); - const type_of_arg = if (expr.id != .Block) expr else blk: { + const type_of_arg = if (expr.tag != .Block) expr else blk: { const blk = @fieldParentPtr(ast.Node.Block, "base", expr); const blk_last = blk.statements()[blk.statements_len - 1]; - std.debug.assert(blk_last.id == .ControlFlowExpression); + std.debug.assert(blk_last.tag == .ControlFlowExpression); const br = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", blk_last); break :blk br.rhs.?; }; @@ -5788,7 +5789,7 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, fn macroBoolToInt(c: *Context, node: *ast.Node) !*ast.Node { if (!isBoolRes(node)) { - if (node.id != .InfixOp) return node; + if (node.tag != .InfixOp) return node; const group_node = try c.arena.create(ast.Node.GroupedExpression); group_node.* = .{ @@ -5807,7 +5808,7 @@ fn macroBoolToInt(c: *Context, node: *ast.Node) !*ast.Node { fn macroIntToBool(c: *Context, node: *ast.Node) !*ast.Node { if (isBoolRes(node)) { - if (node.id != .InfixOp) return node; + if (node.tag != .InfixOp) return node; const group_node = try c.arena.create(ast.Node.GroupedExpression); group_node.* = .{ @@ -6105,7 +6106,7 @@ fn tokenSlice(c: *Context, token: ast.TokenIndex) []u8 { } fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { - switch (node.id) { + switch (node.tag) { .ContainerDecl, .AddressOf, .Await, @@ -6182,7 +6183,7 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { fn getFnProto(c: *Context, ref: *ast.Node) ?*ast.Node.FnProto { const init = if (ref.cast(ast.Node.VarDecl)) |v| v.getTrailer("init_node").? else return null; if (getContainerTypeOf(c, init)) |ty_node| { - if (ty_node.cast(ast.Node.OptionalType)) |prefix| { + if (ty_node.castTag(.OptionalType)) |prefix| { if (prefix.rhs.cast(ast.Node.FnProto)) |fn_proto| { return fn_proto; } -- 2.54.0 From 82562b205f9d99c27c4d5224311734e141bf2fda Mon Sep 17 00:00:00 2001 From: haze Date: Wed, 15 Jul 2020 22:37:04 -0400 Subject: [PATCH 283/295] On darwin, only add the self exe to the cache hash for compiler id (#5880) Now that Big Sur does not have system libraries on the filesystem, zig can no longer read them and add them to the cache hash for the compiler id. This changes it so that only the first library path returned by os_self_exe_shared_libs is added to the cache hash under Darwin. I looked into methods on getting the system version to keep parity with older versions, but @fengb reported that this works on Catalina (a version behind Big Sur) Signed-off-by: Haze Booth --- src/compiler.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/compiler.cpp b/src/compiler.cpp index 8294fc787189a88d17b77816813e3372ea952683..6c477a1506fa2939cc94bc6edecb41bec5b3a75b 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -38,11 +38,19 @@ Error get_compiler_id(Buf **result) { ZigList lib_paths = {}; if ((err = os_self_exe_shared_libs(lib_paths))) return err; + #if defined(ZIG_OS_DARWIN) + // only add the self exe path on mac os + Buf *lib_path = lib_paths.at(0); + if ((err = cache_add_file(ch, lib_path))) + return err; + #else for (size_t i = 0; i < lib_paths.length; i += 1) { Buf *lib_path = lib_paths.at(i); if ((err = cache_add_file(ch, lib_path))) return err; } + #endif + if ((err = cache_final(ch, &saved_compiler_id))) return err; -- 2.54.0 From af12596e8d728423e361e4755a6078c5ef8faf69 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Jul 2020 19:39:18 -0700 Subject: [PATCH 284/295] stage2: breaking AST memory layout modifications InfixOp is flattened out so that each operator is an independent AST node tag. The two kinds of structs are now Catch and SimpleInfixOp. Beginning implementation of supporting codegen for const locals. --- lib/std/zig/ast.zig | 241 ++++++++++++++++++-------------- lib/std/zig/parse.zig | 167 ++++++++++++++++++---- lib/std/zig/render.zig | 133 ++++++++++++++++-- src-self-hosted/Module.zig | 2 +- src-self-hosted/astgen.zig | 180 ++++++++++++++---------- src-self-hosted/translate_c.zig | 165 ++++++++++++++-------- 6 files changed, 601 insertions(+), 287 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 16173c423701a0f21bbf0ed40ed41e30afb1cd03..b91cac7865662a3394ad50ac1e2f0a5d23f525d3 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -408,8 +408,54 @@ pub const Node = struct { VarDecl, Defer, - // Operators - InfixOp, + // Infix operators + Catch, + + // SimpleInfixOp + Add, + AddWrap, + ArrayCat, + ArrayMult, + Assign, + AssignBitAnd, + AssignBitOr, + AssignBitShiftLeft, + AssignBitShiftRight, + AssignBitXor, + AssignDiv, + AssignSub, + AssignSubWrap, + AssignMod, + AssignAdd, + AssignAddWrap, + AssignMul, + AssignMulWrap, + BangEqual, + BitAnd, + BitOr, + BitShiftLeft, + BitShiftRight, + BitXor, + BoolAnd, + BoolOr, + Div, + EqualEqual, + ErrorUnion, + GreaterOrEqual, + GreaterThan, + LessOrEqual, + LessThan, + MergeErrorSets, + Mod, + Mul, + MulWrap, + Period, + Range, + Sub, + SubWrap, + UnwrapOptional, + + // SimplePrefixOp AddressOf, Await, BitNot, @@ -419,6 +465,7 @@ pub const Node = struct { NegationWrap, Resume, Try, + ArrayType, /// ArrayType but has a sentinel node. ArrayTypeSentinel, @@ -492,7 +539,51 @@ pub const Node = struct { .TestDecl => TestDecl, .VarDecl => VarDecl, .Defer => Defer, - .InfixOp => InfixOp, + .Catch => Catch, + + .Add, + .AddWrap, + .ArrayCat, + .ArrayMult, + .Assign, + .AssignBitAnd, + .AssignBitOr, + .AssignBitShiftLeft, + .AssignBitShiftRight, + .AssignBitXor, + .AssignDiv, + .AssignSub, + .AssignSubWrap, + .AssignMod, + .AssignAdd, + .AssignAddWrap, + .AssignMul, + .AssignMulWrap, + .BangEqual, + .BitAnd, + .BitOr, + .BitShiftLeft, + .BitShiftRight, + .BitXor, + .BoolAnd, + .BoolOr, + .Div, + .EqualEqual, + .ErrorUnion, + .GreaterOrEqual, + .GreaterThan, + .LessOrEqual, + .LessThan, + .MergeErrorSets, + .Mod, + .Mul, + .MulWrap, + .Period, + .Range, + .Sub, + .SubWrap, + .UnwrapOptional, + => SimpleInfixOp, .AddressOf, .Await, @@ -507,13 +598,17 @@ pub const Node = struct { .ArrayType => ArrayType, .ArrayTypeSentinel => ArrayTypeSentinel, + .PtrType => PtrType, .SliceType => SliceType, .SuffixOp => SuffixOp, + .ArrayInitializer => ArrayInitializer, .ArrayInitializerDot => ArrayInitializerDot, + .StructInitializer => StructInitializer, .StructInitializerDot => StructInitializerDot, + .Call => Call, .Switch => Switch, .While => While, @@ -1859,117 +1954,22 @@ pub const Node = struct { } }; - /// TODO split up and make every op its own AST Node tag - pub const InfixOp = struct { - base: Node = Node{ .tag = .InfixOp }, + pub const Catch = struct { + base: Node = Node{ .tag = .Catch }, op_token: TokenIndex, lhs: *Node, - op: Op, rhs: *Node, + payload: ?*Node, - pub const Op = union(enum) { - Add, - AddWrap, - ArrayCat, - ArrayMult, - Assign, - AssignBitAnd, - AssignBitOr, - AssignBitShiftLeft, - AssignBitShiftRight, - AssignBitXor, - AssignDiv, - AssignSub, - AssignSubWrap, - AssignMod, - AssignAdd, - AssignAddWrap, - AssignMul, - AssignMulWrap, - BangEqual, - BitAnd, - BitOr, - BitShiftLeft, - BitShiftRight, - BitXor, - BoolAnd, - BoolOr, - Catch: ?*Node, - Div, - EqualEqual, - ErrorUnion, - GreaterOrEqual, - GreaterThan, - LessOrEqual, - LessThan, - MergeErrorSets, - Mod, - Mul, - MulWrap, - Period, - Range, - Sub, - SubWrap, - UnwrapOptional, - }; - - pub fn iterate(self: *const InfixOp, index: usize) ?*Node { + pub fn iterate(self: *const Catch, index: usize) ?*Node { var i = index; if (i < 1) return self.lhs; i -= 1; - switch (self.op) { - .Catch => |maybe_payload| { - if (maybe_payload) |payload| { - if (i < 1) return payload; - i -= 1; - } - }, - - .Add, - .AddWrap, - .ArrayCat, - .ArrayMult, - .Assign, - .AssignBitAnd, - .AssignBitOr, - .AssignBitShiftLeft, - .AssignBitShiftRight, - .AssignBitXor, - .AssignDiv, - .AssignSub, - .AssignSubWrap, - .AssignMod, - .AssignAdd, - .AssignAddWrap, - .AssignMul, - .AssignMulWrap, - .BangEqual, - .BitAnd, - .BitOr, - .BitShiftLeft, - .BitShiftRight, - .BitXor, - .BoolAnd, - .BoolOr, - .Div, - .EqualEqual, - .ErrorUnion, - .GreaterOrEqual, - .GreaterThan, - .LessOrEqual, - .LessThan, - .MergeErrorSets, - .Mod, - .Mul, - .MulWrap, - .Period, - .Range, - .Sub, - .SubWrap, - .UnwrapOptional, - => {}, + if (self.payload) |payload| { + if (i < 1) return payload; + i -= 1; } if (i < 1) return self.rhs; @@ -1978,11 +1978,38 @@ pub const Node = struct { return null; } - pub fn firstToken(self: *const InfixOp) TokenIndex { + pub fn firstToken(self: *const Catch) TokenIndex { return self.lhs.firstToken(); } - pub fn lastToken(self: *const InfixOp) TokenIndex { + pub fn lastToken(self: *const Catch) TokenIndex { + return self.rhs.lastToken(); + } + }; + + pub const SimpleInfixOp = struct { + base: Node, + op_token: TokenIndex, + lhs: *Node, + rhs: *Node, + + pub fn iterate(self: *const SimpleInfixOp, index: usize) ?*Node { + var i = index; + + if (i < 1) return self.lhs; + i -= 1; + + if (i < 1) return self.rhs; + i -= 1; + + return null; + } + + pub fn firstToken(self: *const SimpleInfixOp) TokenIndex { + return self.lhs.firstToken(); + } + + pub fn lastToken(self: *const SimpleInfixOp) TokenIndex { return self.rhs.lastToken(); } }; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 6eb3742c0e74202b2ac7dc2b880abec968265f95..b02cdcc1fd4a6342b2d9a48b52941680374d41f1 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1015,7 +1015,7 @@ const Parser = struct { /// BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)* fn parseBoolOrExpr(p: *Parser) !?*Node { return p.parseBinOpExpr( - SimpleBinOpParseFn(.Keyword_or, Node.InfixOp.Op.BoolOr), + SimpleBinOpParseFn(.Keyword_or, .BoolOr), parseBoolAndExpr, .Infinitely, ); @@ -1405,8 +1405,8 @@ const Parser = struct { fn parseErrorUnionExpr(p: *Parser) !?*Node { const suffix_expr = (try p.parseSuffixExpr()) orelse return null; - if (try SimpleBinOpParseFn(.Bang, Node.InfixOp.Op.ErrorUnion)(p)) |node| { - const error_union = node.cast(Node.InfixOp).?; + if (try SimpleBinOpParseFn(.Bang, .ErrorUnion)(p)) |node| { + const error_union = node.castTag(.ErrorUnion).?; const type_expr = try p.expectNode(parseTypeExpr, .{ .ExpectedTypeExpr = .{ .token = p.tok_i }, }); @@ -1439,10 +1439,56 @@ const Parser = struct { .ExpectedPrimaryTypeExpr = .{ .token = p.tok_i }, }); + // TODO pass `res` into `parseSuffixOp` rather than patching it up afterwards. while (try p.parseSuffixOp()) |node| { switch (node.tag) { .SuffixOp => node.cast(Node.SuffixOp).?.lhs = res, - .InfixOp => node.cast(Node.InfixOp).?.lhs = res, + .Catch => node.castTag(.Catch).?.lhs = res, + + .Add, + .AddWrap, + .ArrayCat, + .ArrayMult, + .Assign, + .AssignBitAnd, + .AssignBitOr, + .AssignBitShiftLeft, + .AssignBitShiftRight, + .AssignBitXor, + .AssignDiv, + .AssignSub, + .AssignSubWrap, + .AssignMod, + .AssignAdd, + .AssignAddWrap, + .AssignMul, + .AssignMulWrap, + .BangEqual, + .BitAnd, + .BitOr, + .BitShiftLeft, + .BitShiftRight, + .BitXor, + .BoolAnd, + .BoolOr, + .Div, + .EqualEqual, + .ErrorUnion, + .GreaterOrEqual, + .GreaterThan, + .LessOrEqual, + .LessThan, + .MergeErrorSets, + .Mod, + .Mul, + .MulWrap, + .Period, + .Range, + .Sub, + .SubWrap, + .UnwrapOptional, + => node.cast(Node.SimpleInfixOp).?.lhs = res, + else => unreachable, } res = node; @@ -1470,10 +1516,55 @@ const Parser = struct { var res = expr; while (true) { + // TODO pass `res` into `parseSuffixOp` rather than patching it up afterwards. if (try p.parseSuffixOp()) |node| { switch (node.tag) { .SuffixOp => node.cast(Node.SuffixOp).?.lhs = res, - .InfixOp => node.cast(Node.InfixOp).?.lhs = res, + .Catch => node.castTag(.Catch).?.lhs = res, + + .Add, + .AddWrap, + .ArrayCat, + .ArrayMult, + .Assign, + .AssignBitAnd, + .AssignBitOr, + .AssignBitShiftLeft, + .AssignBitShiftRight, + .AssignBitXor, + .AssignDiv, + .AssignSub, + .AssignSubWrap, + .AssignMod, + .AssignAdd, + .AssignAddWrap, + .AssignMul, + .AssignMulWrap, + .BangEqual, + .BitAnd, + .BitOr, + .BitShiftLeft, + .BitShiftRight, + .BitXor, + .BoolAnd, + .BoolOr, + .Div, + .EqualEqual, + .ErrorUnion, + .GreaterOrEqual, + .GreaterThan, + .LessOrEqual, + .LessThan, + .MergeErrorSets, + .Mod, + .Mul, + .MulWrap, + .Period, + .Range, + .Sub, + .SubWrap, + .UnwrapOptional, + => node.cast(Node.SimpleInfixOp).?.lhs = res, else => unreachable, } res = node; @@ -1560,11 +1651,11 @@ const Parser = struct { const global_error_set = try p.createLiteral(Node.ErrorType, token); if (period == null or identifier == null) return global_error_set; - const node = try p.arena.allocator.create(Node.InfixOp); + const node = try p.arena.allocator.create(Node.SimpleInfixOp); node.* = .{ + .base = Node{ .tag = .Period }, .op_token = period.?, .lhs = global_error_set, - .op = .Period, .rhs = identifier.?, }; return &node.base; @@ -2237,11 +2328,11 @@ const Parser = struct { .ExpectedExpr = .{ .token = p.tok_i }, }); - const node = try p.arena.allocator.create(Node.InfixOp); + const node = try p.arena.allocator.create(Node.SimpleInfixOp); node.* = .{ + .base = Node{ .tag = .Range }, .op_token = token, .lhs = expr, - .op = .Range, .rhs = range_end, }; return &node.base; @@ -2266,7 +2357,7 @@ const Parser = struct { /// / EQUAL fn parseAssignOp(p: *Parser) !?*Node { const token = p.nextToken(); - const op: Node.InfixOp.Op = switch (p.token_ids[token]) { + const op: Node.Tag = switch (p.token_ids[token]) { .AsteriskEqual => .AssignMul, .SlashEqual => .AssignDiv, .PercentEqual => .AssignMod, @@ -2287,11 +2378,11 @@ const Parser = struct { }, }; - const node = try p.arena.allocator.create(Node.InfixOp); + const node = try p.arena.allocator.create(Node.SimpleInfixOp); node.* = .{ + .base = .{ .tag = op }, .op_token = token, .lhs = undefined, // set by caller - .op = op, .rhs = undefined, // set by caller }; return &node.base; @@ -2306,7 +2397,7 @@ const Parser = struct { /// / RARROWEQUAL fn parseCompareOp(p: *Parser) !?*Node { const token = p.nextToken(); - const op: Node.InfixOp.Op = switch (p.token_ids[token]) { + const op: Node.Tag = switch (p.token_ids[token]) { .EqualEqual => .EqualEqual, .BangEqual => .BangEqual, .AngleBracketLeft => .LessThan, @@ -2330,12 +2421,22 @@ const Parser = struct { /// / KEYWORD_catch Payload? fn parseBitwiseOp(p: *Parser) !?*Node { const token = p.nextToken(); - const op: Node.InfixOp.Op = switch (p.token_ids[token]) { + const op: Node.Tag = switch (p.token_ids[token]) { .Ampersand => .BitAnd, .Caret => .BitXor, .Pipe => .BitOr, .Keyword_orelse => .UnwrapOptional, - .Keyword_catch => .{ .Catch = try p.parsePayload() }, + .Keyword_catch => { + const payload = try p.parsePayload(); + const node = try p.arena.allocator.create(Node.Catch); + node.* = .{ + .op_token = token, + .lhs = undefined, // set by caller + .rhs = undefined, // set by caller + .payload = payload, + }; + return &node.base; + }, else => { p.putBackToken(token); return null; @@ -2350,7 +2451,7 @@ const Parser = struct { /// / RARROW2 fn parseBitShiftOp(p: *Parser) !?*Node { const token = p.nextToken(); - const op: Node.InfixOp.Op = switch (p.token_ids[token]) { + const op: Node.Tag = switch (p.token_ids[token]) { .AngleBracketAngleBracketLeft => .BitShiftLeft, .AngleBracketAngleBracketRight => .BitShiftRight, else => { @@ -2370,7 +2471,7 @@ const Parser = struct { /// / MINUSPERCENT fn parseAdditionOp(p: *Parser) !?*Node { const token = p.nextToken(); - const op: Node.InfixOp.Op = switch (p.token_ids[token]) { + const op: Node.Tag = switch (p.token_ids[token]) { .Plus => .Add, .Minus => .Sub, .PlusPlus => .ArrayCat, @@ -2394,7 +2495,7 @@ const Parser = struct { /// / ASTERISKPERCENT fn parseMultiplyOp(p: *Parser) !?*Node { const token = p.nextToken(); - const op: Node.InfixOp.Op = switch (p.token_ids[token]) { + const op: Node.Tag = switch (p.token_ids[token]) { .PipePipe => .MergeErrorSets, .Asterisk => .Mul, .Slash => .Div, @@ -2673,14 +2774,14 @@ const Parser = struct { if (p.eatToken(.Period)) |period| { if (try p.parseIdentifier()) |identifier| { - // TODO: It's a bit weird to return an InfixOp from the SuffixOp parser. + // TODO: It's a bit weird to return a SimpleInfixOp from the SuffixOp parser. // Should there be an Node.SuffixOp.FieldAccess variant? Or should // this grammar rule be altered? - const node = try p.arena.allocator.create(Node.InfixOp); + const node = try p.arena.allocator.create(Node.SimpleInfixOp); node.* = .{ + .base = Node{ .tag = .Period }, .op_token = period, .lhs = undefined, // set by caller - .op = .Period, .rhs = identifier, }; return &node.base; @@ -2987,7 +3088,7 @@ const Parser = struct { }.parse; } - fn SimpleBinOpParseFn(comptime token: Token.Id, comptime op: Node.InfixOp.Op) NodeParseFn { + fn SimpleBinOpParseFn(comptime token: Token.Id, comptime op: Node.Tag) NodeParseFn { return struct { pub fn parse(p: *Parser) Error!?*Node { const op_token = if (token == .Keyword_and) switch (p.token_ids[p.tok_i]) { @@ -3001,11 +3102,11 @@ const Parser = struct { else => return null, } else p.eatToken(token) orelse return null; - const node = try p.arena.allocator.create(Node.InfixOp); + const node = try p.arena.allocator.create(Node.SimpleInfixOp); node.* = .{ + .base = .{ .tag = op }, .op_token = op_token, .lhs = undefined, // set by caller - .op = op, .rhs = undefined, // set by caller }; return &node.base; @@ -3350,9 +3451,13 @@ const Parser = struct { const left = res; res = node; - const op = node.cast(Node.InfixOp).?; - op.*.lhs = left; - op.*.rhs = right; + if (node.castTag(.Catch)) |op| { + op.lhs = left; + op.rhs = right; + } else if (node.cast(Node.SimpleInfixOp)) |op| { + op.lhs = left; + op.rhs = right; + } switch (chain) { .Once => break, @@ -3363,12 +3468,12 @@ const Parser = struct { return res; } - fn createInfixOp(p: *Parser, index: TokenIndex, op: Node.InfixOp.Op) !*Node { - const node = try p.arena.allocator.create(Node.InfixOp); + fn createInfixOp(p: *Parser, op_token: TokenIndex, tag: Node.Tag) !*Node { + const node = try p.arena.allocator.create(Node.SimpleInfixOp); node.* = .{ - .op_token = index, + .base = Node{ .tag = tag }, + .op_token = op_token, .lhs = undefined, // set by caller - .op = op, .rhs = undefined, // set by caller }; return &node.base; diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 67d73c92a6ce14d94e53e8eb39a7405014cce429..7f8a18299bd4b5ae2d88b00f6a5f9e0e93ac6d7e 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -436,11 +436,79 @@ fn renderExpression( } }, - .InfixOp => { - const infix_op_node = @fieldParentPtr(ast.Node.InfixOp, "base", base); + .Catch => { + const infix_op_node = @fieldParentPtr(ast.Node.Catch, "base", base); - const op_space = switch (infix_op_node.op) { - ast.Node.InfixOp.Op.Period, ast.Node.InfixOp.Op.ErrorUnion, ast.Node.InfixOp.Op.Range => Space.None, + const op_space = Space.Space; + try renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.lhs, op_space); + + const after_op_space = blk: { + const loc = tree.tokenLocation(tree.token_locs[infix_op_node.op_token].end, tree.nextToken(infix_op_node.op_token)); + break :blk if (loc.line == 0) op_space else Space.Newline; + }; + + try renderToken(tree, stream, infix_op_node.op_token, indent, start_col, after_op_space); + if (after_op_space == Space.Newline and + tree.token_ids[tree.nextToken(infix_op_node.op_token)] != .MultilineStringLiteralLine) + { + try stream.writeByteNTimes(' ', indent + indent_delta); + start_col.* = indent + indent_delta; + } + + if (infix_op_node.payload) |payload| { + try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space); + } + + return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space); + }, + + .Add, + .AddWrap, + .ArrayCat, + .ArrayMult, + .Assign, + .AssignBitAnd, + .AssignBitOr, + .AssignBitShiftLeft, + .AssignBitShiftRight, + .AssignBitXor, + .AssignDiv, + .AssignSub, + .AssignSubWrap, + .AssignMod, + .AssignAdd, + .AssignAddWrap, + .AssignMul, + .AssignMulWrap, + .BangEqual, + .BitAnd, + .BitOr, + .BitShiftLeft, + .BitShiftRight, + .BitXor, + .BoolAnd, + .BoolOr, + .Div, + .EqualEqual, + .ErrorUnion, + .GreaterOrEqual, + .GreaterThan, + .LessOrEqual, + .LessThan, + .MergeErrorSets, + .Mod, + .Mul, + .MulWrap, + .Period, + .Range, + .Sub, + .SubWrap, + .UnwrapOptional, + => { + const infix_op_node = @fieldParentPtr(ast.Node.SimpleInfixOp, "base", base); + + const op_space = switch (base.tag) { + .Period, .ErrorUnion, .Range => Space.None, else => Space.Space, }; try renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.lhs, op_space); @@ -458,13 +526,6 @@ fn renderExpression( start_col.* = indent + indent_delta; } - switch (infix_op_node.op) { - ast.Node.InfixOp.Op.Catch => |maybe_payload| if (maybe_payload) |payload| { - try renderExpression(allocator, stream, tree, indent, start_col, payload, Space.Space); - }, - else => {}, - } - return renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.rhs, space); }, @@ -2553,10 +2614,52 @@ fn nodeIsBlock(base: *const ast.Node) bool { } fn nodeCausesSliceOpSpace(base: *ast.Node) bool { - const infix_op = base.cast(ast.Node.InfixOp) orelse return false; - return switch (infix_op.op) { - ast.Node.InfixOp.Op.Period => false, - else => true, + return switch (base.tag) { + .Catch, + .Add, + .AddWrap, + .ArrayCat, + .ArrayMult, + .Assign, + .AssignBitAnd, + .AssignBitOr, + .AssignBitShiftLeft, + .AssignBitShiftRight, + .AssignBitXor, + .AssignDiv, + .AssignSub, + .AssignSubWrap, + .AssignMod, + .AssignAdd, + .AssignAddWrap, + .AssignMul, + .AssignMulWrap, + .BangEqual, + .BitAnd, + .BitOr, + .BitShiftLeft, + .BitShiftRight, + .BitXor, + .BoolAnd, + .BoolOr, + .Div, + .EqualEqual, + .ErrorUnion, + .GreaterOrEqual, + .GreaterThan, + .LessOrEqual, + .LessThan, + .MergeErrorSets, + .Mod, + .Mul, + .MulWrap, + .Range, + .Sub, + .SubWrap, + .UnwrapOptional, + => true, + + else => false, }; } diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 050588f3dffc207432317d6f64ecfff99181e8f2..42a30a1d2081123aa74199bb0055c5e0b6420d10 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1954,7 +1954,7 @@ pub fn addZIRInstSpecial( positionals: std.meta.fieldInfo(T, "positionals").field_type, kw_args: std.meta.fieldInfo(T, "kw_args").field_type, ) !*T { - const gen_zir = scope.cast(Scope.GenZIR).?; + const gen_zir = scope.getGenZIR(); try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); const inst = try newZIRInst(gen_zir.arena, src, T, positionals, kw_args); gen_zir.instructions.appendAssumeCapacity(&inst.base); diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index f75097575d50c72c2b95bd427552e48faee54310..1f157e6389e0c0232b03b11ed5910948d9907441 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -12,22 +12,29 @@ const Scope = Module.Scope; const InnerError = Module.InnerError; /// Turn Zig AST into untyped ZIR istructions. -pub fn expr(mod: *Module, scope: *Scope, ast_node: *ast.Node) InnerError!*zir.Inst { - switch (ast_node.tag) { +pub fn expr(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst { + switch (node.tag) { .VarDecl => unreachable, // Handled in `blockExpr`. - .Identifier => return identifier(mod, scope, @fieldParentPtr(ast.Node.Identifier, "base", ast_node)), - .Asm => return assembly(mod, scope, @fieldParentPtr(ast.Node.Asm, "base", ast_node)), - .StringLiteral => return stringLiteral(mod, scope, @fieldParentPtr(ast.Node.StringLiteral, "base", ast_node)), - .IntegerLiteral => return integerLiteral(mod, scope, @fieldParentPtr(ast.Node.IntegerLiteral, "base", ast_node)), - .BuiltinCall => return builtinCall(mod, scope, @fieldParentPtr(ast.Node.BuiltinCall, "base", ast_node)), - .Call => return callExpr(mod, scope, @fieldParentPtr(ast.Node.Call, "base", ast_node)), - .Unreachable => return unreach(mod, scope, @fieldParentPtr(ast.Node.Unreachable, "base", ast_node)), - .ControlFlowExpression => return controlFlowExpr(mod, scope, @fieldParentPtr(ast.Node.ControlFlowExpression, "base", ast_node)), - .If => return ifExpr(mod, scope, @fieldParentPtr(ast.Node.If, "base", ast_node)), - .InfixOp => return infixOp(mod, scope, @fieldParentPtr(ast.Node.InfixOp, "base", ast_node)), - .BoolNot => return boolNot(mod, scope, @fieldParentPtr(ast.Node.SimplePrefixOp, "base", ast_node)), - else => return mod.failNode(scope, ast_node, "TODO implement astgen.Expr for {}", .{@tagName(ast_node.tag)}), + .Identifier => return identifier(mod, scope, node.castTag(.Identifier).?), + .Asm => return assembly(mod, scope, node.castTag(.Asm).?), + .StringLiteral => return stringLiteral(mod, scope, node.castTag(.StringLiteral).?), + .IntegerLiteral => return integerLiteral(mod, scope, node.castTag(.IntegerLiteral).?), + .BuiltinCall => return builtinCall(mod, scope, node.castTag(.BuiltinCall).?), + .Call => return callExpr(mod, scope, node.castTag(.Call).?), + .Unreachable => return unreach(mod, scope, node.castTag(.Unreachable).?), + .ControlFlowExpression => return controlFlowExpr(mod, scope, node.castTag(.ControlFlowExpression).?), + .If => return ifExpr(mod, scope, node.castTag(.If).?), + .Assign => return assign(mod, scope, node.castTag(.Assign).?), + .Add => return add(mod, scope, node.castTag(.Add).?), + .BangEqual => return cmp(mod, scope, node.castTag(.BangEqual).?, .neq), + .EqualEqual => return cmp(mod, scope, node.castTag(.EqualEqual).?, .eq), + .GreaterThan => return cmp(mod, scope, node.castTag(.GreaterThan).?, .gt), + .GreaterOrEqual => return cmp(mod, scope, node.castTag(.GreaterOrEqual).?, .gte), + .LessThan => return cmp(mod, scope, node.castTag(.LessThan).?, .lt), + .LessOrEqual => return cmp(mod, scope, node.castTag(.LessOrEqual).?, .lte), + .BoolNot => return boolNot(mod, scope, node.castTag(.BoolNot).?), + else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}), } } @@ -57,6 +64,7 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block } fn varDecl(mod: *Module, scope: *Scope, node: *ast.Node.VarDecl) InnerError!Scope.LocalVar { + // TODO implement detection of shadowing if (node.getTrailer("comptime_token")) |comptime_token| { return mod.failTok(scope, comptime_token, "TODO implement comptime locals", .{}); } @@ -98,66 +106,50 @@ fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerErr return mod.addZIRInst(scope, src, zir.Inst.BoolNot, .{ .operand = operand }, .{}); } -fn infixOp(mod: *Module, scope: *Scope, infix_node: *ast.Node.InfixOp) InnerError!*zir.Inst { - switch (infix_node.op) { - .Assign => { - if (infix_node.lhs.tag == .Identifier) { - const ident = @fieldParentPtr(ast.Node.Identifier, "base", infix_node.lhs); - const tree = scope.tree(); - const ident_name = tree.tokenSlice(ident.token); - if (std.mem.eql(u8, ident_name, "_")) { - return expr(mod, scope, infix_node.rhs); - } else { - return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); - } - } else { - return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); - } - }, - .Add => { - const lhs = try expr(mod, scope, infix_node.lhs); - const rhs = try expr(mod, scope, infix_node.rhs); - - const tree = scope.tree(); - const src = tree.token_locs[infix_node.op_token].start; - - return mod.addZIRInst(scope, src, zir.Inst.Add, .{ .lhs = lhs, .rhs = rhs }, .{}); - }, - .BangEqual, - .EqualEqual, - .GreaterThan, - .GreaterOrEqual, - .LessThan, - .LessOrEqual, - => { - const lhs = try expr(mod, scope, infix_node.lhs); - const rhs = try expr(mod, scope, infix_node.rhs); - - const tree = scope.tree(); - const src = tree.token_locs[infix_node.op_token].start; - - const op: std.math.CompareOperator = switch (infix_node.op) { - .BangEqual => .neq, - .EqualEqual => .eq, - .GreaterThan => .gt, - .GreaterOrEqual => .gte, - .LessThan => .lt, - .LessOrEqual => .lte, - else => unreachable, - }; - - return mod.addZIRInst(scope, src, zir.Inst.Cmp, .{ - .lhs = lhs, - .op = op, - .rhs = rhs, - }, .{}); - }, - else => |op| { - return mod.failNode(scope, &infix_node.base, "TODO implement infix operator {}", .{op}); - }, +fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { + if (infix_node.lhs.tag == .Identifier) { + const ident = @fieldParentPtr(ast.Node.Identifier, "base", infix_node.lhs); + const tree = scope.tree(); + const ident_name = tree.tokenSlice(ident.token); + if (std.mem.eql(u8, ident_name, "_")) { + return expr(mod, scope, infix_node.rhs); + } else { + return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); + } + } else { + return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); } } +fn add(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { + const lhs = try expr(mod, scope, infix_node.lhs); + const rhs = try expr(mod, scope, infix_node.rhs); + + const tree = scope.tree(); + const src = tree.token_locs[infix_node.op_token].start; + + return mod.addZIRInst(scope, src, zir.Inst.Add, .{ .lhs = lhs, .rhs = rhs }, .{}); +} + +fn cmp( + mod: *Module, + scope: *Scope, + infix_node: *ast.Node.SimpleInfixOp, + op: std.math.CompareOperator, +) InnerError!*zir.Inst { + const lhs = try expr(mod, scope, infix_node.lhs); + const rhs = try expr(mod, scope, infix_node.rhs); + + const tree = scope.tree(); + const src = tree.token_locs[infix_node.op_token].start; + + return mod.addZIRInst(scope, src, zir.Inst.Cmp, .{ + .lhs = lhs, + .op = op, + .rhs = rhs, + }, .{}); +} + fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.Inst { if (if_node.payload) |payload| { return mod.failNode(scope, payload, "TODO implement astgen.IfExpr for optionals", .{}); @@ -571,6 +563,47 @@ fn nodeNeedsMemoryLocation(node: *ast.Node) bool { .ErrorSetDecl, .ContainerDecl, .Asm, + .Add, + .AddWrap, + .ArrayCat, + .ArrayMult, + .Assign, + .AssignBitAnd, + .AssignBitOr, + .AssignBitShiftLeft, + .AssignBitShiftRight, + .AssignBitXor, + .AssignDiv, + .AssignSub, + .AssignSubWrap, + .AssignMod, + .AssignAdd, + .AssignAddWrap, + .AssignMul, + .AssignMulWrap, + .BangEqual, + .BitAnd, + .BitOr, + .BitShiftLeft, + .BitShiftRight, + .BitXor, + .BoolAnd, + .BoolOr, + .Div, + .EqualEqual, + .ErrorUnion, + .GreaterOrEqual, + .GreaterThan, + .LessOrEqual, + .LessThan, + .MergeErrorSets, + .Mod, + .Mul, + .MulWrap, + .Range, + .Period, + .Sub, + .SubWrap, => false, .ArrayInitializer, @@ -579,9 +612,10 @@ fn nodeNeedsMemoryLocation(node: *ast.Node) bool { .StructInitializerDot, => true, - .GroupedExpression => nodeNeedsMemoryLocation(node.cast(ast.Node.GroupedExpression).?.expr), + .GroupedExpression => nodeNeedsMemoryLocation(node.castTag(.GroupedExpression).?.expr), - .InfixOp => @panic("TODO nodeNeedsMemoryLocation for InfixOp"), + .UnwrapOptional => @panic("TODO nodeNeedsMemoryLocation for UnwrapOptional"), + .Catch => @panic("TODO nodeNeedsMemoryLocation for Catch"), .Await => @panic("TODO nodeNeedsMemoryLocation for Await"), .Try => @panic("TODO nodeNeedsMemoryLocation for Try"), .If => @panic("TODO nodeNeedsMemoryLocation for If"), diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index b04528a3d39b4ac8c19959d1fda6022331329324..1da52cda969f21dd3f2040290986b6068808b8cc 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -1103,11 +1103,11 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No const enum_ident = try transCreateNodeIdentifier(c, name); const period_tok = try appendToken(c, .Period, "."); const field_ident = try transCreateNodeIdentifier(c, field_name); - const field_access_node = try c.arena.create(ast.Node.InfixOp); + const field_access_node = try c.arena.create(ast.Node.SimpleInfixOp); field_access_node.* = .{ + .base = .{ .tag = .Period }, .op_token = period_tok, .lhs = enum_ident, - .op = .Period, .rhs = field_ident, }; cast_node.params()[0] = &field_access_node.base; @@ -1294,7 +1294,7 @@ fn transBinaryOperator( const op = ZigClangBinaryOperator_getOpcode(stmt); const qt = ZigClangBinaryOperator_getType(stmt); var op_token: ast.TokenIndex = undefined; - var op_id: ast.Node.InfixOp.Op = undefined; + var op_id: ast.Node.Tag = undefined; switch (op) { .Assign => return try transCreateNodeAssign(rp, scope, result_used, ZigClangBinaryOperator_getLHS(stmt), ZigClangBinaryOperator_getRHS(stmt)), .Comma => { @@ -1737,25 +1737,22 @@ fn exprIsStringLiteral(expr: *const ZigClangExpr) bool { fn isBoolRes(res: *ast.Node) bool { switch (res.tag) { - .InfixOp => switch (@fieldParentPtr(ast.Node.InfixOp, "base", res).op) { - .BoolOr, - .BoolAnd, - .EqualEqual, - .BangEqual, - .LessThan, - .GreaterThan, - .LessOrEqual, - .GreaterOrEqual, - => return true, + .BoolOr, + .BoolAnd, + .EqualEqual, + .BangEqual, + .LessThan, + .GreaterThan, + .LessOrEqual, + .GreaterOrEqual, + .BoolNot, + .BoolLiteral, + => return true, - else => {}, - }, - .BoolNot => return true, - .BoolLiteral => return true, .GroupedExpression => return isBoolRes(@fieldParentPtr(ast.Node.GroupedExpression, "base", res).expr), - else => {}, + + else => return false, } - return false; } fn finishBoolExpr( @@ -2312,11 +2309,11 @@ fn transInitListExprArray( &filler_init_node.base else blk: { const mul_tok = try appendToken(rp.c, .AsteriskAsterisk, "**"); - const mul_node = try rp.c.arena.create(ast.Node.InfixOp); + const mul_node = try rp.c.arena.create(ast.Node.SimpleInfixOp); mul_node.* = .{ + .base = .{ .tag = .ArrayMult }, .op_token = mul_tok, .lhs = &filler_init_node.base, - .op = .ArrayMult, .rhs = try transCreateNodeInt(rp.c, leftover_count), }; break :blk &mul_node.base; @@ -2326,11 +2323,11 @@ fn transInitListExprArray( return rhs_node; } - const cat_node = try rp.c.arena.create(ast.Node.InfixOp); + const cat_node = try rp.c.arena.create(ast.Node.SimpleInfixOp); cat_node.* = .{ + .base = .{ .tag = .ArrayCat }, .op_token = cat_tok, .lhs = &init_node.base, - .op = .ArrayCat, .rhs = rhs_node, }; return &cat_node.base; @@ -2723,11 +2720,11 @@ fn transCase( const ellips = try appendToken(rp.c, .Ellipsis3, "..."); const rhs_node = try transExpr(rp, scope, rhs, .used, .r_value); - const node = try rp.c.arena.create(ast.Node.InfixOp); + const node = try rp.c.arena.create(ast.Node.SimpleInfixOp); node.* = .{ + .base = .{ .tag = .Range }, .op_token = ellips, .lhs = lhs_node, - .op = .Range, .rhs = rhs_node, }; break :blk &node.base; @@ -3153,7 +3150,7 @@ fn transCreatePreCrement( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnaryOperator, - op: ast.Node.InfixOp.Op, + op: ast.Node.Tag, op_tok_id: std.zig.Token.Id, bytes: []const u8, used: ResultUsed, @@ -3227,7 +3224,7 @@ fn transCreatePostCrement( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangUnaryOperator, - op: ast.Node.InfixOp.Op, + op: ast.Node.Tag, op_tok_id: std.zig.Token.Id, bytes: []const u8, used: ResultUsed, @@ -3349,10 +3346,10 @@ fn transCreateCompoundAssign( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangCompoundAssignOperator, - assign_op: ast.Node.InfixOp.Op, + assign_op: ast.Node.Tag, assign_tok_id: std.zig.Token.Id, assign_bytes: []const u8, - bin_op: ast.Node.InfixOp.Op, + bin_op: ast.Node.Tag, bin_tok_id: std.zig.Token.Id, bin_bytes: []const u8, used: ResultUsed, @@ -3377,7 +3374,7 @@ fn transCreateCompoundAssign( // zig: lhs += rhs if ((is_mod or is_div) and is_signed) { const op_token = try appendToken(rp.c, .Equal, "="); - const op_node = try rp.c.arena.create(ast.Node.InfixOp); + const op_node = try rp.c.arena.create(ast.Node.SimpleInfixOp); const builtin = if (is_mod) "@rem" else "@divTrunc"; const builtin_node = try rp.c.createBuiltinCall(builtin, 2); const lhs_node = try transExpr(rp, scope, lhs, .used, .l_value); @@ -3386,9 +3383,9 @@ fn transCreateCompoundAssign( builtin_node.params()[1] = try transExpr(rp, scope, rhs, .used, .r_value); builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); op_node.* = .{ + .base = .{ .tag = .Assign }, .op_token = op_token, .lhs = lhs_node, - .op = .Assign, .rhs = &builtin_node.base, }; _ = try appendToken(rp.c, .Semicolon, ";"); @@ -3452,7 +3449,7 @@ fn transCreateCompoundAssign( if ((is_mod or is_div) and is_signed) { const op_token = try appendToken(rp.c, .Equal, "="); - const op_node = try rp.c.arena.create(ast.Node.InfixOp); + const op_node = try rp.c.arena.create(ast.Node.SimpleInfixOp); const builtin = if (is_mod) "@rem" else "@divTrunc"; const builtin_node = try rp.c.createBuiltinCall(builtin, 2); builtin_node.params()[0] = try transCreateNodePtrDeref(rp.c, lhs_node); @@ -3461,9 +3458,9 @@ fn transCreateCompoundAssign( builtin_node.rparen_token = try appendToken(rp.c, .RParen, ")"); _ = try appendToken(rp.c, .Semicolon, ";"); op_node.* = .{ + .base = .{ .tag = .Assign }, .op_token = op_token, .lhs = ref_node, - .op = .Assign, .rhs = &builtin_node.base, }; _ = try appendToken(rp.c, .Semicolon, ";"); @@ -3716,11 +3713,11 @@ fn maybeSuppressResult( } const lhs = try transCreateNodeIdentifier(rp.c, "_"); const op_token = try appendToken(rp.c, .Equal, "="); - const op_node = try rp.c.arena.create(ast.Node.InfixOp); + const op_node = try rp.c.arena.create(ast.Node.SimpleInfixOp); op_node.* = .{ + .base = .{ .tag = .Assign }, .op_token = op_token, .lhs = lhs, - .op = .Assign, .rhs = result, }; return &op_node.base; @@ -4095,11 +4092,11 @@ fn transCreateNodeAssign( } fn transCreateNodeFieldAccess(c: *Context, container: *ast.Node, field_name: []const u8) !*ast.Node { - const field_access_node = try c.arena.create(ast.Node.InfixOp); + const field_access_node = try c.arena.create(ast.Node.SimpleInfixOp); field_access_node.* = .{ + .base = .{ .tag = .Period }, .op_token = try appendToken(c, .Period, "."), .lhs = container, - .op = .Period, .rhs = try transCreateNodeIdentifier(c, field_name), }; return &field_access_node.base; @@ -4124,7 +4121,7 @@ fn transCreateNodeInfixOp( rp: RestorePoint, scope: *Scope, lhs_node: *ast.Node, - op: ast.Node.InfixOp.Op, + op: ast.Node.Tag, op_token: ast.TokenIndex, rhs_node: *ast.Node, used: ResultUsed, @@ -4134,11 +4131,11 @@ fn transCreateNodeInfixOp( try appendToken(rp.c, .LParen, "(") else null; - const node = try rp.c.arena.create(ast.Node.InfixOp); + const node = try rp.c.arena.create(ast.Node.SimpleInfixOp); node.* = .{ + .base = .{ .tag = op }, .op_token = op_token, .lhs = lhs_node, - .op = op, .rhs = rhs_node, }; if (!grouped) return maybeSuppressResult(rp, scope, used, &node.base); @@ -4156,7 +4153,7 @@ fn transCreateNodeBoolInfixOp( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangBinaryOperator, - op: ast.Node.InfixOp.Op, + op: ast.Node.Tag, used: ResultUsed, grouped: bool, ) !*ast.Node { @@ -4536,7 +4533,7 @@ fn transCreateNodeShiftOp( rp: RestorePoint, scope: *Scope, stmt: *const ZigClangBinaryOperator, - op: ast.Node.InfixOp.Op, + op: ast.Node.Tag, op_tok_id: std.zig.Token.Id, bytes: []const u8, ) !*ast.Node { @@ -4558,11 +4555,11 @@ fn transCreateNodeShiftOp( cast_node.params()[1] = rhs; cast_node.rparen_token = try appendToken(rp.c, .RParen, ")"); - const node = try rp.c.arena.create(ast.Node.InfixOp); + const node = try rp.c.arena.create(ast.Node.SimpleInfixOp); node.* = .{ + .base = .{ .tag = op }, .op_token = op_token, .lhs = lhs, - .op = op, .rhs = &cast_node.base, }; @@ -5404,11 +5401,11 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_ // suppress result const lhs = try transCreateNodeIdentifier(c, "_"); const op_token = try appendToken(c, .Equal, "="); - const op_node = try c.arena.create(ast.Node.InfixOp); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); op_node.* = .{ + .base = .{ .tag = .Assign }, .op_token = op_token, .lhs = lhs, - .op = .Assign, .rhs = last, }; try block_scope.statements.append(&op_node.base); @@ -5787,9 +5784,60 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, } } +fn nodeIsInfixOp(tag: ast.Node.Tag) bool { + return switch (tag) { + .Add, + .AddWrap, + .ArrayCat, + .ArrayMult, + .Assign, + .AssignBitAnd, + .AssignBitOr, + .AssignBitShiftLeft, + .AssignBitShiftRight, + .AssignBitXor, + .AssignDiv, + .AssignSub, + .AssignSubWrap, + .AssignMod, + .AssignAdd, + .AssignAddWrap, + .AssignMul, + .AssignMulWrap, + .BangEqual, + .BitAnd, + .BitOr, + .BitShiftLeft, + .BitShiftRight, + .BitXor, + .BoolAnd, + .BoolOr, + .Div, + .EqualEqual, + .ErrorUnion, + .GreaterOrEqual, + .GreaterThan, + .LessOrEqual, + .LessThan, + .MergeErrorSets, + .Mod, + .Mul, + .MulWrap, + .Period, + .Range, + .Sub, + .SubWrap, + .UnwrapOptional, + .Catch, + => true, + + else => false, + }; +} + fn macroBoolToInt(c: *Context, node: *ast.Node) !*ast.Node { if (!isBoolRes(node)) { - if (node.tag != .InfixOp) return node; + if (!nodeIsInfixOp(node.tag)) return node; const group_node = try c.arena.create(ast.Node.GroupedExpression); group_node.* = .{ @@ -5808,7 +5856,7 @@ fn macroBoolToInt(c: *Context, node: *ast.Node) !*ast.Node { fn macroIntToBool(c: *Context, node: *ast.Node) !*ast.Node { if (isBoolRes(node)) { - if (node.tag != .InfixOp) return node; + if (!nodeIsInfixOp(node.tag)) return node; const group_node = try c.arena.create(ast.Node.GroupedExpression); group_node.* = .{ @@ -5821,11 +5869,11 @@ fn macroIntToBool(c: *Context, node: *ast.Node) !*ast.Node { const op_token = try appendToken(c, .BangEqual, "!="); const zero = try transCreateNodeInt(c, 0); - const res = try c.arena.create(ast.Node.InfixOp); + const res = try c.arena.create(ast.Node.SimpleInfixOp); res.* = .{ + .base = .{ .tag = .BangEqual }, .op_token = op_token, .lhs = node, - .op = .BangEqual, .rhs = zero, }; const group_node = try c.arena.create(ast.Node.GroupedExpression); @@ -5842,7 +5890,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, while (true) { const tok = it.next().?; var op_token: ast.TokenIndex = undefined; - var op_id: ast.Node.InfixOp.Op = undefined; + var op_id: ast.Node.Tag = undefined; var bool_op = false; switch (tok.id) { .Period => { @@ -6049,11 +6097,11 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, const cast_fn = if (bool_op) macroIntToBool else macroBoolToInt; const lhs_node = try cast_fn(c, node); const rhs_node = try parseCPrefixOpExpr(c, it, source, source_loc, scope); - const op_node = try c.arena.create(ast.Node.InfixOp); + const op_node = try c.arena.create(ast.Node.SimpleInfixOp); op_node.* = .{ + .base = .{ .tag = op_id }, .op_token = op_token, .lhs = lhs_node, - .op = op_id, .rhs = try cast_fn(c, rhs_node), }; node = &op_node.base; @@ -6131,10 +6179,9 @@ fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { } }, - .InfixOp => { - const infix = node.cast(ast.Node.InfixOp).?; - if (infix.op != .Period) - return null; + .Period => { + const infix = node.castTag(.Period).?; + if (getContainerTypeOf(c, infix.lhs)) |ty_node| { if (ty_node.cast(ast.Node.ContainerDecl)) |container| { for (container.fieldsAndDecls()) |field_ref| { @@ -6161,9 +6208,7 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { return getContainer(c, ty); } } - } else if (ref.cast(ast.Node.InfixOp)) |infix| { - if (infix.op != .Period) - return null; + } else if (ref.castTag(.Period)) |infix| { if (getContainerTypeOf(c, infix.lhs)) |ty_node| { if (ty_node.cast(ast.Node.ContainerDecl)) |container| { for (container.fieldsAndDecls()) |field_ref| { -- 2.54.0 From d29dd5834b9d7386bb88e44bd2852428863cae81 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Jul 2020 22:36:35 -0700 Subject: [PATCH 285/295] stage2: local consts These are now supported enough that this example code hits the limitations of the register allocator: fn add(a: u32, b: u32) void { const c = a + b; // 7 const d = a + c; // 10 const e = d + b; // 14 assert(e == 14); } // error: TODO implement copyToNewRegister So now the next step is to implement register allocation as planned. --- src-self-hosted/Module.zig | 43 ++++++++++++++++++++++++++++--------- src-self-hosted/astgen.zig | 39 +++++++++++++++++++++------------ src-self-hosted/codegen.zig | 6 +++++- src-self-hosted/ir.zig | 5 +---- src-self-hosted/zir.zig | 15 +++++++------ 5 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 42a30a1d2081123aa74199bb0055c5e0b6420d10..72e5f6cd6307bda5447aa2ee3f17f169f918e3b5 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -700,19 +700,22 @@ pub const Scope = struct { pub const GenZIR = struct { pub const base_tag: Tag = .gen_zir; base: Scope = Scope{ .tag = base_tag }, + /// Parents can be: `GenZIR`, `ZIRModule`, `File` + parent: *Scope, decl: *Decl, arena: *Allocator, + /// The first N instructions in a function body ZIR are arg instructions. instructions: std.ArrayListUnmanaged(*zir.Inst) = .{}, }; /// This structure lives as long as the AST generation of the Block - /// node that contains the variable. This struct's parents can be - /// other `LocalVar` and finally a `GenZIR` at the top. + /// node that contains the variable. pub const LocalVar = struct { pub const base_tag: Tag = .local_var; base: Scope = Scope{ .tag = base_tag }, + /// Parents can be: `LocalVar`, `GenZIR`. + parent: *Scope, gen_zir: *GenZIR, - parent: *Scope, name: []const u8, inst: *zir.Inst, }; @@ -1164,6 +1167,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var fn_type_scope: Scope.GenZIR = .{ .decl = decl, .arena = &fn_type_scope_arena.allocator, + .parent = decl.scope, }; defer fn_type_scope.instructions.deinit(self.gpa); @@ -1241,12 +1245,32 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { var gen_scope: Scope.GenZIR = .{ .decl = decl, .arena = &gen_scope_arena.allocator, + .parent = decl.scope, }; defer gen_scope.instructions.deinit(self.gpa); + // We need an instruction for each parameter, and they must be first in the body. + try gen_scope.instructions.resize(self.gpa, fn_proto.params_len); + var params_scope = &gen_scope.base; + for (fn_proto.params()) |param, i| { + const name_token = param.name_token.?; + const src = tree.token_locs[name_token].start; + const param_name = tree.tokenSlice(name_token); + const arg = try newZIRInst(&gen_scope_arena.allocator, src, zir.Inst.Arg, .{}, .{}); + gen_scope.instructions.items[i] = &arg.base; + const sub_scope = try gen_scope_arena.allocator.create(Scope.LocalVar); + sub_scope.* = .{ + .parent = params_scope, + .gen_zir = &gen_scope, + .name = param_name, + .inst = &arg.base, + }; + params_scope = &sub_scope.base; + } + const body_block = body_node.cast(ast.Node.Block).?; - try astgen.blockExpr(self, &gen_scope.base, body_block); + try astgen.blockExpr(self, params_scope, body_block); if (!fn_type.fnReturnType().isNoReturn() and (gen_scope.instructions.items.len == 0 or !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) @@ -2236,17 +2260,16 @@ fn analyzeInstCompileError(self: *Module, scope: *Scope, inst: *zir.Inst.Compile fn analyzeInstArg(self: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*Inst { const b = try self.requireRuntimeBlock(scope, inst.base.src); const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; + const param_index = b.instructions.items.len; const param_count = fn_ty.fnParamLen(); - if (inst.positionals.index >= param_count) { + if (param_index >= param_count) { return self.fail(scope, inst.base.src, "parameter index {} outside list of length {}", .{ - inst.positionals.index, + param_index, param_count, }); } - const param_type = fn_ty.fnParamType(inst.positionals.index); - return self.addNewInstArgs(b, inst.base.src, param_type, Inst.Arg, .{ - .index = inst.positionals.index, - }); + const param_type = fn_ty.fnParamType(param_index); + return self.addNewInstArgs(b, inst.base.src, param_type, Inst.Arg, {}); } fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst { diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 1f157e6389e0c0232b03b11ed5910948d9907441..be70a724c2f8b32302adfedbfd2d630f511dc8aa 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -160,6 +160,7 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In } } var block_scope: Scope.GenZIR = .{ + .parent = scope, .decl = scope.decl().?, .arena = scope.arena(), .instructions = .{}, @@ -180,6 +181,7 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), }); var then_scope: Scope.GenZIR = .{ + .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, .instructions = .{}, @@ -199,6 +201,7 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In }; var else_scope: Scope.GenZIR = .{ + .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, .instructions = .{}, @@ -250,7 +253,11 @@ fn controlFlowExpr( } fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerError!*zir.Inst { + const tracy = trace(@src()); + defer tracy.end(); + const tree = scope.tree(); + // TODO implement @"aoeu" identifiers const ident_name = tree.tokenSlice(ident.token); const src = tree.token_locs[ident.token].start; if (mem.eql(u8, ident_name, "_")) { @@ -288,23 +295,27 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerErr } } + // Local variables, including function parameters. + { + var s = scope; + while (true) switch (s.tag) { + .local_var => { + const local_var = s.cast(Scope.LocalVar).?; + if (mem.eql(u8, local_var.name, ident_name)) { + return local_var.inst; + } + s = local_var.parent; + }, + .gen_zir => s = s.cast(Scope.GenZIR).?.parent, + else => break, + }; + } + if (mod.lookupDeclName(scope, ident_name)) |decl| { return try mod.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); } - // Function parameter - if (scope.decl()) |decl| { - if (tree.root_node.decls()[decl.src_index].cast(ast.Node.FnProto)) |fn_proto| { - for (fn_proto.params()) |param, i| { - const param_name = tree.tokenSlice(param.name_token.?); - if (mem.eql(u8, param_name, ident_name)) { - return try mod.addZIRInst(scope, src, zir.Inst.Arg, .{ .index = i }, .{}); - } - } - } - } - - return mod.failNode(scope, &ident.base, "TODO implement local variable identifier lookup", .{}); + return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name}); } fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.StringLiteral) InnerError!*zir.Inst { @@ -434,7 +445,7 @@ fn callExpr(mod: *Module, scope: *Scope, node: *ast.Node.Call) InnerError!*zir.I const lhs = try expr(mod, scope, node.lhs); const param_nodes = node.params(); - const args = try scope.cast(Scope.GenZIR).?.arena.alloc(*zir.Inst, param_nodes.len); + const args = try scope.getGenZIR().arena.alloc(*zir.Inst, param_nodes.len); for (param_nodes) |param_node, i| { args[i] = try expr(mod, scope, param_node); } diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 314d497808cf023267a9ac45e2c0002d8b756b5a..e78ee28b5dca2c233c913e2237291e4a8910d81c 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -73,6 +73,7 @@ pub fn generateSymbol( .code = code, .err_msg = null, .args = mc_args, + .arg_index = 0, .branch_stack = &branch_stack, .src = src, }; @@ -255,6 +256,7 @@ const Function = struct { code: *std.ArrayList(u8), err_msg: ?*ErrorMsg, args: []MCValue, + arg_index: usize, src: usize, /// Whenever there is a runtime branch, we push a Branch onto this stack, @@ -603,7 +605,9 @@ const Function = struct { } fn genArg(self: *Function, inst: *ir.Inst.Arg) !MCValue { - return self.args[inst.args.index]; + const i = self.arg_index; + self.arg_index += 1; + return self.args[i]; } fn genBreakpoint(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch) !MCValue { diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index c654bef61128a4963dba300daef8d11b11feb520..a150957de098a668b74daf6541cc5401181d6773 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -101,10 +101,7 @@ pub const Inst = struct { pub const Arg = struct { pub const base_tag = Tag.arg; base: Inst, - - args: struct { - index: usize, - }, + args: void, }; pub const Assembly = struct { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 45ced542550d54ff9a5bb7d4c42cc020c3afad1f..2f696d1787a48eb6453eb4b6e58a91572becc04b 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -34,7 +34,8 @@ pub const Inst = struct { /// These names are used directly as the instruction names in the text format. pub const Tag = enum { - /// Function parameter value. + /// Function parameter value. These must be first in a function's main block, + /// in respective order with the parameters. arg, /// A labeled block of code, which can return a value. block, @@ -184,9 +185,7 @@ pub const Inst = struct { pub const base_tag = Tag.arg; base: Inst, - positionals: struct { - index: usize, - }, + positionals: struct {}, kw_args: struct {}, }; @@ -1384,15 +1383,17 @@ const EmitZIR = struct { for (src_decls.items) |ir_decl| { switch (ir_decl.analysis) { .unreferenced => continue, + .complete => {}, + .codegen_failure => {}, // We still can emit the ZIR. + .codegen_failure_retryable => {}, // We still can emit the ZIR. + .in_progress => unreachable, .outdated => unreachable, .sema_failure, .sema_failure_retryable, - .codegen_failure, .dependency_failure, - .codegen_failure_retryable, => if (self.old_module.failed_decls.get(ir_decl)) |err_msg| { const fail_inst = try self.arena.allocator.create(Inst.CompileError); fail_inst.* = .{ @@ -1728,7 +1729,7 @@ const EmitZIR = struct { .src = inst.src, .tag = Inst.Arg.base_tag, }, - .positionals = .{ .index = old_inst.args.index }, + .positionals = .{}, .kw_args = .{}, }; break :blk &new_inst.base; -- 2.54.0 From 01ab167ce3c5c0db908b11451f160359230a440f Mon Sep 17 00:00:00 2001 From: pfg Date: Thu, 16 Jul 2020 00:04:54 -0700 Subject: [PATCH 286/295] =?UTF-8?q?stage2:=20change=20large=20switch=20?= =?UTF-8?q?=E2=86=92=20inline=20for=20loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-self-hosted/zir.zig | 45 ++++++----------------------------------- 1 file changed, 6 insertions(+), 39 deletions(-) diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 2f696d1787a48eb6453eb4b6e58a91572becc04b..5e8c966b949e91627e3971d7a4f8e3aca4da872d 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -760,46 +760,13 @@ const Writer = struct { stream: anytype, inst: *Inst, ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - // TODO I tried implementing this with an inline for loop and hit a compiler bug - switch (inst.tag) { - .arg => return self.writeInstToStreamGeneric(stream, .arg, inst), - .block => return self.writeInstToStreamGeneric(stream, .block, inst), - .@"break" => return self.writeInstToStreamGeneric(stream, .@"break", inst), - .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, inst), - .breakvoid => return self.writeInstToStreamGeneric(stream, .breakvoid, inst), - .call => return self.writeInstToStreamGeneric(stream, .call, inst), - .declref => return self.writeInstToStreamGeneric(stream, .declref, inst), - .declref_str => return self.writeInstToStreamGeneric(stream, .declref_str, inst), - .declval => return self.writeInstToStreamGeneric(stream, .declval, inst), - .declval_in_module => return self.writeInstToStreamGeneric(stream, .declval_in_module, inst), - .compileerror => return self.writeInstToStreamGeneric(stream, .compileerror, inst), - .@"const" => return self.writeInstToStreamGeneric(stream, .@"const", inst), - .boolnot => return self.writeInstToStreamGeneric(stream, .boolnot, inst), - .str => return self.writeInstToStreamGeneric(stream, .str, inst), - .int => return self.writeInstToStreamGeneric(stream, .int, inst), - .inttype => return self.writeInstToStreamGeneric(stream, .inttype, inst), - .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, inst), - .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, inst), - .deref => return self.writeInstToStreamGeneric(stream, .deref, inst), - .as => return self.writeInstToStreamGeneric(stream, .as, inst), - .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", inst), - .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", inst), - .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", inst), - .returnvoid => return self.writeInstToStreamGeneric(stream, .returnvoid, inst), - .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", inst), - .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", inst), - .primitive => return self.writeInstToStreamGeneric(stream, .primitive, inst), - .fntype => return self.writeInstToStreamGeneric(stream, .fntype, inst), - .intcast => return self.writeInstToStreamGeneric(stream, .intcast, inst), - .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst), - .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst), - .add => return self.writeInstToStreamGeneric(stream, .add, inst), - .sub => return self.writeInstToStreamGeneric(stream, .sub, inst), - .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst), - .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst), - .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst), - .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, inst), + inline for (@typeInfo(Inst.Tag).Enum.fields) |enum_field| { + const expected_tag = @field(Inst.Tag, enum_field.name); + if (inst.tag == expected_tag) { + return self.writeInstToStreamGeneric(stream, expected_tag, inst); + } } + unreachable; // all tags handled } fn writeInstToStreamGeneric( -- 2.54.0 From 8fe076daaf182863d116f2be7b13e3743bbcaae3 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 16 Jul 2020 16:00:42 +0300 Subject: [PATCH 287/295] std.mem.zeroInit support initiating with tuples --- lib/std/mem.zig | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 0fbe96942eedd03a3af3f610b0856475d7f77992..1a330667ed8227f5d37183760cecaa1cf1c46ef1 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -709,6 +709,14 @@ pub fn zeroInit(comptime T: type, init: anytype) T { .Struct => |init_info| { var value = std.mem.zeroes(T); + // typeInfo won't tell us if this is a tuple + if (comptime eql(u8, init_info.fields[0].name, "0")) { + inline for (init_info.fields) |field, i| { + @field(value, struct_info.fields[i].name) = @field(init, field.name); + } + return value; + } + inline for (init_info.fields) |field| { if (!@hasField(T, field.name)) { @compileError("Encountered an initializer for `" ++ field.name ++ "`, but it is not a field of " ++ @typeName(T)); @@ -760,7 +768,7 @@ test "zeroInit" { .a = 42, }); - testing.expectEqual(s, S{ + testing.expectEqual(S{ .a = 42, .b = null, .c = .{ @@ -768,7 +776,22 @@ test "zeroInit" { }, .e = [3]u8{ 0, 0, 0 }, .f = -1, - }); + }, s); + + const Color = struct { + r: u8, + g: u8, + b: u8, + a: u8, + }; + + const c = zeroInit(Color, .{255, 255}); + testing.expectEqual(Color{ + .r = 255, + .g = 255, + .b = 0, + .a = 0, + }, c); } /// Compares two slices of numbers lexicographically. O(n). -- 2.54.0 From 37647375dcb817f88895ba1a17d648184727e4cc Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 16 Jul 2020 16:20:47 +0300 Subject: [PATCH 288/295] translate-c: support initializer list expr macros --- src-self-hosted/translate_c.zig | 55 +++++++++++++++++++++++++++++++++ test/translate_c.zig | 25 +++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 1da52cda969f21dd3f2040290986b6068808b8cc..46f33dd0ff7963ff477658835cbc9add20bb22ef 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -6061,6 +6061,61 @@ fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, node = &call_node.base; continue; }, + .LBrace => { + // must come immediately after `node` + _ = try appendToken(c, .Comma, ","); + + const dot = try appendToken(c, .Period, "."); + _ = try appendToken(c, .LBrace, "{"); + + var init_vals = std.ArrayList(*ast.Node).init(c.gpa); + defer init_vals.deinit(); + + while (true) { + const val = try parseCPrefixOpExpr(c, it, source, source_loc, scope); + try init_vals.append(val); + const next = it.next().?; + if (next.id == .Comma) + _ = try appendToken(c, .Comma, ",") + else if (next.id == .RBrace) + break + else { + const first_tok = it.list.at(0); + try failDecl( + c, + source_loc, + source[first_tok.start..first_tok.end], + "unable to translate C expr: expected ',' or '}}'", + .{}, + ); + return error.ParseError; + } + } + const tuple_node = try ast.Node.StructInitializerDot.alloc(c.arena, init_vals.items.len); + tuple_node.* = .{ + .dot = dot, + .list_len = init_vals.items.len, + .rtoken = try appendToken(c, .RBrace, "}"), + }; + mem.copy(*ast.Node, tuple_node.list(), init_vals.items); + + + //(@import("std").mem.zeroInit(T, .{x})) + const import_fn_call = try c.createBuiltinCall("@import", 1); + const std_node = try transCreateNodeStringLiteral(c, "\"std\""); + import_fn_call.params()[0] = std_node; + import_fn_call.rparen_token = try appendToken(c, .RParen, ")"); + const inner_field_access = try transCreateNodeFieldAccess(c, &import_fn_call.base, "mem"); + const outer_field_access = try transCreateNodeFieldAccess(c, inner_field_access, "zeroInit"); + + const zero_init_call = try c.createCall(outer_field_access, 2); + zero_init_call.params()[0] = node; + zero_init_call.params()[1] = &tuple_node.base; + zero_init_call.rtoken = try appendToken(c, .RParen, ")"); + + node = &zero_init_call.base; + continue; + }, .BangEqual => { op_token = try appendToken(c, .BangEqual, "!="); op_id = .BangEqual; diff --git a/test/translate_c.zig b/test/translate_c.zig index 905c74bca2de451acab4edde2017a40051f9dc7e..d12b2ab860cd401c5ab1b0479a7de78dd79e3759 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -3,6 +3,31 @@ const std = @import("std"); const CrossTarget = std.zig.CrossTarget; pub fn addCases(cases: *tests.TranslateCContext) void { + cases.add("initializer list macro", + \\typedef struct Color { + \\ unsigned char r; + \\ unsigned char g; + \\ unsigned char b; + \\ unsigned char a; + \\} Color; + \\#define CLITERAL(type) (type) + \\#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray + , &[_][]const u8{ // TODO properly translate this + \\pub const struct_Color = extern struct { + \\ r: u8, + \\ g: u8, + \\ b: u8, + \\ a: u8, + \\}; + \\pub const Color = struct_Color; + , + \\pub inline fn CLITERAL(type_1: anytype) @TypeOf(type_1) { + \\ return type_1; + \\} + , + \\pub const LIGHTGRAY = @import("std").mem.zeroInit(CLITERAL(Color), .{ 200, 200, 200, 255 }); + }); + cases.add("complex switch", \\int main() { \\ int i = 2; -- 2.54.0 From 3cdc0f104ee375a669d1a322da877df64255976b Mon Sep 17 00:00:00 2001 From: data-man Date: Thu, 16 Jul 2020 10:26:43 +0500 Subject: [PATCH 289/295] Vectors clarification in docs --- doc/langref.html.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index f4320df1b58f895ba4d573b57136fdd7a78f5463..6180ab592f239b028032255bca29f41142f84120 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1950,7 +1950,7 @@ test "null terminated array" { {#header_open|Vectors#}

- A vector is a group of {#link|Integers#}, {#link|Floats#}, or {#link|Pointers#} which are operated on + A vector is a group of booleans, {#link|Integers#}, {#link|Floats#}, or {#link|Pointers#} which are operated on in parallel using a single instruction ({#link|SIMD#}). Vector types are created with the builtin function {#link|@Type#}, or using the shorthand as {#syntax#}std.meta.Vector{#endsyntax#}.

-- 2.54.0 From 06c08e5219f03328f483cded319c7ba75efb5188 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 16 Jul 2020 17:05:14 +0300 Subject: [PATCH 290/295] std.mem.zeroes use std.mem.set instead of `@memset` stage1 comptime is not smart enough to remeber the size of the casted item which leads to out of bounds errors. --- lib/std/mem.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 1a330667ed8227f5d37183760cecaa1cf1c46ef1..f4489a4867c10eed0b6b854f13069ef76585b58a 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -552,7 +552,7 @@ pub fn zeroes(comptime T: type) T { if (@sizeOf(T) == 0) return T{}; if (comptime meta.containerLayout(T) == .Extern) { var item: T = undefined; - @memset(@ptrCast([*]u8, &item), 0, @sizeOf(T)); + set(u8, asBytes(&item), 0); return item; } else { var structure: T = undefined; -- 2.54.0 From 5e88a7a42724d7ccf9db58e1524beedebb1efd93 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 16 Jul 2020 17:10:52 +0300 Subject: [PATCH 291/295] add behavior tests fro macro translations --- CONTRIBUTING.md | 5 +++++ test/stage1/behavior.zig | 1 + test/stage1/behavior/translate_c_macros.h | 9 +++++++++ test/stage1/behavior/translate_c_macros.zig | 12 ++++++++++++ test/tests.zig | 1 + 5 files changed, 28 insertions(+) create mode 100644 test/stage1/behavior/translate_c_macros.h create mode 100644 test/stage1/behavior/translate_c_macros.zig diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62b8083222eb781c8a5579d39d292bae581201ff..a8c77875e4c9b3a3f32a4908bdafb893033ff56a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -152,6 +152,11 @@ The relevant tests for this feature are: same, and that the program exits cleanly. This kind of test coverage is preferred, when possible, because it makes sure that the resulting Zig code is actually viable. + * `test/stage1/behavior/translate_c_macros.zig` - each test case consists of a Zig test + which checks that the relevant macros in `test/stage1/behavior/translate_c_macros.h`. + have the correct values. Macros have to be tested separately since they are expanded by + Clang in `run_translated_c` tests. + * `test/translate_c.zig` - each test case is C code, with a list of expected strings which must be found in the resulting Zig code. This kind of test is more precise in what it measures, but does not provide test coverage of whether the resulting Zig code is valid. diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index a15f1f26b9127e2d30c29f38cb2139e3e7690e0f..eb5bd9907f92dee0e25d112e065a70542e27c7a2 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -133,4 +133,5 @@ comptime { _ = @import("behavior/while.zig"); _ = @import("behavior/widening.zig"); _ = @import("behavior/src.zig"); + _ = @import("behavior/translate_c_macros.zig"); } diff --git a/test/stage1/behavior/translate_c_macros.h b/test/stage1/behavior/translate_c_macros.h new file mode 100644 index 0000000000000000000000000000000000000000..abc6c1e3cf078a89c9d2152a7a139b820ef845c6 --- /dev/null +++ b/test/stage1/behavior/translate_c_macros.h @@ -0,0 +1,9 @@ +// initializer list expression +typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; +} Color; +#define CLITERAL(type) (type) +#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray \ No newline at end of file diff --git a/test/stage1/behavior/translate_c_macros.zig b/test/stage1/behavior/translate_c_macros.zig new file mode 100644 index 0000000000000000000000000000000000000000..ea42016e9bb6db57f16aeaf89d7b89b78845eafb --- /dev/null +++ b/test/stage1/behavior/translate_c_macros.zig @@ -0,0 +1,12 @@ +const expect = @import("std").testing.expect; + +const h = @cImport(@cInclude("stage1/behavior/translate_c_macros.h")); + +test "initializer list expression" { + @import("std").testing.expectEqual(h.Color{ + .r = 200, + .g = 200, + .b = 200, + .a = 255, + }, h.LIGHTGRAY); +} diff --git a/test/tests.zig b/test/tests.zig index 0cf5ec28dd1ed072b3c00d8e67fa3d825e8571be..6598a05ea79c70f13b307c59d85eb15266a6ab96 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -537,6 +537,7 @@ pub fn addPkgTests( these_tests.enable_qemu = is_qemu_enabled; these_tests.enable_wasmtime = is_wasmtime_enabled; these_tests.glibc_multi_install_dir = glibc_dir; + these_tests.addIncludeDir("test"); step.dependOn(&these_tests.step); } -- 2.54.0 From a1e78d0b0631885b95dfa80ab617818d1fd61277 Mon Sep 17 00:00:00 2001 From: Vexu Date: Thu, 16 Jul 2020 23:35:31 +0300 Subject: [PATCH 292/295] add is_tuple field to struct typeinfo part of #4335 --- lib/std/builtin.zig | 1 + lib/std/mem.zig | 5 ++--- src/ir.cpp | 8 +++++++- test/stage1/behavior/type_info.zig | 7 ++++++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 5eafc4e409c19d6ce620eb6c5b5370ae557959fd..499011eab9c306e3686fdbbbc254d896c71cc95c 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -246,6 +246,7 @@ pub const TypeInfo = union(enum) { layout: ContainerLayout, fields: []const StructField, decls: []const Declaration, + is_tuple: bool, }; /// This data structure is used by the Zig language code generation and diff --git a/lib/std/mem.zig b/lib/std/mem.zig index f4489a4867c10eed0b6b854f13069ef76585b58a..ac7bde47c1acbf626f3df124448242db2c591ebb 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -709,8 +709,7 @@ pub fn zeroInit(comptime T: type, init: anytype) T { .Struct => |init_info| { var value = std.mem.zeroes(T); - // typeInfo won't tell us if this is a tuple - if (comptime eql(u8, init_info.fields[0].name, "0")) { + if (init_info.is_tuple) { inline for (init_info.fields) |field, i| { @field(value, struct_info.fields[i].name) = @field(init, field.name); } @@ -785,7 +784,7 @@ test "zeroInit" { a: u8, }; - const c = zeroInit(Color, .{255, 255}); + const c = zeroInit(Color, .{ 255, 255 }); testing.expectEqual(Color{ .r = 255, .g = 255, diff --git a/src/ir.cpp b/src/ir.cpp index e162125fb47913292fa68263ee654733845e428a..d027badac12d635cb04e0abc22a91c7d37c3a2c8 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -25546,7 +25546,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy result->special = ConstValSpecialStatic; result->type = ir_type_info_get_type(ira, "Struct", nullptr); - ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 3); + ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 4); result->data.x_struct.fields = fields; // layout: ContainerLayout @@ -25627,6 +25627,12 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy return err; } + // is_tuple: bool + ensure_field_index(result->type, "is_tuple", 3); + fields[3]->special = ConstValSpecialStatic; + fields[3]->type = ira->codegen->builtin_types.entry_bool; + fields[3]->data.x_bool = is_tuple(type_entry); + break; } case ZigTypeIdFn: diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index d4c8cec977c6563cb7476bceb6855347e56fd667..a79b3a1554efa2fa2d5f55938cf06241f7d7363a 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -280,7 +280,7 @@ fn testFunction() void { expect(bound_fn_info.BoundFn.args[0].arg_type.? == *const TestStruct); } -extern fn foo(a: usize, b: bool, args: ...) usize; +extern fn foo(a: usize, b: bool, ...) usize; test "typeInfo with comptime parameter in struct fn def" { const S = struct { @@ -425,3 +425,8 @@ test "Declarations are returned in declaration order" { expect(std.mem.eql(u8, d[3].name, "d")); expect(std.mem.eql(u8, d[4].name, "e")); } + +test "Struct.is_tuple" { + expect(@typeInfo(@TypeOf(.{0})).Struct.is_tuple); + expect(!@typeInfo(@TypeOf(.{ .a = 0 })).Struct.is_tuple); +} -- 2.54.0 From 39915ae08668884f51071feb4b1132b1903f0abb Mon Sep 17 00:00:00 2001 From: data-man Date: Fri, 17 Jul 2020 15:47:51 +0500 Subject: [PATCH 293/295] Add trait.isTuple --- lib/std/meta/trait.zig | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/std/meta/trait.zig b/lib/std/meta/trait.zig index 5cea0ecb9a176b853e2e5c54a6fa47012d99ec61..9e5b080e85b3e94c1d3884932aea6874cb255df1 100644 --- a/lib/std/meta/trait.zig +++ b/lib/std/meta/trait.zig @@ -342,6 +342,19 @@ test "std.meta.trait.isContainer" { testing.expect(!isContainer(u8)); } +pub fn isTuple(comptime T: type) bool { + return is(.Struct)(T) and @typeInfo(T).Struct.is_tuple; +} + +test "std.meta.trait.isTuple" { + const t1 = struct {}; + const t2 = .{ .a = 0 }; + const t3 = .{ 1, 2, 3 }; + testing.expect(!isTuple(t1)); + testing.expect(!isTuple(@TypeOf(t2))); + testing.expect(isTuple(@TypeOf(t3))); +} + pub fn hasDecls(comptime T: type, comptime names: anytype) bool { inline for (names) |name| { if (!@hasDecl(T, name)) -- 2.54.0 From 78962eeeda07d613ebdfd239082268a6702c19db Mon Sep 17 00:00:00 2001 From: Vexu Date: Sat, 18 Jul 2020 10:22:15 +0300 Subject: [PATCH 294/295] fix floatCast type check regression Closes #5900 --- src/ir.cpp | 6 ++++++ test/compile_errors.zig | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/ir.cpp b/src/ir.cpp index d027badac12d635cb04e0abc22a91c7d37c3a2c8..88e111ccb5998fc79f44d3a5efbb7dd67fa9d9ca 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -26739,6 +26739,12 @@ static IrInstGen *ir_analyze_instruction_float_cast(IrAnalyze *ira, IrInstSrcFlo } } + if (target->value->type->id != ZigTypeIdFloat) { + ir_add_error(ira, &instruction->target->base, buf_sprintf("expected float type, found '%s'", + buf_ptr(&target->value->type->name))); + return ira->codegen->invalid_inst_gen; + } + if (instr_is_comptime(target) || dest_type->id == ZigTypeIdComptimeFloat) { ZigValue *val = ir_resolve_const(ira, target, UndefBad); if (val == nullptr) diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 5de56f8ca75ced07be0deb67b70628fb79ba43a7..a1a261f887d9b9212c186aea77cf559336d87770 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -122,6 +122,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ var a: u32 = 2; \\ _ = @floatToInt(u32, a); \\} + \\export fn qux() void { + \\ var a: f32 = 2; + \\ _ = @intCast(u32, a); + \\} , &[_][]const u8{ "tmp.zig:3:32: error: unable to evaluate constant expression", "tmp.zig:3:9: note: referenced here", @@ -129,6 +133,8 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:7:9: note: referenced here", "tmp.zig:11:26: error: expected float type, found 'u32'", "tmp.zig:11:9: note: referenced here", + "tmp.zig:15:23: error: expected integer type, found 'f32'", + "tmp.zig:15:9: note: referenced here", }); cases.addTest("invalid float casts", @@ -144,6 +150,10 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ var a: f32 = 2; \\ _ = @intToFloat(f32, a); \\} + \\export fn qux() void { + \\ var a: u32 = 2; + \\ _ = @floatCast(f32, a); + \\} , &[_][]const u8{ "tmp.zig:3:36: error: unable to evaluate constant expression", "tmp.zig:3:9: note: referenced here", @@ -151,6 +161,8 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:7:9: note: referenced here", "tmp.zig:11:26: error: expected int type, found 'f32'", "tmp.zig:11:9: note: referenced here", + "tmp.zig:15:25: error: expected float type, found 'u32'", + "tmp.zig:15:9: note: referenced here", }); cases.addTest("invalid assignments", -- 2.54.0 From 596ca6cf70cf43c27e31bbcfc36bcdc70b13897a Mon Sep 17 00:00:00 2001 From: Vexu Date: Fri, 17 Jul 2020 20:16:23 +0300 Subject: [PATCH 295/295] allow non-pointer extern opaque variables --- src/analyze.cpp | 11 +++++++---- src/analyze.hpp | 2 +- src/ir.cpp | 2 +- test/stage1/behavior/misc.zig | 7 +++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/analyze.cpp b/src/analyze.cpp index 67c900507d0177c8dbed4fc9b3833c6cdcb07b62..66f3f28b50fa73c94f78d0e8b7f3ed048acd18e2 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -3823,15 +3823,18 @@ static Error resolve_decl_container(CodeGen *g, TldContainer *tld_container) { } } -ZigType *validate_var_type(CodeGen *g, AstNode *source_node, ZigType *type_entry) { +ZigType *validate_var_type(CodeGen *g, AstNodeVariableDeclaration *source_node, ZigType *type_entry) { switch (type_entry->id) { case ZigTypeIdInvalid: return g->builtin_types.entry_invalid; + case ZigTypeIdOpaque: + if (source_node->is_extern) + return type_entry; + ZIG_FALLTHROUGH; case ZigTypeIdUnreachable: case ZigTypeIdUndefined: case ZigTypeIdNull: - case ZigTypeIdOpaque: - add_node_error(g, source_node, buf_sprintf("variable of type '%s' not allowed", + add_node_error(g, source_node->type, buf_sprintf("variable of type '%s' not allowed", buf_ptr(&type_entry->name))); return g->builtin_types.entry_invalid; case ZigTypeIdComptimeFloat: @@ -3973,7 +3976,7 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var, bool allow_lazy) { } else { tld_var->analyzing_type = true; ZigType *proposed_type = analyze_type_expr(g, tld_var->base.parent_scope, var_decl->type); - explicit_type = validate_var_type(g, var_decl->type, proposed_type); + explicit_type = validate_var_type(g, var_decl, proposed_type); } } diff --git a/src/analyze.hpp b/src/analyze.hpp index d75c967394e46a0d9ad395bd19beeee85eddcb4f..cb58aa6b7411f423ae45da988a483cce5be002ac 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -77,7 +77,7 @@ void resolve_top_level_decl(CodeGen *g, Tld *tld, AstNode *source_node, bool all ZigType *get_src_ptr_type(ZigType *type); uint32_t get_ptr_align(CodeGen *g, ZigType *type); bool get_ptr_const(CodeGen *g, ZigType *type); -ZigType *validate_var_type(CodeGen *g, AstNode *source_node, ZigType *type_entry); +ZigType *validate_var_type(CodeGen *g, AstNodeVariableDeclaration *source_node, ZigType *type_entry); ZigType *container_ref_type(ZigType *type_entry); bool type_is_complete(ZigType *type_entry); bool type_is_resolved(ZigType *type_entry, ResolveStatus status); diff --git a/src/ir.cpp b/src/ir.cpp index 88e111ccb5998fc79f44d3a5efbb7dd67fa9d9ca..11ff7746e7f2dafd5b0130f2a00b826f1b7bf7c3 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -18505,7 +18505,7 @@ static IrInstGen *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstSrcDeclV if (decl_var_instruction->var_type != nullptr) { var_type = decl_var_instruction->var_type->child; ZigType *proposed_type = ir_resolve_type(ira, var_type); - explicit_type = validate_var_type(ira->codegen, var_type->base.source_node, proposed_type); + explicit_type = validate_var_type(ira->codegen, &var->decl_node->data.variable_declaration, proposed_type); if (type_is_invalid(explicit_type)) { var->var_type = ira->codegen->builtin_types.entry_invalid; return ira->codegen->invalid_inst_gen; diff --git a/test/stage1/behavior/misc.zig b/test/stage1/behavior/misc.zig index 021dbe5602a16b19c86e8ca1194018478e5dd046..57a9ba25766145e2ee29856cd3f6507da4fe5629 100644 --- a/test/stage1/behavior/misc.zig +++ b/test/stage1/behavior/misc.zig @@ -713,3 +713,10 @@ test "auto created variables have correct alignment" { expect(S.foo("\x7a\x7a\x7a\x7a") == 0x7a7a7a7a); comptime expect(S.foo("\x7a\x7a\x7a\x7a") == 0x7a7a7a7a); } + +extern var opaque_extern_var: @Type(.Opaque); +var var_to_export: u32 = 42; +test "extern variable with non-pointer opaque type" { + @export(var_to_export, .{ .name = "opaque_extern_var" }); + expect(@ptrCast(*align(1) u32, &opaque_extern_var).* == 42); +} -- 2.54.0