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 d170273808..5eb0205dfb 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 ae010c87e1..a92d7ec1de 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 f37f91088a..0111a33f34 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 c897276f83..41301f290d 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, + } +} 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 af8033ae91..9d419f9af2 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 0111a33f34..1a7c0c3527 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 2860229cb8..d8ed633887 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), + }); +} 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 9d419f9af2..9b0293228b 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 e38712af2b..4fc74282a5 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)) { 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 9b0293228b..47fba9d095 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 1a7c0c3527..5ba92be3c0 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 d8ed633887..f21e9f1ce9 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 })); +} 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 26ed0dc4e1..1c61239e3b 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 e73e5e3aaa..12d0298ab7 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 bd1a5b1e4c..ab8f4c8e70 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) { 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 1c61239e3b..b88e0510e3 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) { 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 f339aa639b..916fb7ff2d 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -50,14 +50,10 @@ pub const LineInfo = struct { } }; -/// 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(); +/// Tries to write to stderr, unbuffered, and ignores any error returned. +/// Does not append a newline. 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; 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 916fb7ff2d..591f2d1a80 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 eeceeac8a7..4acdbc7d1a 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; } }; 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 b88e0510e3..d59cff62a2 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 12d0298ab7..9792a42c45 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; }; 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 d59cff62a2..e363168579 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); 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 e363168579..33d98fd416 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: 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 887f0a2f9b..d28648e128 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 3a4e0a4d93..7b8690d604 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(); +} 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 605c973bb9..21d3572089 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 9b8dcd91c4..e3c009eee6 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 {} 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 21d3572089..9aea61281b 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(); 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 9aea61281b..102e6caeff 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.*; 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 102e6caeff..b787d371ed 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; } } }; 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 e3c009eee6..0133e55477 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{}); 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 0133e55477..89761736d9 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 {} 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 b787d371ed..a2fcddb7e1 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; + 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 (!std.mem.eql(u8, v1.msg, v2.msg)) { - std.debug.warn("Expected '{}', found '{}'\n", .{ v2.msg, v1.msg }); - err = error.ExpectedOtherError; + if (!handled) { + err = error.UnexpectedError; + std.debug.warn("Unexpected file error: {}\n", .{v1}); } } - 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}); - 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 }); - 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 89761736d9..50bfea5210 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 {} 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 a2fcddb7e1..2c47a4743d 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; 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 50bfea5210..865ececf18 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 {} 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 2c47a4743d..bde4a804e5 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| { 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 bde4a804e5..feba455758 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}); + break; } } - } - { - 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}); - } + 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 865ececf18..5c7a9e4554 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 {} 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 feba455758..c0fb52a74d 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 5c7a9e4554..78a00840a0 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 {} 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 c0fb52a74d..a9a4c4fc4b 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 bf5d4b8eae..a8cf4ca964 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); 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 a9a4c4fc4b..59960d3ed8 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(); 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 59960d3ed8..569b88184d 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, 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 569b88184d..42af19ddd9 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 a8cf4ca964..d58b30c29d 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) \\ 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 42af19ddd9..852c000a29 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; 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 852c000a29..ad0bd4a640 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 78a00840a0..72c41f1230 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 {} 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 ad0bd4a640..78bdcddf71 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 72c41f1230..5484b6459a 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, 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 78bdcddf71..4cf72ce481 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 5484b6459a..43c41aa364 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 {} 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 | 344 ++++++++++++++++++++++++++------------- 1 file changed, 228 insertions(+), 116 deletions(-) diff --git a/lib/std/debug/leb128.zig b/lib/std/debug/leb128.zig index ac278c4b1f..f9e25d9ee7 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)); - - var result: T = 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: T = undefined; - if (@shlWithOverflow(T, byte & 0x7f, @intCast(ShiftT, shift), &operand)) - return error.Overflow; - - result |= operand; - - if ((byte & 0x80) == 0) { - ptr.* += i + 1; - return result; - } - - shift += 7; - } + 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; } -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 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(); +} - var result: UT = 0; - var shift: usize = 0; +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); - while (true) { - const byte: u8 = try in_stream.readByte(); + const max_group = (U.bit_count + 6) / 7; - if (shift > T.bit_count) - return error.Overflow; + var value = @as(U, 0); + var group = @as(ShiftU, 0); - var operand: UT = undefined; - if (@shlWithOverflow(UT, @as(UT, byte & 0x7f), @intCast(ShiftT, shift), &operand)) { - if (byte != 0x7f) - return error.Overflow; + while (group < max_group) : (group += 1) { + const byte = try reader.readByte(); + var temp = @as(U, byte & 0x7f); + + 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; } - 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); + value |= temp; + if (byte & 0x80 == 0) { + if (byte & 0x40 != 0 and group + 1 < max_group) { + value |= @bitCast(U, @as(S, -1)) << ((group + 1) * 7); } - return @bitCast(T, result); + 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 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 value = @intCast(S, int_value); + + while (true) { + 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; - - 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); - } - } + 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; } +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)); + } } 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 f9e25d9ee7..76e76d8e4e 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); } 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 76e76d8e4e..f9e25d9ee7 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); } 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 f9e25d9ee7..76e76d8e4e 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); } 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 76e76d8e4e..f9e25d9ee7 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); } 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 f9e25d9ee7..76e76d8e4e 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); } 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 76e76d8e4e..16a23da123 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)); 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 d28648e128..71e3b473b2 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 && 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 e096a65491..d42a3c3d73 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"); +} 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 0000000000..706c2b2378 --- /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 b1cab77109..0961991ea6 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"); 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 591f2d1a80..f3c2cf3b31 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(); 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 8599d01a5d..94219c1631 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 d28648e128..8267842671 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 0000000000..8408a15116 --- /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 0000000000..0a1f958933 --- /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 1d361494eb..b13b1ce1e0 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); 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 5cf6d40d8a..799c42846b 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 b94e13a176..e3b5679318 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 1eda362e69..565e2911a9 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 dceb3ed770..052c0f7670 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 b70f0c6be3..cdd2fa3c6b 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)))); +} 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 f3c2cf3b31..92b79be35c 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(); 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 af8033ae91..822901be73 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 9413ea73a4..88c7e96943 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 f945dd6545..e20d6d60f5 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 71e3b473b2..6406458bf5 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 c826d76e03..27bedff47f 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"); } 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 278d66ab9f..3f898cc337 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 1620aa52df..8c567fa99f 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 0000000000..eec1154374 --- /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")); +} 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 822901be73..195b301840 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 6406458bf5..48e5db8f28 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 eec1154374..27fa144e54 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); } 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 bb4f955797..87960d4321 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 4bcc30a65e..a157c53491 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,32 +909,493 @@ 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 { try depender.dependencies.ensureCapacity(self.allocator, depender.dependencies.items.len + 1); try dependee.dependants.ensureCapacity(self.allocator, dependee.dependants.items.len + 1); @@ -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 eda9dcfc31..3c696cafa0 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 5660cd760f..7caacc7960 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 a00782771d..c1b547ce99 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)}); 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 | 948 +++++++++++++++++++-------------- 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, 866 insertions(+), 429 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 4d63011266..370f42b463 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 a157c53491..1dfca43c8d 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, - }, - .kw_args = .{}, + 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; }; - try self.analyzeNewDecl(new_decl, &fn_inst.base); + + 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, + }, + }; + 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 83a8f3c09f..48b2c04970 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 a3a15b463e..f412b2dad2 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 330b1c4135..387c88df3b 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 f394d45864..fd38bee863 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 aa8c000095..d8bc40a4f8 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 7caacc7960..fc5854c40f 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 c1b547ce99..90793c51f9 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 e20d6d60f5..75d126eaf6 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))) { 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 1dfca43c8d..f6c80c3d89 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 fd38bee863..012f543a7e 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.* = .{ 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 75d126eaf6..6c3fdddfb1 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); } 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 2a3ff9d9de..7a59ca0c38 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) { 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 f6c80c3d89..947495f0ae 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), 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 6c3fdddfb1..a0e49ccf9e 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"); } 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 d98ef71a59..a4d922f2ad 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 8fd27d6cfc..746b0ac91b 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); +} 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 df2e16a4bf..2c88d2ba0c 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)); } 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 eda9dcfc31..4db98c60dc 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); 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 d950a1cfa4..ec4059b9c7 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; } 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 262aa4872d..3ff4819ac5 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 ec4059b9c7..0a3c1b5ab7 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 4db98c60dc..6c13f8ab00 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); 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 4d71b0fb36..e2af4ba8ce 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 d98ef71a59..3a6585c90f 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 947495f0ae..508a9b2799 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 f412b2dad2..7473ccc431 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 0000000000..1e480d75b0 --- /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); +} 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 706c2b2378..63aeaecf88 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; } } 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 4a56293208..e810b36be2 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#} 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 e810b36be2..aaa15e91d0 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#} 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 23066a6963..cf0b21dc3a 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) }")); } 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 6c13f8ab00..3c81070439 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| { 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 | 89 ++++++++++++++++++++++++---------------- 3 files changed, 80 insertions(+), 47 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 3ff4819ac5..fa782b14c0 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 0a3c1b5ab7..f0304c332a 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 3c81070439..ee4cf9bbe3 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; - }; - 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(); - - 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; - }, + 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 read '{}': {}\n", .{ file_path, err }); + 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 = 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.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| { 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 d42a3c3d73..452ebe2124 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 198c69e2a7..f3fc8b8107 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 6b8ede5489..6f9efa9575 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 4acdbc7d1a..ae10dc9559 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 8b16f54361..1e36a72038 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 2a3ff9d9de..e6cd7a8b6d 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 }, }); 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 fa782b14c0..dfd58de500 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 ee4cf9bbe3..f322e455a8 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); 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 f322e455a8..29930270eb 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}); } } 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 dfd58de500..10422b9d54 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 }); 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 f0304c332a..160f8314b9 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, 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 953a16a2ea..1ed1ef1f54 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, 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 3af78e1857..4c28827ba8 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'); +} 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 4d71b0fb36..2b5863a2b9 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)); 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 4c28827ba8..410fa97c8f 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); 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 410fa97c8f..77d79ed98e 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); } 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 29930270eb..085e644d7f 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. 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 | 164 +++----------------------------- test/translate_c.zig | 16 ++-- 3 files changed, 118 insertions(+), 157 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index f93f035429..f27da1a512 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 3b1a91b28c..acce0e7f1d 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"), + //(@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, }; - if_1.condition = &cmp_1.base; - _ = try appendToken(c, .RParen, ")"); + 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 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, - }; - - 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; + 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 1c23afdb30..a4e2a33000 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) { 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 e20d6d60f5..f7c3575f86 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 635af397c4..bb6ca554df 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 3f898cc337..e3dd1f0d8f 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'" + }); } 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 bb6ca554df..3fa138ed8e 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 1faefe6800..8aa3bdb7c1 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(); 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 f27da1a512..6c10941aa7 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 acce0e7f1d..e492edb379 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"); 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 fe9fc7ac40..97d6bf5215 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 0558390b9e..16ab314c14 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 cc3b4f5741..d4624929ee 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(); 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 16ab314c14..59cde5d606 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 d4624929ee..c298a0a203 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" { 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 a65f6da3af..57b7888e19 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", 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 57b7888e19..520b219f97 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(); 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 ae10dc9559..6377b69a80 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| { 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 160f8314b9..cffc8cf87e 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 875565fc54..23a9c96ff5 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 03744e4da4..4c682e8aba 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; 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 508a9b2799..6da637cd58 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 90793c51f9..b885073db0 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, 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 6da637cd58..730954b431 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; 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 3fa138ed8e..01c7936f75 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 e3dd1f0d8f..e8b7e610ee 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) { 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 aaa15e91d0..8974e6d175 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 f08c74c129..fb6e6679fd 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -5,11 +5,37 @@ const Allocator = mem.Allocator; usingnamespace std.os.wasi; +/// Type-tag of WASI preopen. +/// +/// WASI currently offers only `Dir` as a valid preopen resource. +pub const PreopenTypeTag = enum { + Dir, +}; + /// Type of WASI preopen. /// /// WASI currently offers only `Dir` as a valid preopen resource. -pub const PreopenType = enum { - Dir, +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 @@ -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 2d136d56c9..117a788e16 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 }; 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 fb6e6679fd..db4317064d 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); } 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 | 195 +++++++++++++++----------- src-self-hosted/zir.zig | 278 +++++++++++++++++-------------------- 2 files changed, 242 insertions(+), 231 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 730954b431..5bb92fc26a 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 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); } 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 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.decl); - return self.analyzeDeclRef(scope, inst.base.src, decl); - } else { - unreachable; - } + 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 b885073db0..dec5793397 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; } }; 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 | 402 ++++++++++++++++++------------------- src-self-hosted/main.zig | 4 + 2 files changed, 200 insertions(+), 206 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 5bb92fc26a..13d1350566 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,9 +1021,12 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { } fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { - switch (decl.analysis) { + const tracy = trace(@src()); + defer tracy.end(); + + const subsequent_analysis = switch (decl.analysis) { + .complete => return, .in_progress => unreachable, - .outdated => unreachable, .sema_failure, .sema_failure_retryable, @@ -1033,29 +1035,73 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { .codegen_failure_retryable, => return error.AnalysisFail, - .complete => return, + .outdated => blk: { + //std.debug.warn("re-analyzing {}\n", .{decl.name}); - .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; - }, - }; + // 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 name_hash = scope.namespace().fullyQualifiedNameHash(name); 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 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 d4955c33fc..04febf26c5 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| { 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 0f27cee9ab..df33a5494e 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); 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 df33a5494e..0f27cee9ab 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); 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 | 296 ++++++++++++++++++++----------------- src-self-hosted/main.zig | 2 +- src-self-hosted/zir.zig | 14 +- test/stage2/zir.zig | 117 +++++---------- 4 files changed, 212 insertions(+), 217 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 13d1350566..9b3d1d23b5 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); - } + 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, {}); + } + } + + 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; } - }, - - .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(); - - // 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, {}); - } + } 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); } - - 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 (exports_to_resolve.items) |export_decl| { - _ = try self.resolveDecl(&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); + } + } + 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); - arena_state.* = decl_scope.arena.state; + var prev_type_has_bits = false; + var type_changed = true; - new_decl.typed_value = .{ + 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; + 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 04febf26c5..c12f3a86dc 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 dec5793397..a471d35c14 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 d58b30c29d..a22c770a9a 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! 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 ebb4c096f8..24792c7ca0 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 7403cca9c2..828d3165db 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 604c22101c..811e05012f 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 88c7e96943..4465bf674c 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 01c7936f75..b765ea09ff 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 27bedff47f..0acde512f6 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; 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 b765ea09ff..5f44e5306b 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 e8b7e610ee..90c970c842 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 30df7f64aa..4214ed84d2 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 { 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 2e50c3a84c..e60d115a00 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; 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 8974e6d175..95b7171c44 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); 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 fdf9d92d7d..388d6db044 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 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 ece3bbde11..8ac5181cab 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 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 59cde5d606..aa85f9483b 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 c298a0a203..34747518e4 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; 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 97d6bf5215..d4c70a9fe4 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 f827eb6863..2426638569 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; 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 aa85f9483b..99d66db2bb 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"); 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 542fbb56ce..699b121276 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 41301f290d..68ff3aa310 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); + } +} 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 9b3d1d23b5..dfefd3e6ba 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); 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 | 39 +++-- 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, 221 insertions(+), 264 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index dfefd3e6ba..89dcac3f41 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,6 +1782,9 @@ 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. @@ -1782,9 +1794,6 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { try self.deleteDecl(kv.key); } } - for (exports_to_resolve.items) |export_decl| { - _ = try self.resolveZirDecl(&root_scope.base, export_decl); - } } 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 012f543a7e..c6acf21b84 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 c12f3a86dc..aa5a56010c 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 4cf72ce481..a7942575d0 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 1e480d75b0..6f56a87ce6 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 d8bc40a4f8..fb97186648 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 a471d35c14..43c0eac197 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 43c41aa364..7596894dca 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 a22c770a9a..832629c6e7 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{""}, ); 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 | 289 +++++++++++++++------------------ test/stage2/compare_output.zig | 137 +++++++++++++--- test/stage2/zir.zig | 200 +++++++++++------------ 5 files changed, 369 insertions(+), 297 deletions(-) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 87960d4321..841827cc19 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 aa5a56010c..3743b4f334 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 a7942575d0..14804cab68 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 { } } }, + .Execution => |expected_stdout| { + var exec_result = x: { + var exec_node = update_node.start("execute", null); + exec_node.activate(); + defer exec_node.end(); - else => return error.Unimplemented, - } - } - } + try module.makeBinFileExecutable(); - 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 exe_path = try std.fmt.allocPrint(allocator, "." ++ std.fs.path.sep_str ++ "{}", .{bin_name}); + defer allocator.free(exe_path); - 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; + 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 1f289c2762..182ff8131a 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 832629c6e7..673be6d99f 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") + , ""); } 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 62681968a6..a702a8aed6 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; 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 14804cab68..cd53eaeb68 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 182ff8131a..4332ed120c 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 7596894dca..4f4ec611db 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 dc92f99506..e0ef291588 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 673be6d99f..a3dec10e73 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) 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 cd53eaeb68..3f7ff3303a 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 4332ed120c..902c1e493f 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 4f4ec611db..1ee8e9184b 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 a3dec10e73..17d5ce9b5b 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) 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 01c7936f75..3e2b75822e 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 8c567fa99f..a15f1f26b9 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 0000000000..5ef533a761 --- /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); +} 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 3e2b75822e..20067b74d1 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 e8b7e610ee..79ecaee7c1 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'", }); } 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 10422b9d54..6cb7d478b2 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), } 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 23a9c96ff5..b024f0f4f6 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; 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 8f961f80f2..8c5d75f80a 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 255c98e409..0eb3a25fe1 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 5a82ec81ee..87f9f9a1e7 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", .{}); 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 | 256 +++++++++++++++++++++------ 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, 844 insertions(+), 99 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 89dcac3f41..c0c52d2ce6 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.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() == .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 == .Unspecified) - { - return self.constType(scope, fntype.base.src, Type.initTag(.fn_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); } - 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.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)); - } - - 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 7473ccc431..686edda373 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 387c88df3b..cb9c7b5e11 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 fb97186648..2a02d0d89b 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 fc5854c40f..6509ee52f6 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 43c0eac197..92dbc66e2b 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), 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 3f7ff3303a..89532378a5 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 902c1e493f..d49f16876e 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 1ee8e9184b..905f106f94 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 17d5ce9b5b..052ada667e 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) 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 b024f0f4f6..87477549d9 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; 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 89532378a5..13f08d86e2 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) }; 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 89dcac3f41..63cca38973 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); 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 63cca38973..600da10d93 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 905f106f94..79b67da2c2 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 {} 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 600da10d93..134693b6fd 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 79b67da2c2..f7766abcef 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"}); 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 134693b6fd..8ac4e89807 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 f7766abcef..1184048cbb 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; 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 13f08d86e2..cb1a9f6981 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 1184048cbb..bfe4bf7d48 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 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 | 91 +++++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 25 deletions(-) diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index cb1a9f6981..fd3d772c98 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,6 +301,33 @@ pub const TestContext = struct { ctx.addObj(name, target, T).compiles(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, + target: std.zig.CrossTarget, + src: [:0]const u8, + ) void { + 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, + target: std.zig.CrossTarget, + src: [:0]const u8, + ) void { + 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, @@ -287,6 +341,11 @@ pub const TestContext = struct { 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, @@ -300,24 +359,6 @@ pub const TestContext = struct { case.compiles(fixed_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) }; 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 fd3d772c98..af9ffef509 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( 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 916f2e16f3..e7ed9b746d 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 ``` 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 | 602 +++++++++++------------ lib/std/heap/arena_allocator.zig | 29 +- lib/std/heap/logging_allocator.zig | 61 ++- lib/std/mem.zig | 323 +++++++++--- 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, 630 insertions(+), 467 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 452ebe2124..896e5bf9dc 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 97d6bf5215..57e6de65a3 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 f05378c215..322c24934e 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; -var c_allocator_state = Allocator{ - .reallocFn = cRealloc, - .shrinkFn = cShrink, +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; }; -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]; +pub const c_allocator = mem.getAllocatorPtr(&c_allocator_state); +var c_allocator_state = Allocator{ + .allocFn = cAlloc, + .resizeFn = cResize, +}; + +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 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]); + const dropLen = aligned_addr - @ptrToInt(slice.ptr); + if (dropLen != 0) { + os.munmap(slice[0..dropLen]); } - return @intToPtr([*]u8, aligned_addr)[0..n]; + // 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..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 == buf_unaligned.len) { + return alignPageAllocLen(new_size_aligned, new_size, len_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]; - } - 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); - - return new_alloc_mem; - }; - - assert(@ptrToInt(realloc_addr) == old_addr_end_rounded); - return @ptrCast([*]u8, old_mem.ptr)[0..new_size]; + // new_size > buf_unaligned.len not implemented + return error.OutOfMemory; } - if (new_size <= old_mem.len and new_align <= old_align) { - return shrink(allocator, old_mem, old_align, new_size, new_align); + + 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); + + 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 { - const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); - if (n == 0) return &[0]u8{}; + fn getRecordPtr(buf: []u8) *align(1) usize { + return @intToPtr(*align(1) usize, @ptrToInt(buf.ptr) + buf.len); + } - const amt = n + alignment + @sizeOf(usize); + fn alloc(allocator: *Allocator, n: usize, ptr_align: u29, len_align: u29) error{OutOfMemory}![]u8 { + const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); + + 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. - 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; - } - } + assert(self.ownsSlice(buf)); // sanity check - fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - return old_mem[0..new_size]; + 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; + } + 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 b41399772a..ee3bfc19d4 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 0d15986a76..e465795b8f 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 b942fd3bf4..6a7e846f62 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,15 +392,106 @@ pub const Allocator = struct { } }; -var failAllocator = Allocator{ - .reallocFn = failAllocatorRealloc, - .shrinkFn = failAllocatorShrink, -}; -fn failAllocatorRealloc(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { - return error.OutOfMemory; +/// 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; } -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"); + +/// 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{ + .allocFn = failAllocatorAlloc, + .resizeFn = Allocator.noResize, +}; +fn failAllocatorAlloc(self: *Allocator, n: usize, alignment: u29, len_align: u29) Allocator.Error![]u8 { + return error.OutOfMemory; } test "mem.Allocator basics" { @@ -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 191e8deded..a6a9e50d53 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 117a788e16..0f811cada8 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 081a29cd97..ade3e9d85a 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 65244e529b..87564aeea7 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 { 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 6cb7d478b2..c574737194 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, }; } 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 c574737194..e7de8bd52d 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"); 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 e7de8bd52d..28f2daa96a 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; }, 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 b024f0f4f6..1220d96b5b 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 34747518e4..e508f5ae20 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; 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 8ac4e89807..b15f1d8822 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 bfe4bf7d48..b324cdf5d5 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'"}, 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 896e5bf9dc..b57d051d2b 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 322c24934e..70e0e9d538 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 ee3bfc19d4..74ced774ed 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 e465795b8f..b521515a79 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 6a7e846f62..bf1e000056 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 0f811cada8..670d8fd5d6 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 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 af9ffef509..248792b42c 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) { 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 b15f1d8822..e63dc51eec 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 b324cdf5d5..45e60c0741 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'"}); - // 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("function redefinition", linux_x64, + // \\fn entry() void {} + // \\fn entry() void {} + //, &[_][]const u8{":2:4: error: redefinition of 'entry'"}, + // \\fn entry() void {} + //); - ctx.incrementalFailure("missing function name", linux_x64, - \\fn() void {} - , &[_][]const u8{":1:3: error: missing function name"}, - \\fn a() 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 {} + // ); // TODO: re-enable these tests. // https://github.com/ziglang/zig/issues/1364 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 248792b42c..398d988eeb 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); 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 398d988eeb..38392b8aa7 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(); 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 e63dc51eec..a59aacc226 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( 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 a59aacc226..ffaff9bc9d 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 => { 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 70e0e9d538..260841ad2d 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 { 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 87477549d9..3991ddd8a3 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) { 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 260841ad2d..ea9e95c675 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 bf1e000056..6bde83f782 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| { 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 74ced774ed..31fbad57f9 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); } }; 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 | 307 ++++++++++++++++++----------- src-self-hosted/codegen/x86_64.zig | 30 ++- src-self-hosted/type.zig | 70 +++++++ 3 files changed, 291 insertions(+), 116 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 686edda373..ccd0da8ae0 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 { - 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 = {} }, - .constant => unreachable, // excluded from function bodies - .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), - .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).?), + 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 genAdd(self: *Function, inst: *ir.Inst.Add) !MCValue { - switch (self.target.cpu.arch) { + 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).?, 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).?, 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), + } + } + + 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 0326f99b99..8bf131dbdd 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 2a02d0d89b..caacf4e7fc 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, 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 ccd0da8ae0..73758bda87 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 8bf131dbdd..ddcbd5320e 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 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 31fbad57f9..74ced774ed 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); } }; 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 74ced774ed..a5d8aaea45 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, 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 6cb7d478b2..08e77347da 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 99d66db2bb..201cca9788 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), } } 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 57c1dfb945..20293daecb 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 dd22a42304..54d4673f64 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 08e77347da..fe2e7338c8 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 201cca9788..a7e29555d6 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 bd24ffb399..60076b9cdb 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; } 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 a7e29555d6..a1b3665845 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 64c9401dbc..166dbf4c43 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, 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 | 151 ++++++++++++++++--------------------- lib/std/zig/system.zig | 5 +- src-self-hosted/stage2.zig | 3 - 6 files changed, 69 insertions(+), 114 deletions(-) diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 20293daecb..57c1dfb945 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 54d4673f64..b6609d8b31 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 fe2e7338c8..caa414ac17 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 a1b3665845..1e1049ae51 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,6 +653,9 @@ pub const WriteError = error{ FileTooBig, InputOutput, NoSpaceLeft, + + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to write to it. AccessDenied, BrokenPipe, SystemResources, @@ -662,10 +664,6 @@ 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. @@ -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 166dbf4c43..2b32e39624 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 60076b9cdb..bd24ffb399 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; } 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 fd513de54f..3daef75a58 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{ 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 95b7171c44..aef7b09798 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.

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 aef7b09798..dfbb93decf 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#}

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 6bde83f782..08ecc5167f 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, }); } 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 69e5568093..45440d61e7 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) + 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); + } + + 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) { - int index = (start_index + roll_over) % _capacity; - Entry *entry = &_entries[index]; - - 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; + 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 68ff3aa310..2685a3552e 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" { 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 52f823a32f..ea08b8f064 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); 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 ea08b8f064..ddf4a28213 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 '..' 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 e492edb379..171846d380 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 c7f7c2c5c6..efdc9702a4 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; + \\} + , ""); } 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 ddf4a28213..a3cf2e8002 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 }); } 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 45440d61e7..ce5369eac8 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 2c6d90c855..803a251437 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 { 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 ce5369eac8..8681e5b761 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); 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 94219c1631..6deeafd235 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 e7ed9b746d..5bc59840d6 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. 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 5bc59840d6..ddafd68057 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 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 6deeafd235..4679185573 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 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 3daef75a58..f9cd1b0dea 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); 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 df1dc6d73a..dc8487cc09 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 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 e8bc735b57..71d8a218fa 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 dc8487cc09..46d4d4baae 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 d160c4ebb2..257f407826 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 e932253bdb..3c0ae8f265 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 34326f2872..2176498feb 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 8c5a983735..b161e45c71 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 ba929a446c..571e746631 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 6377b69a80..20eed05737 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 9379f881db..85e14bc55c 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 229731b617..a9dcf53f92 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 dfd2379da2..69b9e06a5b 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 520b219f97..1a0dfad474 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 1909a07df0..910d263c6a 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 2b32e39624..898d376cc7 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); } 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 7a61cd5ccd..da5e575587 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 { 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 da5e575587..5fc1f7d67e 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 92dbc66e2b..7b77575f55 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 { 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 d49f16876e..1f2853707a 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", + ); + } } 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 18ef7d9182..2f72861bc2 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 e60d115a00..e27ae7e16e 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")) { 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 ff94455846..8a2896fcb6 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 eb731ee898..a72c13e233 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 7d519c34cb..ba53d1dee0 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 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 | 1112 ++++++++++++++++++++++++++++------------ 3 files changed, 793 insertions(+), 337 deletions(-) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index b57d051d2b..a68c1fa9d6 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 92b79be35c..e9bafec94c 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 bcd4280153..4b91a83ba2 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; + } + + /// Deprecated. Iterate using `items`. + pub fn iterator(self: *const Self) Iterator { + return Iterator{ + .hm = self, + .index = 0, + }; } /// 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. + /// `Entry` 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. + /// the `Entry` pointer points to it. Caller should then initialize + /// the value (but not the key). 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, - }; + return self.unmanaged.getOrPut(self.allocator, key); } - 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; + /// 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); } - 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; + pub fn getOrPutValue(self: *Self, key: K, value: V) !*Entry { + return self.unmanaged.getOrPutValue(self.allocator, key, value); } - /// 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); + /// 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); } - /// 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 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(); } - /// 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); + /// 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); } - /// Calls put() and asserts that no kv pair is clobbered. + /// 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 { - assert((try self.put(key, value)) == null); + return self.unmanaged.putNoClobber(self.allocator, key, value); } - 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; + /// 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 { - assert(self.putAssumeCapacity(key, value) == null); + return self.unmanaged.putAssumeCapacityNoClobber(key, value); } - pub fn get(hm: *const Self, key: K) ?*KV { - if (hm.entries.len == 0) { - return null; - } - return hm.internalGet(key); + /// 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); } - pub fn getValue(hm: *const Self, key: K) ?V { - return if (hm.get(key)) |kv| kv.value else null; + /// 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 contains(hm: *const Self, key: K) bool { - return hm.get(key) != null; + pub fn getEntry(self: Self, key: K) ?*Entry { + return self.unmanaged.getEntry(key); } - /// 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; + pub fn get(self: Self, key: K) ?V { + return self.unmanaged.get(key); } - /// 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 contains(self: Self, key: K) bool { + return self.unmanaged.contains(key); } - pub fn iterator(hm: *const Self) Iterator { - return Iterator{ - .hm = hm, - .count = 0, - .index = 0, - .initial_modification_count = hm.modification_count, - }; + /// 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 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; - } - } - - fn incrementModificationCount(hm: *Self) void { - if (want_modification_safety) { - hm.modification_count +%= 1; - } - } - - 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); + 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; - 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) : ({ - roll_over += 1; - distance_from_start_index += 1; - }) { - const index = self.constrainIndex(start_index + roll_over); - const entry = &self.entries[index]; + 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; - 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; - } - 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; - } + const entry = &self.entries.items[index.entry_index]; + + 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); } - 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 - } - - 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]; - - if (!entry.used) return null; - if (eql(entry.kv.key, key)) return &entry.kv; + // 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 keyToIndex(hm: Self, key: K) usize { - return hm.constrainIndex(@as(usize, hash(key))); + 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; } - 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); + /// 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; + 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 = 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, + }; + } + + // 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; + } + } + unreachable; + } + } + unreachable; + } + + 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; + + 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 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 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 { 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 e8bc735b57..651561b374 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 89df0478ff..d8a0264bd7 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 df1dc6d73a..5619bc5fe6 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 4b91a83ba2..d0b18d9419 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 ba929a446c..1c48a07819 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 6377b69a80..a8b19756da 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 3743b4f334..33f422692c 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 171846d380..261cef37b5 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 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 d0b18d9419..0fe8ae34f1 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) { 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 7886c7cc90..e2acfae768 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 dfbb93decf..d7b9de3c6e 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 e9bafec94c..e6d0c17da4 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 0fe8ae34f1..aaec9a4d58 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 7a61cd5ccd..0c80803fc7 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 73758bda87..8885ed2825 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 c6acf21b84..c615ad35fd 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 92dbc66e2b..7dceaaea1b 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 { 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 cf0b21dc3a..e09775ba95 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" { 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 9df3e21e52..8edc4acea8 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 910d263c6a..5466b39f0b 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 bd24ffb399..91524d7396 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"); 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 8edc4acea8..110b7a088f 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, 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 e09775ba95..2674ba485a 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; 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 f4d65ab7c0..4c83076cad 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 8885ed2825..eea3de7f36 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 cb9c7b5e11..6fa06c8138 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 0000000000..65f81bbf4a --- /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 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 a68c1fa9d6..d667bc4d17 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"); + } +} 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 aaec9a4d58..a9c30aba87 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 0961991ea6..be4aad5f0d 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; 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 4c83076cad..4240a1c6c8 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 eea3de7f36..e364763b0a 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 6fa06c8138..17e6a54725 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 c615ad35fd..a0894fb1a1 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 65f81bbf4a..2e0aad49c3 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 33f422692c..82edcc4eff 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| { 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 | 299 +++++++++++++++++++++++++----------- src-self-hosted/ir.zig | 14 +- src-self-hosted/zir.zig | 30 ++++ 4 files changed, 255 insertions(+), 93 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 4240a1c6c8..776e63018b 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 e364763b0a..aac1a53d6a 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 = {} }, - .constant => unreachable, // excluded from function bodies - .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).?, 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), + .constant => unreachable, // excluded from function bodies .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).?), + .ret => return self.genRet(inst.cast(ir.Inst.Ret).?, arch), + .retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?, 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 17e6a54725..c8acc14545 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 95548657a9..628773e09c 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); 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 aaec9a4d58..05550a289a 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 { 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 38392b8aa7..ae5cecce20 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. 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 | 35 ++++++++++++++++++++--------------- src-self-hosted/main.zig | 5 ++++- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 776e63018b..6cd64e9235 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 a0894fb1a1..c297c3a1ce 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 2e0aad49c3..28eb2145c7 100644 --- a/src-self-hosted/liveness.zig +++ b/src-self-hosted/liveness.zig @@ -25,24 +25,27 @@ fn analyzeWithTable(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, 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; + try analyzeInstGeneric(arena, table, 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 => {}, + } + } + 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; @@ -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 82edcc4eff..7a47fc5f78 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), } ++ "): "; 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 6cd64e9235..d40afebb64 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 aac1a53d6a..d1b32f4ed4 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 c8acc14545..81b617c8a2 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 628773e09c..8cc6cdaf05 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); 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 | 2467 ++++++++++++++++++----------------- src-self-hosted/test.zig | 25 +- test/stage2/cbe.zig | 18 + test/stage2/test.zig | 1 + 6 files changed, 1356 insertions(+), 1219 deletions(-) create mode 100644 test/stage2/cbe.zig diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index f4d65ab7c0..ac7bd00170 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 8885ed2825..46715b762b 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 c615ad35fd..9e273a2b9e 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, + allocator: *Allocator, + file: ?fs.File, + owns_file_handle: bool, + options: Options, + ptr_width: enum { p32, p64 }, - pub const empty = TextBlock{ - .local_sym_index = 0, - .offset_table_index = undefined, - .prev = null, - .next = 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, - /// 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| { + /// 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 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 makeExecutable(self: *ElfFile) !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), - }); - } - - /// 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; - - 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.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.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; - } - - 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; - } - 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; - } - 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; - } - 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.?]; + pub const Export = struct { + sym_index: ?u32 = null, + }; - 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.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"); + 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(); } - // 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: *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), - }; - 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); - - 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; + pub fn makeExecutable(self: *File.Elf) !void { + assert(self.owns_file_handle); + if (self.file) |f| { + f.close(); + self.file = null; + } + } - if (needed_size > allocated_size) { - shstrtab_sect.sh_size = 0; // free the space - shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1); + 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: *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; + + 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; } - 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.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.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; } - if (self.shdr_table_dirty) { + + 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; + } + + 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; + } + + 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); + } + + 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)); + } + + 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; + } + return self.makeString(new_name); + } + + pub fn populateMissingMetadata(self: *File.Elf) !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; + } + 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; + } + 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; + } + 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), @@ -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 (needed_size > allocated_size) { - self.shdr_table_offset = null; // free the space - self.shdr_table_offset = self.findFreeSpace(needed_size, shalign); + 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); + + 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(); + + 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); } - // 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; - } - } - - 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; - } - 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; } - }; - 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: { + 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; + } + } + + 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.?]; - - 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; - } - - 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.local_symbols.items[decl.link.local_sym_index].st_info = 0; - - decl.link.local_sym_index = 0; - } - } - - 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; - - //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); + 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; } - } 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 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 = vaddr, - .st_size = code.len, }; - 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); - } - - 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); - } - - /// 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; + 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; + + 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 } - 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, - }; + + // 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 { - 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, + 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: *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(); + } + + 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.?]; + + 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; + } + + 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); + + self.local_symbols.items[decl.link.local_sym_index].st_info = 0; + + decl.link.local_sym_index = 0; + } + } + + 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); + } + } 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 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); + } + + /// 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]; + + 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; + } + } + 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); + } } } - } - 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; - } - - 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]); - } - 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, + 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 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, - } - } - - 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 - - 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); - }, - } - } - 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 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, + } + } + + 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); + }, + } + } + + 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 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 ae5cecce20..a1dd659e75 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 0000000000..884f5c927f --- /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 e0ef291588..f5acc72f93 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); } 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 9e273a2b9e..e37f567d92 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 a1dd659e75..ce20c172cf 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 884f5c927f..a599367aec 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) {} + ); } 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 ce20c172cf..0a4e9708ac 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); } } 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 0000000000..f06f9df6ac --- /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 e37f567d92..98133a4f42 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 0a4e9708ac..153c77ce47 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) { 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 f06f9df6ac..48b725c988 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 153c77ce47..9e7a7bbf50 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 a599367aec..86f966af82 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) {} + \\ ); } 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 e932253bdb..f8777f28e4 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; 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 9e7a7bbf50..80e40900a0 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); } } } 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 48b725c988..99828ee362 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 98133a4f42..c47e1597c6 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 86f966af82..88eb6c5953 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) {} \\ ); } 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 | 21 +++++++-- test/stage2/cbe.zig | 51 +++++++++++++++++++++ 3 files changed, 154 insertions(+), 15 deletions(-) diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 99828ee362..5b04d98119 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 c47e1597c6..8352f64fca 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(); - if (self.header.items.len > 0) { - try self.header.append('\n'); + 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 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 88eb6c5953..9a28c25e27 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; + // \\} + // \\ + //); } 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 33f422692c..fdbc5fc1ae 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}); } } 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 ac7bd00170..2ef85c4d50 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 0000000000..85b07eb48a --- /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 5b04d98119..872fa3e830 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 8352f64fca..0efab9657f 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 fdbc5fc1ae..fc02a57cc8 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 80e40900a0..6eca7ab651 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 9a28c25e27..e56507bd84 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(); // \\} // \\ 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 699b121276..afe0fe6849 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 adf92800fe..611094c050 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(); 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 0efab9657f..6625598d11 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 fc02a57cc8..3d859483a5 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 6eca7ab651..0a32008369 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, 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 6625598d11..297b3d8800 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 { 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 2ef85c4d50..9df02dd186 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 297b3d8800..da01282995 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(), 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 872fa3e830..0de28748c6 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 da01282995..0683a741c5 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; }, }; 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 3d859483a5..518be8c023 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, }; 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 85b07eb48a..66e7b8bd3e 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 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 | 126 +++++++++++++++++++++++++++--------- src-self-hosted/ir.zig | 3 + src-self-hosted/zir.zig | 19 ++++-- 4 files changed, 110 insertions(+), 43 deletions(-) diff --git a/lib/std/math.zig b/lib/std/math.zig index 799c42846b..14ffd61c29 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 d1b32f4ed4..b3fa94f088 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); - } + 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); + + 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); }, - 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 { - // 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 { + // 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 81b617c8a2..f2ba3801a1 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 8cc6cdaf05..0f36ac99f3 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -1589,11 +1589,9 @@ const EmitZIR = struct { 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); - + // 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, @@ -1601,10 +1599,17 @@ const EmitZIR = struct { }, .positionals = .{ .label = try self.autoName(), - .body = .{ .instructions = block_body.toOwnedSlice() }, + .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() }; + 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); } } 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 0f36ac99f3..697c7673ac 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 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 d40afebb64..0bd917b2b1 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 b3fa94f088..232703b697 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 697c7673ac..ab48bf535f 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}), }, } 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 0bd917b2b1..67c1b00a1e 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 ab48bf535f..0221e4d330 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, 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 9df02dd186..733aac7c71 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 0de28748c6..75caddc131 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 0683a741c5..2f21dcbfde 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 e56507bd84..5961ab4c0c 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 052ada667e..1a0aed85cf 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) 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 2f21dcbfde..98a2578822 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 0a32008369..3e4958cc95 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!"); 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 d7b9de3c6e..1e4d993fe9 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#}
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 1e4d993fe9..032e05d600 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#} 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 b313a76161..a55de24496 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 032e05d600..78dcd537b8 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.

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 78dcd537b8..f64170817f 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#} 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 | 190 +++++++++++++++++++++++------- src-self-hosted/codegen.zig | 13 ++- src-self-hosted/ir.zig | 19 ++- src-self-hosted/zir.zig | 225 +++++++++++++++++++++++------------- 5 files changed, 319 insertions(+), 130 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 370f42b463..72d8f3560e 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 67c1b00a1e..328b043153 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,7 +2320,22 @@ fn newZIRInst( .positionals = positionals, .kw_args = kw_args, }; - return &inst.base; + return inst; +} + +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, +) !*T { + const gen_zir = scope.cast(Scope.GenZIR).?; + 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( @@ -2260,11 +2346,8 @@ fn addZIRInst( 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; + 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. @@ -2273,6 +2356,12 @@ fn addZIRInstConst(self: *Module, scope: *Scope, src: usize, typed_value: TypedV 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 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); +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 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 232703b697..e2c5c989ac 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 f2ba3801a1..1de7c626ea 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 0221e4d330..2a4db02c19 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 { - // First, build a map of *Inst to @ or % indexes - var inst_table = InstPtrTable.init(allocator); - defer inst_table.deinit(); + 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(); - try inst_table.ensureCapacity(self.decls.len); + // First, build a map of *Inst to @ or % indexes + 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); + 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.* = .{ .base = .{ .src = inst.src, .tag = Inst.Block.base_tag, }, .positionals = .{ - .label = try self.autoName(), - .body = undefined, + .body = .{ .instructions = block_body.toOwnedSlice() }, }, .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() }; - + 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 = .{}, }; 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 828d3165db..fd8c068f05 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 670d8fd5d6..bdaf759d62 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 { 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 f64170817f..cf9e485e8a 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#} 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 a45455ad39..81b7f14f84 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 77cdacc307..4325b0b9e4 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); 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 81b7f14f84..a45455ad39 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 4325b0b9e4..77cdacc307 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); 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 645789a04b..cfb75f077e 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); } 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 cf9e485e8a..0f17f4c096 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 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 0f17f4c096..c84edbc6a2 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. 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 c84edbc6a2..6821dfd9f9 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 ,.

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 6821dfd9f9..d181844e90 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.

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 a45455ad39..ca0fd47c49 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 611094c050..df2e759bf1 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 77cdacc307..4b678cd2db 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); 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 d181844e90..51e7902c8c 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#} 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 72d8f3560e..e95a8855af 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 5a34c0ff91..4f8eff3c68 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 1a30e46ee0..3150d6a3f4 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 3bf0d350cf..7f9c6f6288 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", 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 f64170817f..015865ee3b 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 4465bf674c..a73efe2c82 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 afe0fe6849..5eba515e68 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 49fad80a40..ad308bf416 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 2f72861bc2..3473a2b0ac 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 ca0fd47c49..6447db8c71 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 c6c5823672..fcd1133b0d 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 f09a146f2b..487a125d62 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 552ded4ef8..d8af21ee00 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -54,6 +54,7 @@ enum TokenId { TokenIdKeywordAllowZero, TokenIdKeywordAnd, TokenIdKeywordAnyFrame, + TokenIdKeywordAnyType, TokenIdKeywordAsm, TokenIdKeywordAsync, TokenIdKeywordAwait, 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 2685a3552e..9da1da6e87 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")); +} 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 110b7a088f..f20bec421e 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 5c49d4acfc..9af95dfada 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 962e3073cf..4b3f83bbc3 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 aab8e9d068..90b060c03f 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 4d0da9b2c3..af4f5ba5be 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 6b548ac031..ddb12d3d85 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 b0558908e3..f429099d88 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 21211ae20e..fc95b2dee8 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 e1b858341f..947137b3e2 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 6a79aea1da..d719a6bb71 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 c06b82f02a..ffea7344fc 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 ff8921eaf2..dbdb107024 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 3ec6cc7c20..e1cbc845fc 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 453ce8887f..f065a8b169 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 066282f3c6..72b2b6d431 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 5eccd61c7e..bfcd1abc1a 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, -}; 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 f9cd1b0dea..e4ed04ea16 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 d667bc4d17..4d8cdc200c 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 b83cc4ad62..828be7462f 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 d6d0b70754..880af37ef4 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 2d5ec4bd91..19de76b00d 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 746b0ac91b..058a4a64ff 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 0c7e534bed..5eafc4e409 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 28d4157d6a..e483b5b50f 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 bb8c01f138..467050d57d 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 257f407826..acaa5edc8d 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 3021f6bc1e..8cc5cac130 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 8c5d75f80a..f0f40bd231 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 1ff326cf39..61260c7e39 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 e6d0c17da4..3346598ab7 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 16a23da123..8149554246 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 24792c7ca0..1400442247 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 b6609d8b31..98508df190 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 61130b32cb..0dc6550218 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 2674ba485a..7415b1b520 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 db4317064d..4a897c78f7 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 a33b23354b..a3e1a390c2 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 0eb3a25fe1..5f8a15831c 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 a717303090..73b94acbd2 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 96efc8b9c1..effa13ad69 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 ea9e95c675..ba96257557 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 b521515a79..d3055c75ee 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 9310dac348..f5465d4151 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 d5e8ce934f..fbdf7fbe78 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 bdf9156136..7c1d3e5dba 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 f33dc127d2..73d74b465f 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 5cd102b510..a970f899d6 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 90e4580eea..c0cd53c7ee 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 ee5fe48ca5..32625f3b7a 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 02ed75eaaa..e63940bff7 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 2bf6b83bc5..08e940c6ec 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 8c63b8b966..8fe0782c84 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 659ba2703e..a98e3b1acd 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 65ebe55072..f1b91fc829 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 dcfbf04bc1..778173cc24 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 63aeaecf88..0006580031 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 14ffd61c29..111a618cef 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 aec0d4706a..cdd86601fd 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 0f99335058..9a594f9cc4 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 db57e2088f..4cff69fc1b 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 ab1b650139..940b953d06 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 eb9154b5fe..9342b6ed59 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 e58a10fff5..de742bd4cd 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 85e14bc55c..b6d7731f1a 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 3624a16139..6f62a462b8 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 2b219d5368..42163b96dc 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 e3b5679318..39de46f361 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 75b967f3d2..db31aef42a 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 24a645375c..072fd77f08 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 996334034a..59117a8b27 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 f690e92143..6cf959a081 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 01fa33156a..9f7cd396aa 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 47d8244adb..0c3c2bd115 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 5ba6f7b0d2..98bde3e125 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 8b70306224..a07c2969e4 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 1065d4bb73..42a34e7dfc 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 1aefa73db5..9daf89c730 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 a9ac893602..bd51629bd4 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 9f9e3db807..6f6061a947 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 9eccd4bb98..c23b9b346e 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 f1fad3175e..ec02c6c325 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 349f6b3abb..e208ae0370 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 87dc57911b..1b10f8fca6 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 7dd880c71c..32f2a730fb 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 57e73f6cd1..0edb02a7a9 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 70304803db..050898c573 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 afd2e6aee4..1d614cca58 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 aa336769b1..df5c0a53be 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 1cd8c5f27f..bab47dcdbd 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 da80b201c0..c84d929adf 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 411f789187..da391189b2 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 91752e9f80..80cdefae20 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 e70e365f26..f404570fb6 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 a659e35ca2..ca91f594fd 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 565e2911a9..3a71cc7cdf 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 cfdf9f838d..0e4558dc37 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 22e3fbaa97..748cf9ea0d 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 26b3ce54a1..0681eae0b7 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 6eacab52ad..19357d89d1 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 ac865f0d0c..797c115d1d 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 917b4ebfcf..a3144f2784 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 555a786907..99e54c4cc7 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 7367af28c6..e55bd8c1e8 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 5e92cfdea3..e24ba8d84d 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 54f8bc2baa..95d06a2b60 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 6fd89e3dda..5ab5318a79 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 052c0f7670..854adee4ba 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 bab109f334..71a8110ce7 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 9cb62b5042..49397f7bd4 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 e88f5eeeaf..df3b294ca6 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 0e2cb5a3d5..26e0e05f38 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 097b0152f7..2f0d251432 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 86f473f448..2cd5a407df 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 1cad399729..7697db5271 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 cdd2fa3c6b..df24b77111 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 08ecc5167f..82831787a7 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 6c10941aa7..2827cfecf4 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 11aa8457ee..5cea0ecb9a 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 a9dcf53f92..71bab383fa 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 1e1049ae51..dfb47208ca 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 1d2f88794d..c037075cd8 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 d80f8c4423..b81e81aa2c 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 d087e480f6..d39fe3e239 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 cb6162e9b0..464054e4a5 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 1c88f98e6e..83c181e841 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 fd8c068f05..301457dde0 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 110b7a088f..0b95e8f75a 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 bdaf759d62..44c221d76a 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 d07c41c5b0..3d20f54558 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 e95a8855af..03153f541a 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 4f8eff3c68..29b60c0cee 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 cc6030ad15..f7ceee16ac 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 898d376cc7..af494efbab 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 a6508943df..b0678ea665 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 7ed84d540c..ba91e0726f 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 cad12834a7..20324cbf0c 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 1de7c626ea..094b877b53 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 dfc0f1235a..65c6c8c16d 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 2fb14ab690..b664d93353 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 28eb2145c7..797a55a80c 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 ee60b600a4..c0ac42c845 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 f84a4a2cbc..34eda71ccf 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 261cef37b5..396ee09ef9 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 caacf4e7fc..82c7cfa607 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 6509ee52f6..c1e9a38bd1 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 2a4db02c19..8faf248636 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 4214ed84d2..807e4c6275 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 009f4544ba..2a86044dc1 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 ab32a22cf3..1034a256d3 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 f09127a66f..bf6e1f5207 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 0196af1748..98ab7bd155 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 ab0c01417a..8337712ea5 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 23b2513f17..9d0f028e57 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 3794a965c6..ec3d18a532 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 40b5be4cd3..4d05a83a39 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 b6cb86a363..765828f5ce 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 def7fd679d..975e08b04f 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 2af34eaf7e..17d5aafe06 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 c1e5459378..c9f7477ecf 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 a5d2f9dabe..6b584e381d 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 0003bb86e1..1dc33eb8ea 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 7b8690d604..2d83bd23bb 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 0b2fdfe4e0..299abec3c9 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 2685a3552e..48263dba6f 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 8555e712dd..da898347b9 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 0c8674a7fb..eae8f8f888 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 851074e0d1..8bdffcd500 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; 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 c8e7abd5cb..da06d93b96 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; 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 03153f541a..7cb4936444 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 29b60c0cee..f5f0c10826 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 3150d6a3f4..f24886c0ab 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 396ee09ef9..4ae2d74336 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 a4e2a33000..738f9523ad 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; \\} }); 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 e2acfae768..af4d2530d0 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 7415b1b520..7e288170af 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 8fe0782c84..317dde6417 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 0006580031..3fb75b7e37 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 7cb4936444..0502f5bb64 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 b0678ea665..d173bd36e8 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 5eba515e68..67c900507d 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 df2e759bf1..fb4428fbc6 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 { 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 23f70d6202..02ba7f0d31 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -github: [andrewrk] +github: [ziglang] 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 fcd1133b0d..26233ec6a4 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 17d5aafe06..38dd12c59d 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); } } 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 8337712ea5..ab0c01417a 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 9d0f028e57..23b2513f17 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 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 cfb75f077e..3952ecb4b2 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 be4aad5f0d..50aaef6f11 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; 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 f5465d4151..3c589c55d1 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" { 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 3c589c55d1..86aff5f364 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) { 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 4a897c78f7..149ede252d 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); } } 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 ba96257557..a8ab729413 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; 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 82831787a7..2f17a83422 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).*; 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 2f17a83422..0fbe96942e 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{}; 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 6447db8c71..dc380b4389 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 fb4428fbc6..5de56f8ca7 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; 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 a464cbb85c..e1aab72786 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 d173bd36e8..4a33f0121d 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 b664d93353..64328d4df5 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 c0ac42c845..f63e915c25 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 3e4958cc95..c64ff3bbcd 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(); 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 4a33f0121d..3f5a833680 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 75caddc131..81e2b4b7f8 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 82c7cfa607..c0d88b1414 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 5961ab4c0c..e642489b01 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; + \\} + \\ + ); } 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 81e2b4b7f8..daac5c328d 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); 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 daac5c328d..376ce904e9 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 { 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 | 306 ++++++++++++++++++++------------------- 1 file changed, 154 insertions(+), 152 deletions(-) diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig index 376ce904e9..91dc4a81a4 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 { - 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; - 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}); - }, - } - } - try writer.writeAll("\n"); - } - - 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 byte arrays without sentinels", .{}); - } - } else { - return file.fail(decl.src(), "TODO non-byte arrays", .{}); - } - }, - else => |e| { - return file.fail(decl.src(), "TODO {}", .{e}); - }, + 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(); + 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?", .{}); + } +} + +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()}); + } + } 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(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(");"); +} 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 3f5a833680..bb8eca5ccf 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 { 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 | 321 +++++++++++++++++++++--------------- 1 file changed, 187 insertions(+), 134 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index ba91e0726f..487f20d1db 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 { - switch (arch) { - .x86_64, .i386 => { - 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", .{}); - } + 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 => { + 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 (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); 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 487f20d1db..143ad787b7 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; } 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 bb8eca5ccf..03307ab96d 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 094b877b53..74da3430a7 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 c0d88b1414..512dd631a7 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 8faf248636..0dfbdd20a0 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 { 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 03307ab96d..975158c477 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 91dc4a81a4..ebc4ff7e1a 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 64328d4df5..776f6de31c 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 e642489b01..e817029b83 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; \\} \\ ); 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 1f2853707a..27c9f48d72 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; + \\} + , + "", + ); + } } 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 975158c477..089e570eda 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 => { 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 015865ee3b..340cfdd303 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#} 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 6fb6557ca6..f4320df1b5 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#} 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 | 360 +++++++++++++++++++++---------- lib/std/zig/render.zig | 366 +++++++++++++++++++------------- src-self-hosted/Module.zig | 8 + src-self-hosted/translate_c.zig | 167 +++++++-------- 5 files changed, 725 insertions(+), 422 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 0502f5bb64..1de00dd0b0 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,116 @@ 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 const ArrayInfo = struct { - len_expr: *Node, - sentinel: ?*Node, - }; - - 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, - }; - }; - }; - - pub fn iterate(self: *const PrefixOp, index: usize) ?*Node { + pub fn iterate(self: *const ArrayType, 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 (i < 1) return self.len_expr; + i -= 1; - if (addr_of_info.align_info) |align_info| { - if (i < 1) return align_info.node; - i -= 1; - } - }, + if (i < 1) return self.rhs; + 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; - } - }, + return null; + } - .AddressOf, - .Await, - .BitNot, - .BoolNot, - .OptionalType, - .Negation, - .NegationWrap, - .Try, - .Resume, - => {}, + 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 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; + + 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 +1928,47 @@ pub const Node = struct { return null; } - pub fn firstToken(self: *const PrefixOp) TokenIndex { + pub fn firstToken(self: *const PtrType) TokenIndex { return self.op_token; } - pub fn lastToken(self: *const PrefixOp) TokenIndex { + 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; + i -= 1; + + return null; + } + + pub fn firstToken(self: *const SliceType) TokenIndex { + return self.op_token; + } + + 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 f5f0c10826..d46833c23f 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; - 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; - 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; - continue; - } - break; + slice_type.ptr_info.align_info = ast.PtrInfo.Align{ + .node = align_expr, + .bit_range = null, + }; + continue; } - }, - else => unreachable, + 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; + } + 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; + } + 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; + } + slice_type.ptr_info.allowzero_token = allowzero_token; + continue; + } + 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 f24886c0ab..b75e6f6ca3 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); + .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); + }, - 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); + .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, + ); + }, - try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align - try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // ( + .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 renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None); + try renderToken(tree, stream, align_token, indent, start_col, Space.None); // align + try renderToken(tree, stream, lparen_token, indent, start_col, 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 renderExpression(allocator, stream, tree, indent, start_col, align_info.node, Space.None); - 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); + if (align_info.bit_range) |bit_range| { + const colon1 = tree.prevToken(bit_range.start.firstToken()); + const colon2 = tree.prevToken(bit_range.end.firstToken()); - 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 - } - }, + 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); - .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); // ] - } + 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); + }, - 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); - }, + .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 089e570eda..993313d947 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 4ae2d74336..c45befd372 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; } } } 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 993313d947..1ed655a055 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 143ad787b7..731c039d9c 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 74da3430a7..6c0c807468 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 0dfbdd20a0..45ced54255 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); 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 7e288170af..c9ba3b3470 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" { 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 731c039d9c..5753559ce1 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", .{}); } 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 6c0c807468..c654bef611 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 797a55a80c..a06a4dd1d1 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; 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 1ed655a055..de9e1f2386 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 5753559ce1..314d497808 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 512dd631a7..1cc1b690f3 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 c1e9a38bd1..a3e1daa383 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 27c9f48d72..c4f85bfba4 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; + \\} + , + "", + ); + } } 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 dc380b4389..0dd8f4e86a 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 765828f5ce..b9bb1db533 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 da898347b9..cf3412eb5b 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(); +} 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 0dd8f4e86a..e162125fb4 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 b9bb1db533..f569264520 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(); 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 2827cfecf4..6340a44fae 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 0000000000..eb8cd6d98c --- /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); +} 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 | 221 ++++++++++----- 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, 664 insertions(+), 430 deletions(-) diff --git a/lib/std/meta/trailer_flags.zig b/lib/std/meta/trailer_flags.zig index eb8cd6d98c..e1e216cde6 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 1de00dd0b0..7f066f541b 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(), } } }; - /// 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); + /// 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 free(self: *FnProto, allocator: *mem.Allocator) void { - const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(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 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 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 d46833c23f..9f2aea390a 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 da06d93b96..aa0a76c8a5 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 b75e6f6ca3..4f9eb0e4cd 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 de9e1f2386..0a8a5152cf 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 c45befd372..b9ab28cc17 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 738f9523ad..905c74bca2 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", 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 e1e216cde6..0da7ca815a 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); 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 0a8a5152cf..902efbe339 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 0000000000..04541f2329 --- /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; +} 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 | 147 ++++----------- lib/std/zig/render.zig | 91 ++++------ src-self-hosted/Module.zig | 44 ++++- src-self-hosted/astgen.zig | 144 +++++++++++++-- src-self-hosted/translate_c.zig | 27 +-- 6 files changed, 450 insertions(+), 316 deletions(-) diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 7f066f541b..16173c4237 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 9f2aea390a..6eb3742c0e 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 => { + switch (rightmost_op.tag) { + .AddressOf, + .Await, + .BitNot, + .BoolNot, + .OptionalType, + .Negation, + .NegationWrap, + .Resume, + .Try, + => { 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.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 4f9eb0e4cd..67d73c92a6 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); + .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); }, - .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); + + .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 902efbe339..050588f3df 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 04541f2329..f75097575d 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 b9ab28cc17..b04528a3d3 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; } 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 8294fc7871..6c477a1506 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; 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 16173c4237..b91cac7865 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 6eb3742c0e..b02cdcc1fd 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 67d73c92a6..7f8a18299b 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -436,13 +436,10 @@ 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, - else => Space.Space, - }; + const op_space = Space.Space; try renderExpression(allocator, stream, tree, indent, start_col, infix_op_node.lhs, op_space); const after_op_space = blk: { @@ -458,11 +455,75 @@ 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 => {}, + 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); + + 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; } 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 050588f3df..42a30a1d20 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 f75097575d..1f157e6389 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 b04528a3d3..1da52cda96 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| { 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 42a30a1d20..72e5f6cd63 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 }, - gen_zir: *GenZIR, + /// Parents can be: `LocalVar`, `GenZIR`. parent: *Scope, + gen_zir: *GenZIR, 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 1f157e6389..be70a724c2 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 314d497808..e78ee28b5d 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 c654bef611..a150957de0 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 45ced54255..2f696d1787 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; 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 2f696d1787..5e8c966b94 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( 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 0fbe96942e..1a330667ed 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). 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 1da52cda96..46f33dd0ff 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 905c74bca2..d12b2ab860 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; 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 f4320df1b5..6180ab592f 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#}.

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 1a330667ed..f4489a4867 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; 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 62b8083222..a8c77875e4 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 a15f1f26b9..eb5bd9907f 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 0000000000..abc6c1e3cf --- /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 0000000000..ea42016e9b --- /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 0cf5ec28dd..6598a05ea7 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); } 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 5eafc4e409..499011eab9 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 f4489a4867..ac7bde47c1 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 e162125fb4..d027badac1 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 d4c8cec977..a79b3a1554 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); +} 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 5cea0ecb9a..9e5b080e85 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)) 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 d027badac1..88e111ccb5 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 5de56f8ca7..a1a261f887 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", 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 67c900507d..66f3f28b50 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 d75c967394..cb58aa6b74 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 88e111ccb5..11ff7746e7 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 021dbe5602..57a9ba2576 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); +}