From 25416d8121d14ec6a2a94943cfa4e2fb944e215b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 15 Sep 2021 13:15:15 +0200 Subject: [PATCH 001/160] macho: when adding extern fn, check if already resolved This way, we will generate valid relocation info in the codegen. --- src/codegen.zig | 9 ++++++--- src/link/MachO.zig | 33 +++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 511d4c2301..e79003f728 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2840,7 +2840,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } else if (func_value.castTag(.extern_fn)) |func_payload| { const decl = func_payload.data; - const where_index = try macho_file.addExternFn(mem.spanZ(decl.name)); + const resolv = try macho_file.addExternFn(mem.spanZ(decl.name)); const offset = blk: { switch (arch) { .x86_64 => { @@ -2861,8 +2861,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // Add relocation to the decl. try macho_file.active_decl.?.link.macho.relocs.append(self.bin_file.allocator, .{ .offset = offset, - .where = .undef, - .where_index = where_index, + .where = switch (resolv.where) { + .local => .local, + .undef => .undef, + }, + .where_index = resolv.where_index, .payload = .{ .branch = .{ .arch = arch, } }, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index ce00c85dea..8037c5e9a0 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -4228,20 +4228,34 @@ fn allocateAtom(self: *MachO, atom: *Atom, new_atom_size: u64, alignment: u64, m return vaddr; } -pub fn addExternFn(self: *MachO, name: []const u8) !u32 { +const AddExternFnRes = struct { + where: enum { + local, + undef, + }, + where_index: u32, +}; + +pub fn addExternFn(self: *MachO, name: []const u8) !AddExternFnRes { const sym_name = try std.fmt.allocPrint(self.base.allocator, "_{s}", .{name}); defer self.base.allocator.free(sym_name); + const n_strx = try self.makeString(sym_name); - if (self.strtab_dir.getKeyAdapted(@as([]const u8, sym_name), StringIndexAdapter{ - .bytes = &self.strtab, - })) |n_strx| { - const resolv = self.symbol_resolver.get(n_strx) orelse unreachable; - return resolv.where_index; + if (self.symbol_resolver.get(n_strx)) |resolv| { + return switch (resolv.where) { + .global => AddExternFnRes{ + .where = .local, + .where_index = resolv.local_sym_index, + }, + .undef => AddExternFnRes{ + .where = .undef, + .where_index = resolv.where_index, + }, + }; } log.debug("adding new extern function '{s}'", .{sym_name}); const sym_index = @intCast(u32, self.undefs.items.len); - const n_strx = try self.makeString(sym_name); try self.undefs.append(self.base.allocator, .{ .n_strx = n_strx, .n_type = macho.N_UNDF, @@ -4255,7 +4269,10 @@ pub fn addExternFn(self: *MachO, name: []const u8) !u32 { }); try self.unresolved.putNoClobber(self.base.allocator, sym_index, .stub); - return sym_index; + return AddExternFnRes{ + .where = .undef, + .where_index = sym_index, + }; } const NextSegmentAddressAndOffset = struct { From 2b90e2c2841e805ed1a1c3d16da96eb2fb8cd97d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Sep 2021 10:32:21 -0700 Subject: [PATCH 002/160] ci: azure: update to newer msys2 release --- ci/azure/pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/azure/pipelines.yml b/ci/azure/pipelines.yml index 755020a6f4..5ecfe14b7f 100644 --- a/ci/azure/pipelines.yml +++ b/ci/azure/pipelines.yml @@ -38,7 +38,7 @@ jobs: timeoutInMinutes: 360 steps: - powershell: | - (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2021-06-04/msys2-base-x86_64-20210604.sfx.exe", "sfx.exe") + (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2021-07-25/msys2-base-x86_64-20210725.sfx.exe", "sfx.exe") .\sfx.exe -y -o\ del sfx.exe displayName: Download/Extract/Install MSYS2 From e06052f201a63edd772ffade74551d159c7a7784 Mon Sep 17 00:00:00 2001 From: Michal Ziulek Date: Wed, 15 Sep 2021 19:38:00 +0200 Subject: [PATCH 003/160] Added implementation for _fseeki64 and _ftelli64 from mingw-w64 9.0.0 (#9402). (#9766) * Added fseeki64.c from mingw-w64 9.0.0. This file was missing in Zig distribution. This file contains implementation for _fseeki64 and _ftelli64 functions. --- lib/libc/mingw/stdio/fseeki64.c | 50 +++++++++++++++++++++++++++++ src/mingw.zig | 1 + test/standalone.zig | 3 ++ test/standalone/issue_9402/main.zig | 14 ++++++++ 4 files changed, 68 insertions(+) create mode 100644 lib/libc/mingw/stdio/fseeki64.c create mode 100644 test/standalone/issue_9402/main.zig diff --git a/lib/libc/mingw/stdio/fseeki64.c b/lib/libc/mingw/stdio/fseeki64.c new file mode 100644 index 0000000000..f70062e391 --- /dev/null +++ b/lib/libc/mingw/stdio/fseeki64.c @@ -0,0 +1,50 @@ +/** + * This file has no copyright assigned and is placed in the Public Domain. + * This file is part of the mingw-w64 runtime package. + * No warranty is given; refer to the file DISCLAIMER.PD within this package. + */ +#include +#include +#include + +#if !defined(__arm__) && !defined(__aarch64__) /* we have F_ARM_ANY(_fseeki64) in msvcrt.def.in */ +int __cdecl _fseeki64(FILE* stream, __int64 offset, int whence) +{ + fpos_t pos; + if (whence == SEEK_CUR) + { + /* If stream is invalid, fgetpos sets errno. */ + if (fgetpos (stream, &pos)) + return (-1); + pos += (fpos_t) offset; + } + else if (whence == SEEK_END) + { + /* If writing, we need to flush before getting file length. */ + fflush (stream); + pos = (fpos_t) (_filelengthi64 (_fileno (stream)) + offset); + } + else if (whence == SEEK_SET) + pos = (fpos_t) offset; + else + { + errno = EINVAL; + return (-1); + } + return fsetpos (stream, &pos); +} + +int __cdecl (*__MINGW_IMP_SYMBOL(_fseeki64))(FILE*, __int64, int) = _fseeki64; +#endif /* !defined(__arm__) && !defined(__aarch64__) */ + +__int64 __cdecl _ftelli64(FILE* stream) +{ + fpos_t pos; + if (fgetpos (stream, &pos)) + return -1LL; + else + return (__int64) pos; +} + +__int64 __cdecl (*__MINGW_IMP_SYMBOL(_ftelli64))(FILE*) = _ftelli64; + diff --git a/src/mingw.zig b/src/mingw.zig index 587f019270..84857df5b5 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -857,6 +857,7 @@ const mingwex_generic_src = [_][]const u8{ "stdio" ++ path.sep_str ++ "fopen64.c", "stdio" ++ path.sep_str ++ "fseeko32.c", "stdio" ++ path.sep_str ++ "fseeko64.c", + "stdio" ++ path.sep_str ++ "fseeki64.c", "stdio" ++ path.sep_str ++ "fsetpos64.c", "stdio" ++ path.sep_str ++ "ftello.c", "stdio" ++ path.sep_str ++ "ftello64.c", diff --git a/test/standalone.zig b/test/standalone.zig index 6c074642dd..45158f1057 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -36,6 +36,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void { } cases.addBuildFile("test/standalone/c_compiler/build.zig", .{ .build_modes = true, .cross_targets = true }); + if (std.Target.current.os.tag == .windows) { + cases.addC("test/standalone/issue_9402/main.zig"); + } // Try to build and run a PIE executable. if (std.Target.current.os.tag == .linux) { cases.addBuildFile("test/standalone/pie/build.zig", .{}); diff --git a/test/standalone/issue_9402/main.zig b/test/standalone/issue_9402/main.zig new file mode 100644 index 0000000000..eea6bbf4b5 --- /dev/null +++ b/test/standalone/issue_9402/main.zig @@ -0,0 +1,14 @@ +const FILE = extern struct { + dummy_field: u8, +}; + +extern fn _ftelli64([*c]FILE) i64; +extern fn _fseeki64([*c]FILE, i64, c_int) c_int; + +pub export fn main(argc: c_int, argv: **u8) c_int { + _ = argv; + _ = argc; + _ = _ftelli64(null); + _ = _fseeki64(null, 123, 2); + return 0; +} From e5fd45003e56f152364a4bdc609fda07a6b524fd Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Tue, 14 Sep 2021 10:29:01 -0600 Subject: [PATCH 004/160] azure pipeline fix removal of recently run exe The following is an azure failure that occured Sep 13: del : Cannot remove item D:\a\1\s\sfx.exe: The process cannot access the file 'D:\a\1\s\sfx.exe' because it is being used by another process. Windows will keep a hold of recently run exeutables even after their process has exited. To avoid this I've just removed the deletion of the exe file. It's about 70 MB so it's probably OK. --- ci/azure/pipelines.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/ci/azure/pipelines.yml b/ci/azure/pipelines.yml index 5ecfe14b7f..f57ef14218 100644 --- a/ci/azure/pipelines.yml +++ b/ci/azure/pipelines.yml @@ -40,7 +40,6 @@ jobs: - powershell: | (New-Object Net.WebClient).DownloadFile("https://github.com/msys2/msys2-installer/releases/download/2021-07-25/msys2-base-x86_64-20210725.sfx.exe", "sfx.exe") .\sfx.exe -y -o\ - del sfx.exe displayName: Download/Extract/Install MSYS2 - script: | @REM install updated filesystem package first without dependency checking From 19691c0b174f283ffe5b6c3fe8533ef458736064 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Sep 2021 12:37:32 -0700 Subject: [PATCH 005/160] stage2: implement `@fence` --- src/Air.zig | 6 ++++++ src/AstGen.zig | 6 +++++- src/Liveness.zig | 1 + src/Sema.zig | 31 +++++++++++++++++-------------- src/Zir.zig | 6 +++--- src/codegen.zig | 6 ++++++ src/codegen/c.zig | 12 ++++++++++++ src/codegen/llvm.zig | 9 +++++++++ src/codegen/llvm/bindings.zig | 8 ++++++++ src/link/C/zig.h | 3 +++ src/print_air.zig | 7 +++++++ test/behavior/atomics.zig | 6 ++++++ test/behavior/atomics_stage1.zig | 6 ------ 13 files changed, 83 insertions(+), 24 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index 29deb9a523..e4289c2826 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -127,6 +127,10 @@ pub const Inst = struct { /// Lowers to a hardware trap instruction, or the next best thing. /// Result type is always void. breakpoint, + /// Lowers to a memory fence instruction. + /// Result type is always void. + /// Uses the `fence` field. + fence, /// Function call. /// Result type is the return type of the function being called. /// Uses the `pl_op` field with the `Call` payload. operand is the callee. @@ -380,6 +384,7 @@ pub const Inst = struct { line: u32, column: u32, }, + fence: std.builtin.AtomicOrder, // Make sure we don't accidentally add a field to make this union // bigger than expected. Note that in Debug builds, Zig is allowed @@ -566,6 +571,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .breakpoint, .dbg_stmt, .store, + .fence, => return Type.initTag(.void), .ptrtoint, diff --git a/src/AstGen.zig b/src/AstGen.zig index b9d7d6f5be..ac4c807027 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7116,9 +7116,13 @@ fn builtinCall( }); return rvalue(gz, rl, result, node); }, + .fence => { + const order = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[0]); + const result = try gz.addUnNode(.fence, order, node); + return rvalue(gz, rl, result, node); + }, .breakpoint => return simpleNoOpVoid(gz, rl, node, .breakpoint), - .fence => return simpleNoOpVoid(gz, rl, node, .fence), .This => return rvalue(gz, rl, try gz.addNodeExtended(.this, node), node), .return_address => return rvalue(gz, rl, try gz.addNodeExtended(.ret_addr, node), node), diff --git a/src/Liveness.zig b/src/Liveness.zig index 5d8e3eed34..a7519a33ee 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -264,6 +264,7 @@ fn analyzeInst( .breakpoint, .dbg_stmt, .unreach, + .fence, => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }), .not, diff --git a/src/Sema.zig b/src/Sema.zig index de0d0b7c88..e679f03fcc 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -377,7 +377,9 @@ pub fn analyzeBody( // We also know that they cannot be referenced later, so we avoid // putting them into the map. .breakpoint => { - try sema.zirBreakpoint(block, inst); + if (!block.is_comptime) { + _ = try block.addNoOp(.breakpoint); + } i += 1; continue; }, @@ -2308,20 +2310,21 @@ fn zirSetRuntimeSafety(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) C block.want_safety = try sema.resolveConstBool(block, operand_src, inst_data.operand); } -fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { - const tracy = trace(@src()); - defer tracy.end(); - - const src_node = sema.code.instructions.items(.data)[inst].node; - const src: LazySrcLoc = .{ .node_offset = src_node }; - try sema.requireRuntimeBlock(block, src); - _ = try block.addNoOp(.breakpoint); -} - fn zirFence(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { - const src_node = sema.code.instructions.items(.data)[inst].node; - const src: LazySrcLoc = .{ .node_offset = src_node }; - return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirFence", .{}); + if (block.is_comptime) return; + + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const order_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const order = try sema.resolveAtomicOrder(block, order_src, inst_data.operand); + + if (@enumToInt(order) < @enumToInt(std.builtin.AtomicOrder.Acquire)) { + return sema.mod.fail(&block.base, order_src, "atomic ordering must be Acquire or stricter", .{}); + } + + _ = try block.addInst(.{ + .tag = .fence, + .data = .{ .fence = order }, + }); } fn zirBreak(sema: *Sema, start_block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index { diff --git a/src/Zir.zig b/src/Zir.zig index 1f0e4e370b..fcc2d5f330 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -731,7 +731,7 @@ pub const Inst = struct { size_of, /// Implements the `@bitSizeOf` builtin. Uses `un_node`. bit_size_of, - /// Implements the `@fence` builtin. Uses `node`. + /// Implements the `@fence` builtin. Uses `un_node`. fence, /// Implement builtin `@ptrToInt`. Uses `un_node`. @@ -1416,7 +1416,7 @@ pub const Inst = struct { .type_info = .un_node, .size_of = .un_node, .bit_size_of = .un_node, - .fence = .node, + .fence = .un_node, .ptr_to_int = .un_node, .error_to_int = .un_node, @@ -3016,6 +3016,7 @@ const Writer = struct { .@"resume", .@"await", .await_nosuspend, + .fence, => try self.writeUnNode(stream, inst), .ref, @@ -3187,7 +3188,6 @@ const Writer = struct { .as_node => try self.writeAs(stream, inst), .breakpoint, - .fence, .repeat, .repeat_inline, .alloc_inferred, diff --git a/src/codegen.zig b/src/codegen.zig index e79003f728..75e7a56b15 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -833,6 +833,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .block => try self.airBlock(inst), .br => try self.airBr(inst), .breakpoint => try self.airBreakpoint(), + .fence => try self.airFence(), .call => try self.airCall(inst), .cond_br => try self.airCondBr(inst), .dbg_stmt => try self.airDbgStmt(inst), @@ -2549,6 +2550,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAirBookkeeping(); } + fn airFence(self: *Self) !void { + return self.fail("TODO implement fence() for {}", .{self.target.cpu.arch}); + //return self.finishAirBookkeeping(); + } + fn airCall(self: *Self, inst: Air.Inst.Index) !void { const pl_op = self.air.instructions.items(.data)[inst].pl_op; const fn_ty = self.air.typeOf(pl_op.operand); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index ff49b18f7b..a2e2d7b20d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -842,6 +842,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .breakpoint => try airBreakpoint(o), .unreach => try airUnreach(o), + .fence => try airFence(o, inst), // TODO use a different strategy for add that communicates to the optimizer // that wrapping is UB. @@ -1439,6 +1440,17 @@ fn airBreakpoint(o: *Object) !CValue { return CValue.none; } +fn airFence(o: *Object, inst: Air.Inst.Index) !CValue { + const atomic_order = o.air.instructions.items(.data)[inst].fence; + const writer = o.writer(); + + try writer.writeAll("zig_fence("); + try writeMemoryOrder(writer, atomic_order); + try writer.writeAll(");\n"); + + return CValue.none; +} + fn airUnreach(o: *Object) !CValue { try o.writer().writeAll("zig_unreachable();\n"); return CValue.none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b28c371466..569f857caa 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1059,6 +1059,7 @@ pub const FuncGen = struct { .array_to_slice => try self.airArrayToSlice(inst), .cmpxchg_weak => try self.airCmpxchg(inst, true), .cmpxchg_strong => try self.airCmpxchg(inst, false), + .fence => try self.airFence(inst), .struct_field_ptr => try self.airStructFieldPtr(inst), .struct_field_val => try self.airStructFieldVal(inst), @@ -2005,6 +2006,14 @@ pub const FuncGen = struct { return null; } + fn airFence(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const atomic_order = self.air.instructions.items(.data)[inst].fence; + const llvm_memory_order = toLlvmAtomicOrdering(atomic_order); + const single_threaded = llvm.Bool.fromBool(self.single_threaded); + _ = self.builder.buildFence(llvm_memory_order, single_threaded, ""); + return null; + } + fn airCmpxchg(self: *FuncGen, inst: Air.Inst.Index, is_weak: bool) !?*const llvm.Value { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const extra = self.air.extraData(Air.Cmpxchg, ty_pl.payload).data; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index b4bd91708d..3fed3ca879 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -522,6 +522,14 @@ pub const Builder = opaque { Else: *const Value, Name: [*:0]const u8, ) *const Value; + + pub const buildFence = LLVMBuildFence; + extern fn LLVMBuildFence( + B: *const Builder, + ordering: AtomicOrdering, + singleThread: Bool, + Name: [*:0]const u8, + ) *const Value; }; pub const IntPredicate = enum(c_uint) { diff --git a/src/link/C/zig.h b/src/link/C/zig.h index f3fb02b840..28d6f2dd17 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -64,12 +64,15 @@ #include #define zig_cmpxchg_strong(obj, expected, desired, succ, fail) atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, fail) #define zig_cmpxchg_weak(obj, expected, desired, succ, fail) atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, fail) +#define zig_fence(order) atomic_thread_fence(order) #elif __GNUC__ #define zig_cmpxchg_strong(obj, expected, desired, succ, fail) __sync_val_compare_and_swap(obj, expected, desired) #define zig_cmpxchg_weak(obj, expected, desired, succ, fail) __sync_val_compare_and_swap(obj, expected, desired) +#define zig_fence(order) __sync_synchronize(order) #else #define zig_cmpxchg_strong(obj, expected, desired, succ, fail) zig_unimplemented() #define zig_cmpxchg_weak(obj, expected, desired, succ, fail) zig_unimplemented() +#define zig_fence(order) zig_unimplemented() #endif #include diff --git a/src/print_air.zig b/src/print_air.zig index 11cf1b7baa..82068188fd 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -192,6 +192,7 @@ const Writer = struct { .cond_br => try w.writeCondBr(s, inst), .switch_br => try w.writeSwitchBr(s, inst), .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst), + .fence => try w.writeFence(s, inst), } } @@ -276,6 +277,12 @@ const Writer = struct { }); } + fn writeFence(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const atomic_order = w.air.instructions.items(.data)[inst].fence; + + try s.print("{s}", .{@tagName(atomic_order)}); + } + fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const val = w.air.values[ty_pl.payload]; diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index 444ff56438..b29f9c9c6c 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -24,3 +24,9 @@ fn testCmpxchg() !void { try expect(@cmpxchgStrong(i32, &x, 5678, 42, .SeqCst, .SeqCst) == null); try expect(x == 42); } + +test "fence" { + var x: i32 = 1234; + @fence(.SeqCst); + x = 5678; +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index 6e754e30cd..e9de7dac6c 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -3,12 +3,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const builtin = @import("builtin"); -test "fence" { - var x: i32 = 1234; - @fence(.SeqCst); - x = 5678; -} - test "atomicrmw and atomicload" { var data: u8 = 200; try testAtomicRmw(&data); From f83a4b444c4c2fca7086fe6dbdaccc7b6d8f35ca Mon Sep 17 00:00:00 2001 From: Jonathan Marler Date: Tue, 14 Sep 2021 02:42:27 -0600 Subject: [PATCH 006/160] fix __chkstk on aarch64 --- lib/std/special/compiler_rt/stack_probe.zig | 32 ++++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/std/special/compiler_rt/stack_probe.zig b/lib/std/special/compiler_rt/stack_probe.zig index db2a86bdfb..256207d6e6 100644 --- a/lib/std/special/compiler_rt/stack_probe.zig +++ b/lib/std/special/compiler_rt/stack_probe.zig @@ -53,19 +53,6 @@ pub fn zig_probe_stack() callconv(.Naked) void { }, else => {}, } - if (comptime native_arch.isAARCH64()) { - asm volatile ( - \\ lsl x16, x15, #4 - \\ mov x17, sp - \\1: - \\ sub x17, x17, #PAGE_SIZE - \\ subs x16, x16, #PAGE_SIZE - \\ ldr xzr, [x17] - \\ b.gt 1b - \\ - \\ ret - ); - } unreachable; } @@ -118,6 +105,21 @@ fn win_probe_stack_only() void { }, else => {}, } + if (comptime native_arch.isAARCH64()) { + // NOTE: page size hardcoded to 4096 for now + asm volatile ( + \\ lsl x16, x15, #4 + \\ mov x17, sp + \\1: + \\ + \\ sub x17, x17, 4096 + \\ subs x16, x16, 4096 + \\ ldr xzr, [x17] + \\ b.gt 1b + \\ + \\ ret + ); + } unreachable; } @@ -199,7 +201,9 @@ pub fn _chkstk() callconv(.Naked) void { } pub fn __chkstk() callconv(.Naked) void { @setRuntimeSafety(false); - switch (native_arch) { + if (comptime native_arch.isAARCH64()) { + @call(.{ .modifier = .always_inline }, win_probe_stack_only, .{}); + } else switch (native_arch) { .i386 => @call(.{ .modifier = .always_inline }, win_probe_stack_adjust_sp, .{}), .x86_64 => @call(.{ .modifier = .always_inline }, win_probe_stack_only, .{}), else => unreachable, From b67d1810be3234c363ee2929ffcc91083bfb0ae5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Sep 2021 18:55:39 -0700 Subject: [PATCH 007/160] stage2: implement `@atomicRmw` and `@atomicLoad` * langref: add some more "see also" links for atomics * Add the following AIR instructions - atomic_load - atomic_store_unordered - atomic_store_monotonic - atomic_store_release - atomic_store_seq_cst - atomic_rmw * Implement those AIR instructions in LLVM and C backends. * AstGen: make the `ty` result locations for `@atomicRmw`, `@atomicLoad`, and `@atomicStore` be `coerced_ty` to avoid unnecessary ZIR instructions when Sema will be doing the coercions redundantly. * Sema for `@atomicLoad` and `@atomicRmw` is done, however Sema for `@atomicStore` is not yet implemented. - comptime eval for `@atomicRmw` is not yet implemented. * Sema: flesh out `coerceInMemoryAllowed` a little bit more. It can now handle pointers. --- doc/langref.html.in | 12 +- src/Air.zig | 60 +++++++++- src/AstGen.zig | 39 +++---- src/Liveness.zig | 13 +++ src/Sema.zig | 192 +++++++++++++++++++++++++++++-- src/codegen.zig | 23 ++++ src/codegen/c.zig | 83 ++++++++++++- src/codegen/llvm.zig | 160 ++++++++++++++++++++++++-- src/codegen/llvm/bindings.zig | 63 ++++++++++ src/link/C/zig.h | 57 ++++++++- src/print_air.zig | 36 ++++++ test/behavior/atomics.zig | 23 ++++ test/behavior/atomics_stage1.zig | 23 ---- 13 files changed, 708 insertions(+), 76 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 9a3eef2390..3f33123372 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7216,7 +7216,9 @@ fn func(y: *i32) void { {#syntax#}T{#endsyntax#} must be a pointer, a {#syntax#}bool{#endsyntax#}, a float, an integer or an enum.

+ {#see_also|@atomicStore|@atomicRmw|@fence|@cmpxchgWeak|@cmpxchgStrong#} {#header_close#} + {#header_open|@atomicRmw#}
{#syntax#}@atomicRmw(comptime T: type, ptr: *T, comptime op: builtin.AtomicRmwOp, operand: T, comptime ordering: builtin.AtomicOrder) T{#endsyntax#}

@@ -7242,7 +7244,9 @@ fn func(y: *i32) void {

  • {#syntax#}.Max{#endsyntax#} - stores the operand if it is larger. Supports integers and floats.
  • {#syntax#}.Min{#endsyntax#} - stores the operand if it is smaller. Supports integers and floats.
  • + {#see_also|@atomicStore|@atomicLoad|@fence|@cmpxchgWeak|@cmpxchgStrong#} {#header_close#} + {#header_open|@atomicStore#}
    {#syntax#}@atomicStore(comptime T: type, ptr: *T, value: T, comptime ordering: builtin.AtomicOrder) void{#endsyntax#}

    @@ -7252,6 +7256,7 @@ fn func(y: *i32) void { {#syntax#}T{#endsyntax#} must be a pointer, a {#syntax#}bool{#endsyntax#}, a float, an integer or an enum.

    + {#see_also|@atomicLoad|@atomicRmw|@fence|@cmpxchgWeak|@cmpxchgStrong#} {#header_close#} {#header_open|@bitCast#} @@ -7540,8 +7545,9 @@ fn cmpxchgStrongButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_v an integer or an enum.

    {#syntax#}@typeInfo(@TypeOf(ptr)).Pointer.alignment{#endsyntax#} must be {#syntax#}>= @sizeOf(T).{#endsyntax#}

    - {#see_also|Compile Variables|cmpxchgWeak#} + {#see_also|@atomicStore|@atomicLoad|@atomicRmw|@fence|@cmpxchgWeak#} {#header_close#} + {#header_open|@cmpxchgWeak#}
    {#syntax#}@cmpxchgWeak(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T{#endsyntax#}

    @@ -7569,7 +7575,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val an integer or an enum.

    {#syntax#}@typeInfo(@TypeOf(ptr)).Pointer.alignment{#endsyntax#} must be {#syntax#}>= @sizeOf(T).{#endsyntax#}

    - {#see_also|Compile Variables|cmpxchgStrong#} + {#see_also|@atomicStore|@atomicLoad|@atomicRmw|@fence|@cmpxchgStrong#} {#header_close#} {#header_open|@compileError#} @@ -7849,7 +7855,7 @@ export fn @"A function name that is a complete sentence."() void {}

    {#syntax#}AtomicOrder{#endsyntax#} can be found with {#syntax#}@import("std").builtin.AtomicOrder{#endsyntax#}.

    - {#see_also|Compile Variables#} + {#see_also|@atomicStore|@atomicLoad|@atomicRmw|@cmpxchgWeak|@cmpxchgStrong#} {#header_close#} {#header_open|@field#} diff --git a/src/Air.zig b/src/Air.zig index e4289c2826..2834699d69 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -127,14 +127,11 @@ pub const Inst = struct { /// Lowers to a hardware trap instruction, or the next best thing. /// Result type is always void. breakpoint, - /// Lowers to a memory fence instruction. - /// Result type is always void. - /// Uses the `fence` field. - fence, /// Function call. /// Result type is the return type of the function being called. /// Uses the `pl_op` field with the `Call` payload. operand is the callee. call, + /// `<`. Result type is always bool. /// Uses the `bin_op` field. cmp_lt, @@ -153,6 +150,7 @@ pub const Inst = struct { /// `!=`. Result type is always bool. /// Uses the `bin_op` field. cmp_neq, + /// Conditional branch. /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `pl_op` field. Operand is the condition. Payload is `CondBr`. @@ -313,10 +311,33 @@ pub const Inst = struct { /// Given a pointer to an array, return a slice. /// Uses the `ty_op` field. array_to_slice, + /// Uses the `ty_pl` field with payload `Cmpxchg`. cmpxchg_weak, /// Uses the `ty_pl` field with payload `Cmpxchg`. cmpxchg_strong, + /// Lowers to a memory fence instruction. + /// Result type is always void. + /// Uses the `fence` field. + fence, + /// Atomically load from a pointer. + /// Result type is the element type of the pointer. + /// Uses the `atomic_load` field. + atomic_load, + /// Atomically store through a pointer. + /// Result type is always `void`. + /// Uses the `bin_op` field. LHS is pointer, RHS is element. + atomic_store_unordered, + /// Same as `atomic_store_unordered` but with `AtomicOrder.Monotonic`. + atomic_store_monotonic, + /// Same as `atomic_store_unordered` but with `AtomicOrder.Release`. + atomic_store_release, + /// Same as `atomic_store_unordered` but with `AtomicOrder.SeqCst`. + atomic_store_seq_cst, + /// Atomically read-modify-write via a pointer. + /// Result type is the element type of the pointer. + /// Uses the `pl_op` field with payload `AtomicRmw`. Operand is `ptr`. + atomic_rmw, pub fn fromCmpOp(op: std.math.CompareOperator) Tag { return switch (op) { @@ -385,6 +406,10 @@ pub const Inst = struct { column: u32, }, fence: std.builtin.AtomicOrder, + atomic_load: struct { + ptr: Ref, + order: std.builtin.AtomicOrder, + }, // Make sure we don't accidentally add a field to make this union // bigger than expected. Note that in Debug builds, Zig is allowed @@ -469,6 +494,21 @@ pub const Cmpxchg = struct { } }; +pub const AtomicRmw = struct { + operand: Inst.Ref, + /// 0b00000000000000000000000000000XXX - ordering + /// 0b0000000000000000000000000XXXX000 - op + flags: u32, + + pub fn ordering(self: AtomicRmw) std.builtin.AtomicOrder { + return @intToEnum(std.builtin.AtomicOrder, @truncate(u3, self.flags)); + } + + pub fn op(self: AtomicRmw) std.builtin.AtomicRmwOp { + return @intToEnum(std.builtin.AtomicRmwOp, @truncate(u4, self.flags >> 3)); + } +}; + pub fn getMainBody(air: Air) []const Air.Inst.Index { const body_index = air.extra[@enumToInt(ExtraIndex.main_block)]; const extra = air.extraData(Block, body_index); @@ -572,6 +612,10 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .dbg_stmt, .store, .fence, + .atomic_store_unordered, + .atomic_store_monotonic, + .atomic_store_release, + .atomic_store_seq_cst, => return Type.initTag(.void), .ptrtoint, @@ -594,6 +638,14 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { const inner_ptr_ty = outer_ptr_ty.elemType(); return inner_ptr_ty.elemType(); }, + .atomic_load => { + const ptr_ty = air.typeOf(datas[inst].atomic_load.ptr); + return ptr_ty.elemType(); + }, + .atomic_rmw => { + const ptr_ty = air.typeOf(datas[inst].pl_op.operand); + return ptr_ty.elemType(); + }, } } diff --git a/src/AstGen.zig b/src/AstGen.zig index ac4c807027..b176136ba4 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7316,6 +7316,7 @@ fn builtinCall( .atomic_load => { const int_type = try typeExpr(gz, scope, params[0]); + // TODO allow this pointer type to be volatile const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{ .ptr_type_simple = .{ .is_allowzero = false, @@ -7325,16 +7326,17 @@ fn builtinCall( .elem_type = int_type, }, } }); - const ptr = try expr(gz, scope, .{ .ty = ptr_type }, params[1]); - const ordering = try expr(gz, scope, .{ .ty = .atomic_order_type }, params[2]); const result = try gz.addPlNode(.atomic_load, node, Zir.Inst.Bin{ - .lhs = ptr, - .rhs = ordering, + // zig fmt: off + .lhs = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]), + .rhs = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[2]), + // zig fmt: on }); return rvalue(gz, rl, result, node); }, .atomic_rmw => { const int_type = try typeExpr(gz, scope, params[0]); + // TODO allow this pointer type to be volatile const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{ .ptr_type_simple = .{ .is_allowzero = false, @@ -7344,20 +7346,19 @@ fn builtinCall( .elem_type = int_type, }, } }); - const ptr = try expr(gz, scope, .{ .ty = ptr_type }, params[1]); - const operation = try expr(gz, scope, .{ .ty = .atomic_rmw_op_type }, params[2]); - const operand = try expr(gz, scope, .{ .ty = int_type }, params[3]); - const ordering = try expr(gz, scope, .{ .ty = .atomic_order_type }, params[4]); const result = try gz.addPlNode(.atomic_rmw, node, Zir.Inst.AtomicRmw{ - .ptr = ptr, - .operation = operation, - .operand = operand, - .ordering = ordering, + // zig fmt: off + .ptr = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]), + .operation = try expr(gz, scope, .{ .coerced_ty = .atomic_rmw_op_type }, params[2]), + .operand = try expr(gz, scope, .{ .coerced_ty = int_type }, params[3]), + .ordering = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[4]), + // zig fmt: on }); return rvalue(gz, rl, result, node); }, .atomic_store => { const int_type = try typeExpr(gz, scope, params[0]); + // TODO allow this pointer type to be volatile const ptr_type = try gz.add(.{ .tag = .ptr_type_simple, .data = .{ .ptr_type_simple = .{ .is_allowzero = false, @@ -7367,13 +7368,12 @@ fn builtinCall( .elem_type = int_type, }, } }); - const ptr = try expr(gz, scope, .{ .ty = ptr_type }, params[1]); - const operand = try expr(gz, scope, .{ .ty = int_type }, params[2]); - const ordering = try expr(gz, scope, .{ .ty = .atomic_order_type }, params[3]); const result = try gz.addPlNode(.atomic_store, node, Zir.Inst.AtomicStore{ - .ptr = ptr, - .operand = operand, - .ordering = ordering, + // zig fmt: off + .ptr = try expr(gz, scope, .{ .coerced_ty = ptr_type }, params[1]), + .operand = try expr(gz, scope, .{ .coerced_ty = int_type }, params[2]), + .ordering = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[3]), + // zig fmt: on }); return rvalue(gz, rl, result, node); }, @@ -7456,12 +7456,11 @@ fn builtinCall( }, .Vector => { const result = try gz.addPlNode(.vector_type, node, Zir.Inst.Bin{ - .lhs = try comptimeExpr(gz, scope, .{.ty = .u32_type}, params[0]), + .lhs = try comptimeExpr(gz, scope, .{ .ty = .u32_type }, params[0]), .rhs = try typeExpr(gz, scope, params[1]), }); return rvalue(gz, rl, result, node); }, - } // zig fmt: on } diff --git a/src/Liveness.zig b/src/Liveness.zig index a7519a33ee..599507500e 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -252,6 +252,10 @@ fn analyzeInst( .ptr_ptr_elem_val, .shl, .shr, + .atomic_store_unordered, + .atomic_store_monotonic, + .atomic_store_release, + .atomic_store_seq_cst, => { const o = inst_datas[inst].bin_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); @@ -345,6 +349,15 @@ fn analyzeInst( const extra = a.air.extraData(Air.Cmpxchg, inst_datas[inst].ty_pl.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ extra.ptr, extra.expected_value, extra.new_value }); }, + .atomic_load => { + const ptr = inst_datas[inst].atomic_load.ptr; + return trackOperands(a, new_set, inst, main_tomb, .{ ptr, .none, .none }); + }, + .atomic_rmw => { + const pl_op = inst_datas[inst].pl_op; + const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data; + return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none }); + }, .br => { const br = inst_datas[inst].br; return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index e679f03fcc..c163178890 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7549,6 +7549,19 @@ fn resolveAtomicOrder( return val.toEnum(std.builtin.AtomicOrder); } +fn resolveAtomicRmwOp( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + zir_ref: Zir.Inst.Ref, +) CompileError!std.builtin.AtomicRmwOp { + const atomic_rmw_op_ty = try sema.getBuiltinType(block, src, "AtomicRmwOp"); + const air_ref = sema.resolveInst(zir_ref); + const coerced = try sema.coerce(block, atomic_rmw_op_ty, air_ref, src); + const val = try sema.resolveConstValue(block, src, coerced); + return val.toEnum(std.builtin.AtomicRmwOp); +} + fn zirCmpxchg( sema: *Sema, block: *Scope.Block, @@ -7664,14 +7677,108 @@ fn zirSelect(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro fn zirAtomicLoad(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirAtomicLoad", .{}); + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + // zig fmt: off + const elem_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + // zig fmt: on + const ptr = sema.resolveInst(extra.lhs); + const elem_ty = sema.typeOf(ptr).elemType(); + try sema.checkAtomicOperandType(block, elem_ty_src, elem_ty); + const order = try sema.resolveAtomicOrder(block, order_src, extra.rhs); + + switch (order) { + .Release, .AcqRel => { + return sema.mod.fail( + &block.base, + order_src, + "@atomicLoad atomic ordering must not be Release or AcqRel", + .{}, + ); + }, + else => {}, + } + + if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { + if (try ptr_val.pointerDeref(sema.arena)) |elem_val| { + return sema.addConstant(elem_ty, elem_val); + } + } + + try sema.requireRuntimeBlock(block, ptr_src); + return block.addInst(.{ + .tag = .atomic_load, + .data = .{ .atomic_load = .{ + .ptr = ptr, + .order = order, + } }, + }); } fn zirAtomicRmw(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const mod = sema.mod; const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.AtomicRmw, inst_data.payload_index).data; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirAtomicRmw", .{}); + // zig fmt: off + const operand_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const op_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const operand_src : LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node }; + const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg4 = inst_data.src_node }; + // zig fmt: on + const ptr = sema.resolveInst(extra.ptr); + const operand_ty = sema.typeOf(ptr).elemType(); + try sema.checkAtomicOperandType(block, operand_ty_src, operand_ty); + const op = try sema.resolveAtomicRmwOp(block, op_src, extra.operation); + + switch (operand_ty.zigTypeTag()) { + .Enum => if (op != .Xchg) { + return mod.fail(&block.base, op_src, "@atomicRmw with enum only allowed with .Xchg", .{}); + }, + .Bool => if (op != .Xchg) { + return mod.fail(&block.base, op_src, "@atomicRmw with bool only allowed with .Xchg", .{}); + }, + .Float => switch (op) { + .Xchg, .Add, .Sub => {}, + else => return mod.fail(&block.base, op_src, "@atomicRmw with float only allowed with .Xchg, .Add, and .Sub", .{}), + }, + else => {}, + } + const operand = try sema.coerce(block, operand_ty, sema.resolveInst(extra.operand), operand_src); + const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering); + + if (order == .Unordered) { + return mod.fail(&block.base, order_src, "@atomicRmw atomic ordering must not be Unordered", .{}); + } + + // special case zero bit types + if (try sema.typeHasOnePossibleValue(block, operand_ty_src, operand_ty)) |val| { + return sema.addConstant(operand_ty, val); + } + + const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: { + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |operand_val| { + _ = ptr_val; + _ = operand_val; + return mod.fail(&block.base, src, "TODO implement Sema for @atomicRmw at comptime", .{}); + } else break :rs operand_src; + } else ptr_src; + + const flags: u32 = @as(u32, @enumToInt(order)) | (@as(u32, @enumToInt(op)) << 3); + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addInst(.{ + .tag = .atomic_rmw, + .data = .{ .pl_op = .{ + .operand = ptr, + .payload = try sema.addExtra(Air.AtomicRmw{ + .operand = operand, + .flags = flags, + }), + } }, + }); } fn zirAtomicStore(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8848,7 +8955,7 @@ fn coerce( if (dest_type.eql(inst_ty)) return inst; - const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty); + const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty, false); if (in_memory_result == .ok) { return sema.bitcast(block, dest_type, inst, inst_src); } @@ -8890,11 +8997,12 @@ fn coerce( const array_type = inst_ty.elemType(); if (array_type.zigTypeTag() != .Array) break :src_array_ptr; const array_elem_type = array_type.elemType(); - if (inst_ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr; + const dest_is_mut = !dest_type.isConstPtr(); + if (inst_ty.isConstPtr() and dest_is_mut) break :src_array_ptr; if (inst_ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr; const dst_elem_type = dest_type.elemType(); - switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) { + switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut)) { .ok => {}, .no_match => break :src_array_ptr, } @@ -9001,10 +9109,80 @@ const InMemoryCoercionResult = enum { no_match, }; -fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult { +/// If pointers have the same representation in runtime memory, a bitcast AIR instruction +/// may be used for the coercion. +/// * `const` attribute can be gained +/// * `volatile` attribute can be gained +/// * `allowzero` attribute can be gained (whether from explicit attribute, C pointer, or optional pointer) but only if !dest_is_mut +/// * alignment can be decreased +/// * bit offset attributes must match exactly +/// * `*`/`[*]` must match exactly, but `[*c]` matches either one +/// * sentinel-terminated pointers can coerce into `[*]` +/// TODO improve this function to report recursive compile errors like it does in stage1. +/// look at the function types_match_const_cast_only +fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InMemoryCoercionResult { if (dest_type.eql(src_type)) return .ok; + if (dest_type.zigTypeTag() == .Pointer and + src_type.zigTypeTag() == .Pointer) + { + const dest_info = dest_type.ptrInfo().data; + const src_info = src_type.ptrInfo().data; + + const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable); + if (child == .no_match) { + return child; + } + + const ok_sent = dest_info.sentinel == null or src_info.size == .C or + (src_info.sentinel != null and + dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type)); + if (!ok_sent) { + return .no_match; + } + + const ok_ptr_size = src_info.size == dest_info.size or + src_info.size == .C or dest_info.size == .C; + if (!ok_ptr_size) { + return .no_match; + } + + const ok_cv_qualifiers = + (src_info.mutable or !dest_info.mutable) and + (!src_info.@"volatile" or dest_info.@"volatile"); + + if (!ok_cv_qualifiers) { + return .no_match; + } + + const ok_allows_zero = (dest_info.@"allowzero" and + (src_info.@"allowzero" or !dest_is_mut)) or + (!dest_info.@"allowzero" and !src_info.@"allowzero"); + if (!ok_allows_zero) { + return .no_match; + } + + if (dest_type.hasCodeGenBits() != src_type.hasCodeGenBits()) { + return .no_match; + } + + if (src_info.host_size != dest_info.host_size or + src_info.bit_offset != dest_info.bit_offset) + { + return .no_match; + } + + assert(src_info.@"align" != 0); + assert(dest_info.@"align" != 0); + + if (dest_info.@"align" > src_info.@"align") { + return .no_match; + } + + return .ok; + } + // TODO: implement more of this function return .no_match; diff --git a/src/codegen.zig b/src/codegen.zig index 75e7a56b15..08ee358bff 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -860,6 +860,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .array_to_slice => try self.airArrayToSlice(inst), .cmpxchg_strong => try self.airCmpxchg(inst), .cmpxchg_weak => try self.airCmpxchg(inst), + .atomic_rmw => try self.airAtomicRmw(inst), + .atomic_load => try self.airAtomicLoad(inst), + + .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), + .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), + .atomic_store_release => try self.airAtomicStore(inst, .Release), + .atomic_store_seq_cst => try self.airAtomicStore(inst, .SeqCst), .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0), .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1), @@ -4773,6 +4780,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ extra.ptr, extra.expected_value, extra.new_value }); } + fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void { + _ = inst; + return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch}); + } + + fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void { + _ = inst; + return self.fail("TODO implement airAtomicLoad for {}", .{self.target.cpu.arch}); + } + + fn airAtomicStore(self: *Self, inst: Air.Inst.Index, order: std.builtin.AtomicOrder) !void { + _ = inst; + _ = order; + return self.fail("TODO implement airAtomicStore for {}", .{self.target.cpu.arch}); + } + fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a2e2d7b20d..5eb4388a9e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -914,6 +914,13 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .array_to_slice => try airArrayToSlice(o, inst), .cmpxchg_weak => try airCmpxchg(o, inst, "weak"), .cmpxchg_strong => try airCmpxchg(o, inst, "strong"), + .atomic_rmw => try airAtomicRmw(o, inst), + .atomic_load => try airAtomicLoad(o, inst), + + .atomic_store_unordered => try airAtomicStore(o, inst, toMemoryOrder(.Unordered)), + .atomic_store_monotonic => try airAtomicStore(o, inst, toMemoryOrder(.Monotonic)), + .atomic_store_release => try airAtomicStore(o, inst, toMemoryOrder(.Release)), + .atomic_store_seq_cst => try airAtomicStore(o, inst, toMemoryOrder(.SeqCst)), .struct_field_ptr_index_0 => try airStructFieldPtrIndex(o, inst, 0), .struct_field_ptr_index_1 => try airStructFieldPtrIndex(o, inst, 1), @@ -1917,8 +1924,61 @@ fn airCmpxchg(o: *Object, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue { return local; } -fn writeMemoryOrder(w: anytype, order: std.builtin.AtomicOrder) !void { - const str = switch (order) { +fn airAtomicRmw(o: *Object, inst: Air.Inst.Index) !CValue { + const pl_op = o.air.instructions.items(.data)[inst].pl_op; + const extra = o.air.extraData(Air.AtomicRmw, pl_op.payload).data; + const inst_ty = o.air.typeOfIndex(inst); + const ptr = try o.resolveInst(pl_op.operand); + const operand = try o.resolveInst(extra.operand); + const local = try o.allocLocal(inst_ty, .Const); + const writer = o.writer(); + + try writer.print(" = zig_atomicrmw_{s}(", .{toAtomicRmwSuffix(extra.op())}); + try o.writeCValue(writer, ptr); + try writer.writeAll(", "); + try o.writeCValue(writer, operand); + try writer.writeAll(", "); + try writeMemoryOrder(writer, extra.ordering()); + try writer.writeAll(");\n"); + + return local; +} + +fn airAtomicLoad(o: *Object, inst: Air.Inst.Index) !CValue { + const atomic_load = o.air.instructions.items(.data)[inst].atomic_load; + const inst_ty = o.air.typeOfIndex(inst); + const ptr = try o.resolveInst(atomic_load.ptr); + const local = try o.allocLocal(inst_ty, .Const); + const writer = o.writer(); + + try writer.writeAll(" = zig_atomic_load("); + try o.writeCValue(writer, ptr); + try writer.writeAll(", "); + try writeMemoryOrder(writer, atomic_load.order); + try writer.writeAll(");\n"); + + return local; +} + +fn airAtomicStore(o: *Object, inst: Air.Inst.Index, order: [*:0]const u8) !CValue { + const bin_op = o.air.instructions.items(.data)[inst].bin_op; + const ptr = try o.resolveInst(bin_op.lhs); + const element = try o.resolveInst(bin_op.rhs); + const inst_ty = o.air.typeOfIndex(inst); + const local = try o.allocLocal(inst_ty, .Const); + const writer = o.writer(); + + try writer.writeAll(" = zig_atomic_store("); + try o.writeCValue(writer, ptr); + try writer.writeAll(", "); + try o.writeCValue(writer, element); + try writer.print(", {s});\n", .{order}); + + return local; +} + +fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { + return switch (order) { .Unordered => "memory_order_relaxed", .Monotonic => "memory_order_consume", .Acquire => "memory_order_acquire", @@ -1926,7 +1986,24 @@ fn writeMemoryOrder(w: anytype, order: std.builtin.AtomicOrder) !void { .AcqRel => "memory_order_acq_rel", .SeqCst => "memory_order_seq_cst", }; - return w.writeAll(str); +} + +fn writeMemoryOrder(w: anytype, order: std.builtin.AtomicOrder) !void { + return w.writeAll(toMemoryOrder(order)); +} + +fn toAtomicRmwSuffix(order: std.builtin.AtomicRmwOp) []const u8 { + return switch (order) { + .Xchg => "xchg", + .Add => "add", + .Sub => "sub", + .And => "and", + .Nand => "nand", + .Or => "or", + .Xor => "xor", + .Max => "max", + .Min => "min", + }; } fn IndentWriter(comptime UnderlyingWriter: type) type { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 569f857caa..af25ae51b1 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1060,6 +1060,13 @@ pub const FuncGen = struct { .cmpxchg_weak => try self.airCmpxchg(inst, true), .cmpxchg_strong => try self.airCmpxchg(inst, false), .fence => try self.airFence(inst), + .atomic_rmw => try self.airAtomicRmw(inst), + .atomic_load => try self.airAtomicLoad(inst), + + .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), + .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), + .atomic_store_release => try self.airAtomicStore(inst, .Release), + .atomic_store_seq_cst => try self.airAtomicStore(inst, .SequentiallyConsistent), .struct_field_ptr => try self.airStructFieldPtr(inst), .struct_field_val => try self.airStructFieldVal(inst), @@ -1983,20 +1990,19 @@ pub const FuncGen = struct { fn airStore(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const dest_ptr = try self.resolveInst(bin_op.lhs); + const ptr_ty = self.air.typeOf(bin_op.lhs); const src_operand = try self.resolveInst(bin_op.rhs); - // TODO set volatile on this store properly - _ = self.builder.buildStore(src_operand, dest_ptr); + _ = self.store(dest_ptr, ptr_ty, src_operand); return null; } fn airLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const is_volatile = self.air.typeOf(ty_op.operand).isVolatilePtr(); - if (!is_volatile and self.liveness.isUnused(inst)) + const ptr_ty = self.air.typeOf(ty_op.operand); + if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; const ptr = try self.resolveInst(ty_op.operand); - // TODO set volatile on this load properly - return self.builder.buildLoad(ptr, ""); + return self.load(ptr, ptr_ty); } fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -2033,14 +2039,12 @@ pub const FuncGen = struct { new_value = self.builder.buildZExt(new_value, abi_ty, ""); } } - const success_order = toLlvmAtomicOrdering(extra.successOrder()); - const failure_order = toLlvmAtomicOrdering(extra.failureOrder()); const result = self.builder.buildCmpXchg( ptr, expected_value, new_value, - success_order, - failure_order, + toLlvmAtomicOrdering(extra.successOrder()), + toLlvmAtomicOrdering(extra.failureOrder()), is_weak, self.single_threaded, ); @@ -2066,6 +2070,109 @@ pub const FuncGen = struct { return self.builder.buildInsertValue(partial, non_null_bit, 1, ""); } + fn airAtomicRmw(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const extra = self.air.extraData(Air.AtomicRmw, pl_op.payload).data; + const ptr = try self.resolveInst(pl_op.operand); + const ptr_ty = self.air.typeOf(pl_op.operand); + const operand_ty = ptr_ty.elemType(); + const operand = try self.resolveInst(extra.operand); + const is_signed_int = operand_ty.isSignedInt(); + const is_float = operand_ty.isFloat(); + const op = toLlvmAtomicRmwBinOp(extra.op(), is_signed_int, is_float); + const ordering = toLlvmAtomicOrdering(extra.ordering()); + const single_threaded = llvm.Bool.fromBool(self.single_threaded); + const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, op == .Xchg); + if (opt_abi_ty) |abi_ty| { + // operand needs widening and truncating or bitcasting. + const casted_ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), ""); + const casted_operand = if (is_float) + self.builder.buildBitCast(operand, abi_ty, "") + else if (is_signed_int) + self.builder.buildSExt(operand, abi_ty, "") + else + self.builder.buildZExt(operand, abi_ty, ""); + + const uncasted_result = self.builder.buildAtomicRmw( + op, + casted_ptr, + casted_operand, + ordering, + single_threaded, + ); + const operand_llvm_ty = try self.dg.llvmType(operand_ty); + if (is_float) { + return self.builder.buildBitCast(uncasted_result, operand_llvm_ty, ""); + } else { + return self.builder.buildTrunc(uncasted_result, operand_llvm_ty, ""); + } + } + + if (operand.typeOf().getTypeKind() != .Pointer) { + return self.builder.buildAtomicRmw(op, ptr, operand, ordering, single_threaded); + } + + // It's a pointer but we need to treat it as an int. + const usize_llvm_ty = try self.dg.llvmType(Type.initTag(.usize)); + const casted_ptr = self.builder.buildBitCast(ptr, usize_llvm_ty.pointerType(0), ""); + const casted_operand = self.builder.buildPtrToInt(operand, usize_llvm_ty, ""); + const uncasted_result = self.builder.buildAtomicRmw( + op, + casted_ptr, + casted_operand, + ordering, + single_threaded, + ); + const operand_llvm_ty = try self.dg.llvmType(operand_ty); + return self.builder.buildIntToPtr(uncasted_result, operand_llvm_ty, ""); + } + + fn airAtomicLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const atomic_load = self.air.instructions.items(.data)[inst].atomic_load; + const ptr = try self.resolveInst(atomic_load.ptr); + const ptr_ty = self.air.typeOf(atomic_load.ptr); + const ordering = toLlvmAtomicOrdering(atomic_load.order); + const operand_ty = ptr_ty.elemType(); + const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false); + + if (opt_abi_ty) |abi_ty| { + // operand needs widening and truncating + const casted_ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), ""); + const load_inst = self.load(casted_ptr, ptr_ty); + load_inst.setOrdering(ordering); + return self.builder.buildTrunc(load_inst, try self.dg.llvmType(operand_ty), ""); + } + const load_inst = self.load(ptr, ptr_ty); + load_inst.setOrdering(ordering); + return load_inst; + } + + fn airAtomicStore( + self: *FuncGen, + inst: Air.Inst.Index, + ordering: llvm.AtomicOrdering, + ) !?*const llvm.Value { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + var ptr = try self.resolveInst(bin_op.lhs); + const ptr_ty = self.air.typeOf(bin_op.lhs); + var element = try self.resolveInst(bin_op.rhs); + const operand_ty = ptr_ty.elemType(); + const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false); + + if (opt_abi_ty) |abi_ty| { + // operand needs widening + ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), ""); + if (operand_ty.isSignedInt()) { + element = self.builder.buildSExt(element, abi_ty, ""); + } else { + element = self.builder.buildZExt(element, abi_ty, ""); + } + } + const store_inst = self.store(ptr, ptr_ty, element); + store_inst.setOrdering(ordering); + return null; + } + fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value { const id = llvm.lookupIntrinsicID(name.ptr, name.len); assert(id != 0); @@ -2074,6 +2181,21 @@ pub const FuncGen = struct { // `getIntrinsicDeclaration` return self.llvmModule().getIntrinsicDeclaration(id, null, 0); } + + fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) *const llvm.Value { + _ = ptr_ty; // TODO set volatile and alignment on this load properly + return self.builder.buildLoad(ptr, ""); + } + + fn store( + self: *FuncGen, + ptr: *const llvm.Value, + ptr_ty: Type, + elem: *const llvm.Value, + ) *const llvm.Value { + _ = ptr_ty; // TODO set volatile and alignment on this store properly + return self.builder.buildStore(elem, ptr); + } }; fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void { @@ -2227,3 +2349,21 @@ fn toLlvmAtomicOrdering(atomic_order: std.builtin.AtomicOrder) llvm.AtomicOrderi .SeqCst => .SequentiallyConsistent, }; } + +fn toLlvmAtomicRmwBinOp( + op: std.builtin.AtomicRmwOp, + is_signed: bool, + is_float: bool, +) llvm.AtomicRMWBinOp { + return switch (op) { + .Xchg => .Xchg, + .Add => if (is_float) llvm.AtomicRMWBinOp.FAdd else return .Add, + .Sub => if (is_float) llvm.AtomicRMWBinOp.FSub else return .Sub, + .And => .And, + .Nand => .Nand, + .Or => .Or, + .Xor => .Xor, + .Max => if (is_signed) llvm.AtomicRMWBinOp.Max else return .UMax, + .Min => if (is_signed) llvm.AtomicRMWBinOp.Min else return .UMin, + }; +} diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 3fed3ca879..3bbc24e174 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -133,6 +133,9 @@ pub const Value = opaque { pub const constIntToPtr = LLVMConstIntToPtr; extern fn LLVMConstIntToPtr(ConstantVal: *const Value, ToType: *const Type) *const Value; + + pub const setOrdering = LLVMSetOrdering; + extern fn LLVMSetOrdering(MemoryAccessInst: *const Value, Ordering: AtomicOrdering) void; }; pub const Type = opaque { @@ -167,6 +170,9 @@ pub const Type = opaque { ElementCount: c_uint, Packed: Bool, ) void; + + pub const getTypeKind = LLVMGetTypeKind; + extern fn LLVMGetTypeKind(Ty: *const Type) TypeKind; }; pub const Module = opaque { @@ -477,6 +483,14 @@ pub const Builder = opaque { Name: [*:0]const u8, ) *const Value; + pub const buildIntToPtr = LLVMBuildIntToPtr; + extern fn LLVMBuildIntToPtr( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; + pub const buildStructGEP = LLVMBuildStructGEP; extern fn LLVMBuildStructGEP( B: *const Builder, @@ -530,6 +544,16 @@ pub const Builder = opaque { singleThread: Bool, Name: [*:0]const u8, ) *const Value; + + pub const buildAtomicRmw = LLVMBuildAtomicRMW; + extern fn LLVMBuildAtomicRMW( + B: *const Builder, + op: AtomicRMWBinOp, + PTR: *const Value, + Val: *const Value, + ordering: AtomicOrdering, + singleThread: Bool, + ) *const Value; }; pub const IntPredicate = enum(c_uint) { @@ -901,3 +925,42 @@ pub const AtomicOrdering = enum(c_uint) { AcquireRelease = 6, SequentiallyConsistent = 7, }; + +pub const AtomicRMWBinOp = enum(c_int) { + Xchg, + Add, + Sub, + And, + Nand, + Or, + Xor, + Max, + Min, + UMax, + UMin, + FAdd, + FSub, +}; + +pub const TypeKind = enum(c_int) { + Void, + Half, + Float, + Double, + X86_FP80, + FP128, + PPC_FP128, + Label, + Integer, + Function, + Struct, + Array, + Pointer, + Vector, + Metadata, + X86_MMX, + Token, + ScalableVector, + BFloat, + X86_AMX, +}; diff --git a/src/link/C/zig.h b/src/link/C/zig.h index 28d6f2dd17..e19a138c1b 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -62,16 +62,61 @@ #if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) #include -#define zig_cmpxchg_strong(obj, expected, desired, succ, fail) atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, fail) -#define zig_cmpxchg_weak(obj, expected, desired, succ, fail) atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, fail) +#define zig_cmpxchg_strong(obj, expected, desired, succ, fail) atomic_compare_exchange_strong_explicit(obj, &(expected), desired, succ, fail) +#define zig_cmpxchg_weak (obj, expected, desired, succ, fail) atomic_compare_exchange_weak_explicit (obj, &(expected), desired, succ, fail) +#define zig_atomicrmw_xchg(obj, arg, order) atomic_exchange_explicit (obj, arg, order) +#define zig_atomicrmw_add (obj, arg, order) atomic_fetch_add_explicit (obj, arg, order) +#define zig_atomicrmw_sub (obj, arg, order) atomic_fetch_sub_explicit (obj, arg, order) +#define zig_atomicrmw_or (obj, arg, order) atomic_fetch_or_explicit (obj, arg, order) +#define zig_atomicrmw_xor (obj, arg, order) atomic_fetch_xor_explicit (obj, arg, order) +#define zig_atomicrmw_and (obj, arg, order) atomic_fetch_and_explicit (obj, arg, order) +#define zig_atomicrmw_nand(obj, arg, order) atomic_fetch_nand_explicit(obj, arg, order) +#define zig_atomicrmw_min (obj, arg, order) atomic_fetch_min_explicit (obj, arg, order) +#define zig_atomicrmw_max (obj, arg, order) atomic_fetch_max_explicit (obj, arg, order) +#define zig_atomic_store (obj, arg, order) atomic_store_explicit (obj, arg, order) +#define zig_atomic_load (obj, order) atomic_load_explicit (obj, order) #define zig_fence(order) atomic_thread_fence(order) #elif __GNUC__ -#define zig_cmpxchg_strong(obj, expected, desired, succ, fail) __sync_val_compare_and_swap(obj, expected, desired) -#define zig_cmpxchg_weak(obj, expected, desired, succ, fail) __sync_val_compare_and_swap(obj, expected, desired) -#define zig_fence(order) __sync_synchronize(order) +#define memory_order_relaxed __ATOMIC_RELAXED +#define memory_order_consume __ATOMIC_CONSUME +#define memory_order_acquire __ATOMIC_ACQUIRE +#define memory_order_release __ATOMIC_RELEASE +#define memory_order_acq_rel __ATOMIC_ACQ_REL +#define memory_order_seq_cst __ATOMIC_SEQ_CST +#define zig_cmpxchg_strong(obj, expected, desired, succ, fail) __atomic_compare_exchange_n(obj, &(expected), desired, false, succ, fail) +#define zig_cmpxchg_weak (obj, expected, desired, succ, fail) __atomic_compare_exchange_n(obj, &(expected), desired, true , succ, fail) +#define zig_atomicrmw_xchg(obj, arg, order) __atomic_exchange_n(obj, arg, order) +#define zig_atomicrmw_add (obj, arg, order) __atomic_fetch_add (obj, arg, order) +#define zig_atomicrmw_sub (obj, arg, order) __atomic_fetch_sub (obj, arg, order) +#define zig_atomicrmw_or (obj, arg, order) __atomic_fetch_or (obj, arg, order) +#define zig_atomicrmw_xor (obj, arg, order) __atomic_fetch_xor (obj, arg, order) +#define zig_atomicrmw_and (obj, arg, order) __atomic_fetch_and (obj, arg, order) +#define zig_atomicrmw_nand(obj, arg, order) __atomic_fetch_nand(obj, arg, order) +#define zig_atomicrmw_min (obj, arg, order) __atomic_fetch_min (obj, arg, order) +#define zig_atomicrmw_max (obj, arg, order) __atomic_fetch_max (obj, arg, order) +#define zig_atomic_store (obj, arg, order) __atomic_store (obj, arg, order) +#define zig_atomic_load (obj, order) __atomic_load (obj, order) +#define zig_fence(order) __atomic_thread_fence(order) #else +#define memory_order_relaxed 0 +#define memory_order_consume 1 +#define memory_order_acquire 2 +#define memory_order_release 3 +#define memory_order_acq_rel 4 +#define memory_order_seq_cst 5 #define zig_cmpxchg_strong(obj, expected, desired, succ, fail) zig_unimplemented() -#define zig_cmpxchg_weak(obj, expected, desired, succ, fail) zig_unimplemented() +#define zig_cmpxchg_weak (obj, expected, desired, succ, fail) zig_unimplemented() +#define zig_atomicrmw_xchg(obj, arg, order) zig_unimplemented() +#define zig_atomicrmw_add (obj, arg, order) zig_unimplemented() +#define zig_atomicrmw_sub (obj, arg, order) zig_unimplemented() +#define zig_atomicrmw_or (obj, arg, order) zig_unimplemented() +#define zig_atomicrmw_xor (obj, arg, order) zig_unimplemented() +#define zig_atomicrmw_and (obj, arg, order) zig_unimplemented() +#define zig_atomicrmw_nand(obj, arg, order) zig_unimplemented() +#define zig_atomicrmw_min (obj, arg, order) zig_unimplemented() +#define zig_atomicrmw_max (obj, arg, order) zig_unimplemented() +#define zig_atomic_store (obj, arg, order) zig_unimplemented() +#define zig_atomic_load (obj, order) zig_unimplemented() #define zig_fence(order) zig_unimplemented() #endif diff --git a/src/print_air.zig b/src/print_air.zig index 82068188fd..39ae4251fa 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -193,6 +193,12 @@ const Writer = struct { .switch_br => try w.writeSwitchBr(s, inst), .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst), .fence => try w.writeFence(s, inst), + .atomic_load => try w.writeAtomicLoad(s, inst), + .atomic_store_unordered => try w.writeAtomicStore(s, inst, .Unordered), + .atomic_store_monotonic => try w.writeAtomicStore(s, inst, .Monotonic), + .atomic_store_release => try w.writeAtomicStore(s, inst, .Release), + .atomic_store_seq_cst => try w.writeAtomicStore(s, inst, .SeqCst), + .atomic_rmw => try w.writeAtomicRmw(s, inst), } } @@ -283,6 +289,36 @@ const Writer = struct { try s.print("{s}", .{@tagName(atomic_order)}); } + fn writeAtomicLoad(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const atomic_load = w.air.instructions.items(.data)[inst].atomic_load; + + try w.writeOperand(s, inst, 0, atomic_load.ptr); + try s.print(", {s}", .{@tagName(atomic_load.order)}); + } + + fn writeAtomicStore( + w: *Writer, + s: anytype, + inst: Air.Inst.Index, + order: std.builtin.AtomicOrder, + ) @TypeOf(s).Error!void { + const bin_op = w.air.instructions.items(.data)[inst].bin_op; + try w.writeOperand(s, inst, 0, bin_op.lhs); + try s.writeAll(", "); + try w.writeOperand(s, inst, 1, bin_op.rhs); + try s.print(", {s}", .{@tagName(order)}); + } + + fn writeAtomicRmw(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const pl_op = w.air.instructions.items(.data)[inst].pl_op; + const extra = w.air.extraData(Air.AtomicRmw, pl_op.payload).data; + + try w.writeOperand(s, inst, 0, pl_op.operand); + try s.writeAll(", "); + try w.writeOperand(s, inst, 1, extra.operand); + try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) }); + } + fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const val = w.air.values[ty_pl.payload]; diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index b29f9c9c6c..01ec767253 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -30,3 +30,26 @@ test "fence" { @fence(.SeqCst); x = 5678; } + +test "atomicrmw and atomicload" { + var data: u8 = 200; + try testAtomicRmw(&data); + try expect(data == 42); + try testAtomicLoad(&data); +} + +fn testAtomicRmw(ptr: *u8) !void { + const prev_value = @atomicRmw(u8, ptr, .Xchg, 42, .SeqCst); + try expect(prev_value == 200); + comptime { + var x: i32 = 1234; + const y: i32 = 12345; + try expect(@atomicLoad(i32, &x, .SeqCst) == 1234); + try expect(@atomicLoad(i32, &y, .SeqCst) == 12345); + } +} + +fn testAtomicLoad(ptr: *u8) !void { + const x = @atomicLoad(u8, ptr, .SeqCst); + try expect(x == 42); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index e9de7dac6c..b13e3a62c6 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -3,29 +3,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const builtin = @import("builtin"); -test "atomicrmw and atomicload" { - var data: u8 = 200; - try testAtomicRmw(&data); - try expect(data == 42); - try testAtomicLoad(&data); -} - -fn testAtomicRmw(ptr: *u8) !void { - const prev_value = @atomicRmw(u8, ptr, .Xchg, 42, .SeqCst); - try expect(prev_value == 200); - comptime { - var x: i32 = 1234; - const y: i32 = 12345; - try expect(@atomicLoad(i32, &x, .SeqCst) == 1234); - try expect(@atomicLoad(i32, &y, .SeqCst) == 12345); - } -} - -fn testAtomicLoad(ptr: *u8) !void { - const x = @atomicLoad(u8, ptr, .SeqCst); - try expect(x == 42); -} - test "cmpxchg with ptr" { var data1: i32 = 1234; var data2: i32 = 5678; From d5c1d24964b0ee8ad37beb8e2a907e8caa645e07 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 15 Sep 2021 19:55:57 -0700 Subject: [PATCH 008/160] stage2: fix "cmpxchg with ptr" test case * Sema: fix atomic operand checking to allow pointers. * LLVM backend: implement pointer-like optional constants. * LLVM backend: fix `is_non_null` and `optional_payload` instructions to support pointer-like optionals. * Type: introduce `isPtrAtRuntime` method. * Type: fix `isPtrLikeOptional` to get the correct answer for allowzero pointers and slices. --- src/Sema.zig | 16 +++++---- src/codegen/llvm.zig | 61 ++++++++++++++++++++------------ src/type.zig | 45 ++++++++++++++++++++++- test/behavior/atomics.zig | 20 +++++++++++ test/behavior/atomics_stage1.zig | 20 ----------- 5 files changed, 113 insertions(+), 49 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index c163178890..19e0f41b98 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7518,12 +7518,16 @@ fn checkAtomicOperandType( return; }, .Bool => return, // Will be treated as `u8`. - else => return sema.mod.fail( - &block.base, - ty_src, - "expected bool, integer, float, enum, or pointer type; found {}", - .{ty}, - ), + else => { + if (ty.isPtrAtRuntime()) return; + + return sema.mod.fail( + &block.base, + ty_src, + "expected bool, integer, float, enum, or pointer type; found {}", + .{ty}, + ); + }, }; const bit_count = int_ty.intInfo(target).bits; if (bit_count > max_atomic_bits) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index af25ae51b1..5720756cc2 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -602,18 +602,18 @@ pub const DeclGen = struct { return elem_type.arrayType(@intCast(c_uint, total_len)); }, .Optional => { - if (!t.isPtrLikeOptional()) { - var buf: Type.Payload.ElemType = undefined; - const child_type = t.optionalChild(&buf); + var buf: Type.Payload.ElemType = undefined; + const child_type = t.optionalChild(&buf); + const payload_llvm_ty = try self.llvmType(child_type); - const optional_types: [2]*const llvm.Type = .{ - try self.llvmType(child_type), - self.context.intType(1), - }; - return self.context.structType(&optional_types, 2, .False); - } else { - return self.todo("implement optional pointers as actual pointers", .{}); + if (t.isPtrLikeOptional()) { + return payload_llvm_ty; } + + const fields: [2]*const llvm.Type = .{ + payload_llvm_ty, self.context.intType(1), + }; + return self.context.structType(&fields, fields.len, .False); }, .ErrorUnion => { const error_type = t.errorUnionSet(); @@ -1573,6 +1573,13 @@ pub const FuncGen = struct { const operand = try self.resolveInst(un_op); if (operand_is_ptr) { + const operand_ty = self.air.typeOf(un_op).elemType(); + if (operand_ty.isPtrLikeOptional()) { + const operand_llvm_ty = try self.dg.llvmType(operand_ty); + const loaded = self.builder.buildLoad(operand, ""); + return self.builder.buildICmp(.NE, loaded, operand_llvm_ty.constNull(), ""); + } + const index_type = self.context.intType(32); var indices: [2]*const llvm.Value = .{ @@ -1581,9 +1588,15 @@ pub const FuncGen = struct { }; return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""), ""); - } else { - return self.builder.buildExtractValue(operand, 1, ""); } + + const operand_ty = self.air.typeOf(un_op); + if (operand_ty.isPtrLikeOptional()) { + const operand_llvm_ty = try self.dg.llvmType(operand_ty); + return self.builder.buildICmp(.NE, operand, operand_llvm_ty.constNull(), ""); + } + + return self.builder.buildExtractValue(operand, 1, ""); } fn airIsNull(self: *FuncGen, inst: Air.Inst.Index, operand_is_ptr: bool) !?*const llvm.Value { @@ -1636,17 +1649,24 @@ pub const FuncGen = struct { const operand = try self.resolveInst(ty_op.operand); if (operand_is_ptr) { + const operand_ty = self.air.typeOf(ty_op.operand).elemType(); + if (operand_ty.isPtrLikeOptional()) { + return self.builder.buildLoad(operand, ""); + } + const index_type = self.context.intType(32); - var indices: [2]*const llvm.Value = .{ - index_type.constNull(), - index_type.constNull(), + index_type.constNull(), index_type.constNull(), }; - return self.builder.buildInBoundsGEP(operand, &indices, 2, ""); - } else { - return self.builder.buildExtractValue(operand, 0, ""); } + + const operand_ty = self.air.typeOf(ty_op.operand); + if (operand_ty.isPtrLikeOptional()) { + return operand; + } + + return self.builder.buildExtractValue(operand, 0, ""); } fn airErrUnionPayload( @@ -2050,8 +2070,6 @@ pub const FuncGen = struct { ); const optional_ty = self.air.typeOfIndex(inst); - var buffer: Type.Payload.ElemType = undefined; - const child_ty = optional_ty.optionalChild(&buffer); var payload = self.builder.buildExtractValue(result, 0, ""); if (opt_abi_ty != null) { @@ -2060,8 +2078,7 @@ pub const FuncGen = struct { const success_bit = self.builder.buildExtractValue(result, 1, ""); if (optional_ty.isPtrLikeOptional()) { - const child_llvm_ty = try self.dg.llvmType(child_ty); - return self.builder.buildSelect(success_bit, child_llvm_ty.constNull(), payload, ""); + return self.builder.buildSelect(success_bit, payload.typeOf().constNull(), payload, ""); } const optional_llvm_ty = try self.dg.llvmType(optional_ty); diff --git a/src/type.zig b/src/type.zig index 2403893133..d9a641474f 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2191,6 +2191,44 @@ pub const Type = extern union { }; } + pub fn isPtrAtRuntime(self: Type) bool { + switch (self.tag()) { + .c_const_pointer, + .c_mut_pointer, + .many_const_pointer, + .many_mut_pointer, + .manyptr_const_u8, + .manyptr_u8, + .optional_single_const_pointer, + .optional_single_mut_pointer, + .single_const_pointer, + .single_const_pointer_to_comptime_int, + .single_mut_pointer, + => return true, + + .pointer => switch (self.castTag(.pointer).?.data.size) { + .Slice => return false, + .One, .Many, .C => return true, + }, + + .optional => { + var buf: Payload.ElemType = undefined; + const child_type = self.optionalChild(&buf); + // optionals of zero sized pointers behave like bools + if (!child_type.hasCodeGenBits()) return false; + if (child_type.zigTypeTag() != .Pointer) return false; + + const info = child_type.ptrInfo().data; + switch (info.size) { + .Slice, .C => return false, + .Many, .One => return !info.@"allowzero", + } + }, + + else => return false, + } + } + /// Asserts that the type is an optional pub fn isPtrLikeOptional(self: Type) bool { switch (self.tag()) { @@ -2203,8 +2241,13 @@ pub const Type = extern union { const child_type = self.optionalChild(&buf); // optionals of zero sized pointers behave like bools if (!child_type.hasCodeGenBits()) return false; + if (child_type.zigTypeTag() != .Pointer) return false; - return child_type.zigTypeTag() == .Pointer and !child_type.isCPtr(); + const info = child_type.ptrInfo().data; + switch (info.size) { + .Slice, .C => return false, + .Many, .One => return !info.@"allowzero", + } }, else => unreachable, } diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index 01ec767253..185108cabd 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -53,3 +53,23 @@ fn testAtomicLoad(ptr: *u8) !void { const x = @atomicLoad(u8, ptr, .SeqCst); try expect(x == 42); } + +test "cmpxchg with ptr" { + var data1: i32 = 1234; + var data2: i32 = 5678; + var data3: i32 = 9101; + var x: *i32 = &data1; + if (@cmpxchgWeak(*i32, &x, &data2, &data3, .SeqCst, .SeqCst)) |x1| { + try expect(x1 == &data1); + } else { + @panic("cmpxchg should have failed"); + } + + while (@cmpxchgWeak(*i32, &x, &data1, &data3, .SeqCst, .SeqCst)) |x1| { + try expect(x1 == &data1); + } + try expect(x == &data3); + + try expect(@cmpxchgStrong(*i32, &x, &data3, &data2, .SeqCst, .SeqCst) == null); + try expect(x == &data2); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index b13e3a62c6..2cdbb7b333 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -3,26 +3,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const builtin = @import("builtin"); -test "cmpxchg with ptr" { - var data1: i32 = 1234; - var data2: i32 = 5678; - var data3: i32 = 9101; - var x: *i32 = &data1; - if (@cmpxchgWeak(*i32, &x, &data2, &data3, .SeqCst, .SeqCst)) |x1| { - try expect(x1 == &data1); - } else { - @panic("cmpxchg should have failed"); - } - - while (@cmpxchgWeak(*i32, &x, &data1, &data3, .SeqCst, .SeqCst)) |x1| { - try expect(x1 == &data1); - } - try expect(x == &data3); - - try expect(@cmpxchgStrong(*i32, &x, &data3, &data2, .SeqCst, .SeqCst) == null); - try expect(x == &data2); -} - test "128-bit cmpxchg" { try test_u128_cmpxchg(); comptime try test_u128_cmpxchg(); From e1bf350b4d66a682c8fc5f151563dd1725e8eaf1 Mon Sep 17 00:00:00 2001 From: Kirjastonhoitaja Date: Wed, 15 Sep 2021 09:51:43 +0200 Subject: [PATCH 009/160] net.Address: Fix writing 0-bytes when formatting Unix addresses The entire 'path' array would get written to the formatting function, when it should instead be treated as a regular zero-terminated string. Note that this doesn't handle abstract paths on Linux, those paths *start* with a \0 byte and are hence treated as empty strings instead. But fixing that would require more adjustments than just formatting, in particular to getOsSockLen(). --- lib/std/net.zig | 2 +- lib/std/net/test.zig | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/std/net.zig b/lib/std/net.zig index 1f1a020028..94df1532d6 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -157,7 +157,7 @@ pub const Address = extern union { unreachable; } - try std.fmt.format(out_stream, "{s}", .{&self.un.path}); + try std.fmt.format(out_stream, "{s}", .{std.mem.sliceTo(&self.un.path, 0)}); }, else => unreachable, } diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index 2e63fc9329..16a43fa421 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -90,6 +90,19 @@ test "parse and render IPv4 addresses" { try testing.expectError(error.NonCanonical, net.Address.parseIp4("127.01.0.1", 0)); } +test "parse and render UNIX addresses" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + if (!net.has_unix_sockets) return error.SkipZigTest; + + var buffer: [14]u8 = undefined; + const addr = net.Address.initUnix("/tmp/testpath") catch unreachable; + const fmt_addr = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable; + try std.testing.expectEqualSlices(u8, "/tmp/testpath", fmt_addr); + + const too_long = [_]u8{'a'} ** (addr.un.path.len + 1); + try testing.expectError(error.NameTooLong, net.Address.initUnix(too_long[0..])); +} + test "resolve DNS" { if (builtin.os.tag == .wasi) return error.SkipZigTest; From db940a2c8131c52fb6e1f2e40af9c68d2228e656 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 15 Sep 2021 17:31:40 -0700 Subject: [PATCH 010/160] std.unicode: cleanup allocations on error in allocating functions Fixes leaks when `utf16leToUtf8Alloc`/`utf16leToUtf8AllocZ`/`utf8ToUtf16LeWithNull` return an error and adds relevant test cases --- lib/std/unicode.zig | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/std/unicode.zig b/lib/std/unicode.zig index b93b5e361f..25f1ba1b48 100644 --- a/lib/std/unicode.zig +++ b/lib/std/unicode.zig @@ -553,8 +553,9 @@ fn testDecode(bytes: []const u8) !u21 { /// Caller must free returned memory. pub fn utf16leToUtf8Alloc(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 { var result = std.ArrayList(u8).init(allocator); + errdefer result.deinit(); // optimistically guess that it will all be ascii. - try result.ensureCapacity(utf16le.len); + try result.ensureTotalCapacity(utf16le.len); var out_index: usize = 0; var it = Utf16LeIterator.init(utf16le); while (try it.nextCodepoint()) |codepoint| { @@ -569,9 +570,10 @@ pub fn utf16leToUtf8Alloc(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 /// Caller must free returned memory. pub fn utf16leToUtf8AllocZ(allocator: *mem.Allocator, utf16le: []const u16) ![:0]u8 { - var result = try std.ArrayList(u8).initCapacity(allocator, utf16le.len); + var result = std.ArrayList(u8).init(allocator); + errdefer result.deinit(); // optimistically guess that it will all be ascii. - try result.ensureCapacity(utf16le.len); + try result.ensureTotalCapacity(utf16le.len); var out_index: usize = 0; var it = Utf16LeIterator.init(utf16le); while (try it.nextCodepoint()) |codepoint| { @@ -653,10 +655,18 @@ test "utf16leToUtf8" { defer std.testing.allocator.free(utf8); try testing.expect(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80")); } + + { + mem.writeIntSliceLittle(u16, utf16le_as_bytes[0..], 0xdcdc); + mem.writeIntSliceLittle(u16, utf16le_as_bytes[2..], 0xdcdc); + const result = utf16leToUtf8Alloc(std.testing.allocator, &utf16le); + try std.testing.expectError(error.UnexpectedSecondSurrogateHalf, result); + } } pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![:0]u16 { var result = std.ArrayList(u16).init(allocator); + errdefer result.deinit(); // optimistically guess that it will not require surrogate pairs try result.ensureCapacity(utf8.len + 1); @@ -718,6 +728,10 @@ test "utf8ToUtf16Le" { try testing.expectEqual(@as(usize, 2), length); try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(utf16le[0..])); } + { + const result = utf8ToUtf16Le(utf16le[0..], "\xf4\x90\x80\x80"); + try testing.expectError(error.InvalidUtf8, result); + } } test "utf8ToUtf16LeWithNull" { @@ -733,6 +747,10 @@ test "utf8ToUtf16LeWithNull" { try testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", mem.sliceAsBytes(utf16[0..])); try testing.expect(utf16[2] == 0); } + { + const result = utf8ToUtf16LeWithNull(testing.allocator, "\xf4\x90\x80\x80"); + try testing.expectError(error.InvalidUtf8, result); + } } /// Converts a UTF-8 string literal into a UTF-16LE string literal. From 506f24cac2f5226210f9ce505d5b93c47b7b8c87 Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Wed, 15 Sep 2021 10:43:45 +1000 Subject: [PATCH 011/160] Set the Storage socket sizes to be system defined Some systems (Solaris, OpenBSD, AIX) change their definitions of sockaddr_storage to be larger than 128 bytes. This comment adds a new constant in the `sockaddr` that defines the size for every system. Fixes #9759 --- lib/std/c/darwin.zig | 1 + lib/std/c/dragonfly.zig | 1 + lib/std/c/freebsd.zig | 1 + lib/std/c/haiku.zig | 1 + lib/std/c/netbsd.zig | 1 + lib/std/c/openbsd.zig | 1 + lib/std/os/linux.zig | 1 + lib/std/os/windows/ws2_32.zig | 1 + lib/std/x/os/socket.zig | 2 +- 9 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 8bb30efab3..b85a5bc40f 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -291,6 +291,7 @@ pub const sockaddr = extern struct { family: sa_family_t, data: [14]u8, + pub const SS_MAXSIZE = 128; pub const storage = std.x.os.Socket.Address.Native.Storage; pub const in = extern struct { len: u8 = @sizeOf(in), diff --git a/lib/std/c/dragonfly.zig b/lib/std/c/dragonfly.zig index 35bbeac6a3..a2b7e31b4f 100644 --- a/lib/std/c/dragonfly.zig +++ b/lib/std/c/dragonfly.zig @@ -465,6 +465,7 @@ pub const sockaddr = extern struct { family: u8, data: [14]u8, + pub const SS_MAXSIZE = 128; pub const storage = std.x.os.Socket.Address.Native.Storage; pub const in = extern struct { diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index f65af3f915..ecc9690069 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -323,6 +323,7 @@ pub const sockaddr = extern struct { /// actually longer; address value data: [14]u8, + pub const SS_MAXSIZE = 128; pub const storage = std.x.os.Socket.Address.Native.Storage; pub const in = extern struct { diff --git a/lib/std/c/haiku.zig b/lib/std/c/haiku.zig index 1ad51cfadd..dcebeea95e 100644 --- a/lib/std/c/haiku.zig +++ b/lib/std/c/haiku.zig @@ -339,6 +339,7 @@ pub const sockaddr = extern struct { /// actually longer; address value data: [14]u8, + pub const SS_MAXSIZE = 128; pub const storage = std.x.os.Socket.Address.Native.Storage; pub const in = extern struct { diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig index d76a9ecdf5..042d540bcc 100644 --- a/lib/std/c/netbsd.zig +++ b/lib/std/c/netbsd.zig @@ -476,6 +476,7 @@ pub const sockaddr = extern struct { /// actually longer; address value data: [14]u8, + pub const SS_MAXSIZE = 128; pub const storage = std.x.os.Socket.Address.Native.Storage; pub const in = extern struct { diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index b3919d4724..39425b5e0e 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -279,6 +279,7 @@ pub const sockaddr = extern struct { /// actually longer; address value data: [14]u8, + pub const SS_MAXSIZE = 256; pub const storage = std.x.os.Socket.Address.Native.Storage; pub const in = extern struct { diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index df7166a4ff..2ea127a522 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -2923,6 +2923,7 @@ pub const sockaddr = extern struct { family: sa_family_t, data: [14]u8, + pub const SS_MAXSIZE = 128; pub const storage = std.x.os.Socket.Address.Native.Storage; /// IPv4 socket address diff --git a/lib/std/os/windows/ws2_32.zig b/lib/std/os/windows/ws2_32.zig index 168a098397..a6eb9e07ff 100644 --- a/lib/std/os/windows/ws2_32.zig +++ b/lib/std/os/windows/ws2_32.zig @@ -1105,6 +1105,7 @@ pub const sockaddr = extern struct { family: ADDRESS_FAMILY, data: [14]u8, + pub const SS_MAXSIZE = 128; pub const storage = std.x.os.Socket.Address.Native.Storage; /// IPv4 socket address diff --git a/lib/std/x/os/socket.zig b/lib/std/x/os/socket.zig index 5930b8cb9a..529fd19598 100644 --- a/lib/std/x/os/socket.zig +++ b/lib/std/x/os/socket.zig @@ -37,7 +37,7 @@ pub const Socket = struct { /// POSIX `sockaddr.storage`. The expected size and alignment is specified in IETF RFC 2553. pub const Storage = extern struct { - pub const expected_size = 128; + pub const expected_size = os.sockaddr.SS_MAXSIZE; pub const expected_alignment = 8; pub const padding_size = expected_size - From 983d6dcd9ea75e05abd8ce2bd247bbad3960acd7 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 15 Sep 2021 16:57:10 +0200 Subject: [PATCH 012/160] macho: implement object relinking in stage2 * In watch mode, when changing the C source, we will trigger complete relinking of objects, dylibs and archives (atoms coming from the incremental updates stay put however). This means, we need to undo metadata populated when linking in objects, archives and dylibs. * Remove unused splitting section into atoms bit. This optimisation will probably be best rewritten from scratch once self-hosted matures so parking the idea for now. Also, for easier management of atoms spawned from the Object file, keep the atoms subgraph as part of the Object file struct. * Remove obsolete ref to static initializers in object struct. * Implement handling of global symbol collision in updateDeclExports. --- src/link/MachO.zig | 184 ++++++++++++++--- src/link/MachO/Atom.zig | 102 +++++---- src/link/MachO/Object.zig | 423 +++++++++++++++----------------------- 3 files changed, 375 insertions(+), 334 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 8037c5e9a0..324870a705 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -231,7 +231,7 @@ const SymbolWithLoc = struct { }, where_index: u32, local_sym_index: u32 = 0, - file: u16 = 0, + file: ?u16 = null, // null means Zig module }; pub const GotIndirectionKey = struct { @@ -543,9 +543,6 @@ pub fn flush(self: *MachO, comp: *Compilation) !void { .mode = link.determineMode(self.base.options), }); try self.populateMissingMetadata(); - - // TODO mimicking insertion of null symbol from incremental linker. - // This will need to moved. try self.locals.append(self.base.allocator, .{ .n_strx = 0, .n_type = macho.N_UNDF, @@ -557,13 +554,56 @@ pub fn flush(self: *MachO, comp: *Compilation) !void { } if (needs_full_relink) { + for (self.objects.items) |*object| { + object.free(self.base.allocator, self); + object.deinit(self.base.allocator); + } self.objects.clearRetainingCapacity(); + + for (self.archives.items) |*archive| { + archive.deinit(self.base.allocator); + } self.archives.clearRetainingCapacity(); + + for (self.dylibs.items) |*dylib| { + dylib.deinit(self.base.allocator); + } self.dylibs.clearRetainingCapacity(); self.dylibs_map.clearRetainingCapacity(); self.referenced_dylibs.clearRetainingCapacity(); - // TODO figure out how to clear atoms from objects, etc. + { + var to_remove = std.ArrayList(u32).init(self.base.allocator); + defer to_remove.deinit(); + var it = self.symbol_resolver.iterator(); + while (it.next()) |entry| { + const key = entry.key_ptr.*; + const value = entry.value_ptr.*; + if (value.file != null) { + try to_remove.append(key); + } + } + + for (to_remove.items) |key| { + if (self.symbol_resolver.fetchRemove(key)) |entry| { + const resolv = entry.value; + switch (resolv.where) { + .global => { + self.globals_free_list.append(self.base.allocator, resolv.where_index) catch {}; + const sym = &self.globals.items[resolv.where_index]; + sym.n_strx = 0; + sym.n_type = 0; + sym.n_value = 0; + }, + .undef => { + const sym = &self.undefs.items[resolv.where_index]; + sym.n_strx = 0; + sym.n_desc = 0; + }, + } + } + } + } // Positional arguments to the linker such as object files and static archives. var positionals = std.ArrayList([]const u8).init(arena); @@ -802,13 +842,35 @@ pub fn flush(self: *MachO, comp: *Compilation) !void { try self.createDsoHandleAtom(); try self.addCodeSignatureLC(); + // log.warn("locals:", .{}); + // for (self.locals.items) |sym, id| { + // log.warn(" {d}: {s}: {}", .{ id, self.getString(sym.n_strx), sym }); + // } + // log.warn("globals:", .{}); + // for (self.globals.items) |sym, id| { + // log.warn(" {d}: {s}: {}", .{ id, self.getString(sym.n_strx), sym }); + // } + // log.warn("undefs:", .{}); + // for (self.undefs.items) |sym, id| { + // log.warn(" {d}: {s}: {}", .{ id, self.getString(sym.n_strx), sym }); + // } + // { + // log.warn("resolver:", .{}); + // var it = self.symbol_resolver.iterator(); + // while (it.next()) |entry| { + // log.warn(" {s} => {}", .{ self.getString(entry.key_ptr.*), entry.value_ptr.* }); + // } + // } + for (self.unresolved.keys()) |index| { const sym = self.undefs.items[index]; const sym_name = self.getString(sym.n_strx); const resolv = self.symbol_resolver.get(sym.n_strx) orelse unreachable; log.err("undefined reference to symbol '{s}'", .{sym_name}); - log.err(" first referenced in '{s}'", .{self.objects.items[resolv.file].name}); + if (resolv.file) |file| { + log.err(" first referenced in '{s}'", .{self.objects.items[file].name}); + } } if (self.unresolved.count() > 0) { return error.UndefinedSymbolReference; @@ -2349,7 +2411,9 @@ fn resolveSymbolsInObject(self: *MachO, object_id: u16) !void { !(symbolIsWeakDef(global.*) or symbolIsPext(global.*))) { log.err("symbol '{s}' defined multiple times", .{sym_name}); - log.err(" first definition in '{s}'", .{self.objects.items[resolv.file].name}); + if (resolv.file) |file| { + log.err(" first definition in '{s}'", .{self.objects.items[file].name}); + } log.err(" next definition in '{s}'", .{object.name}); return error.MultipleSymbolDefinitions; } else if (symbolIsWeakDef(sym) or symbolIsPext(sym)) continue; // Current symbol is weak, so skip it. @@ -2632,10 +2696,10 @@ fn parseObjectsIntoAtoms(self: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - var parsed_atoms = Object.ParsedAtoms.init(self.base.allocator); + var parsed_atoms = std.AutoArrayHashMap(MatchingSection, *Atom).init(self.base.allocator); defer parsed_atoms.deinit(); - var first_atoms = Object.ParsedAtoms.init(self.base.allocator); + var first_atoms = std.AutoArrayHashMap(MatchingSection, *Atom).init(self.base.allocator); defer first_atoms.deinit(); var section_metadata = std.AutoHashMap(MatchingSection, struct { @@ -2644,13 +2708,12 @@ fn parseObjectsIntoAtoms(self: *MachO) !void { }).init(self.base.allocator); defer section_metadata.deinit(); - for (self.objects.items) |*object, object_id| { + for (self.objects.items) |*object| { if (object.analyzed) continue; - var atoms_in_objects = try object.parseIntoAtoms(self.base.allocator, @intCast(u16, object_id), self); - defer atoms_in_objects.deinit(); + try object.parseIntoAtoms(self.base.allocator, self); - var it = atoms_in_objects.iterator(); + var it = object.end_atoms.iterator(); while (it.next()) |entry| { const match = entry.key_ptr.*; const last_atom = entry.value_ptr.*; @@ -3292,8 +3355,6 @@ pub fn updateDeclExports( decl: *Module.Decl, exports: []const *Module.Export, ) !void { - // TODO If we are exporting with global linkage, check for already defined globals and flag - // symbol duplicate/collision! if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } @@ -3303,7 +3364,7 @@ pub fn updateDeclExports( const tracy = trace(@src()); defer tracy.end(); - try self.globals.ensureCapacity(self.base.allocator, self.globals.items.len + exports.len); + try self.globals.ensureUnusedCapacity(self.base.allocator, exports.len); if (decl.link.macho.local_sym_index == 0) return; const decl_sym = &self.locals.items[decl.link.macho.local_sym_index]; @@ -3313,15 +3374,76 @@ pub fn updateDeclExports( if (exp.options.section) |section_name| { if (!mem.eql(u8, section_name, "__text")) { - try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.count() + 1); - module.failed_exports.putAssumeCapacityNoClobber( + try module.failed_exports.putNoClobber( + module.gpa, exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), + try Module.ErrorMsg.create( + self.base.allocator, + decl.srcLoc(), + "Unimplemented: ExportOptions.section", + .{}, + ), ); continue; } } + if (exp.options.linkage == .LinkOnce) { + try module.failed_exports.putNoClobber( + module.gpa, + exp, + try Module.ErrorMsg.create( + self.base.allocator, + decl.srcLoc(), + "Unimplemented: GlobalLinkage.LinkOnce", + .{}, + ), + ); + continue; + } + + const is_weak = exp.options.linkage == .Internal or exp.options.linkage == .Weak; + const n_strx = try self.makeString(exp_name); + if (self.symbol_resolver.getPtr(n_strx)) |resolv| { + switch (resolv.where) { + .global => { + if (resolv.local_sym_index == decl.link.macho.local_sym_index) continue; + + const sym = &self.globals.items[resolv.where_index]; + + if (symbolIsTentative(sym.*)) { + _ = self.tentatives.fetchSwapRemove(resolv.where_index); + } else if (!is_weak and !(symbolIsWeakDef(sym.*) or symbolIsPext(sym.*))) { + _ = try module.failed_exports.put( + module.gpa, + exp, + try Module.ErrorMsg.create( + self.base.allocator, + decl.srcLoc(), + \\LinkError: symbol '{s}' defined multiple times + \\ first definition in '{s}' + , + .{ exp_name, self.objects.items[resolv.file.?].name }, + ), + ); + continue; + } else if (is_weak) continue; // Current symbol is weak, so skip it. + + // Otherwise, update the resolver and the global symbol. + sym.n_type = macho.N_SECT | macho.N_EXT; + resolv.local_sym_index = decl.link.macho.local_sym_index; + resolv.file = null; + exp.link.macho.sym_index = resolv.where_index; + + continue; + }, + .undef => { + _ = self.unresolved.fetchSwapRemove(resolv.where_index); + _ = self.symbol_resolver.remove(n_strx); + }, + } + } + var n_type: u8 = macho.N_SECT | macho.N_EXT; var n_desc: u16 = 0; @@ -3339,14 +3461,7 @@ pub fn updateDeclExports( // Symbol's n_type is like for a symbol with strong linkage. n_desc |= macho.N_WEAK_DEF; }, - .LinkOnce => { - try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.count() + 1); - module.failed_exports.putAssumeCapacityNoClobber( - exp, - try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: GlobalLinkage.LinkOnce", .{}), - ); - continue; - }, + else => unreachable, } const global_sym_index = if (exp.link.macho.sym_index) |i| i else blk: { @@ -3356,8 +3471,6 @@ pub fn updateDeclExports( }; break :blk i; }; - - const n_strx = try self.makeString(exp_name); const sym = &self.globals.items[global_sym_index]; sym.* = .{ .n_strx = try self.makeString(exp_name), @@ -3368,12 +3481,11 @@ pub fn updateDeclExports( }; exp.link.macho.sym_index = global_sym_index; - const resolv = try self.symbol_resolver.getOrPut(self.base.allocator, n_strx); - resolv.value_ptr.* = .{ + try self.symbol_resolver.putNoClobber(self.base.allocator, n_strx, .{ .where = .global, .where_index = global_sym_index, .local_sym_index = decl.link.macho.local_sym_index, - }; + }); } } @@ -3381,8 +3493,11 @@ pub fn deleteExport(self: *MachO, exp: Export) void { const sym_index = exp.sym_index orelse return; self.globals_free_list.append(self.base.allocator, sym_index) catch {}; const global = &self.globals.items[sym_index]; - global.n_type = 0; + log.debug("deleting export '{s}': {}", .{ self.getString(global.n_strx), global }); assert(self.symbol_resolver.remove(global.n_strx)); + global.n_type = 0; + global.n_strx = 0; + global.n_value = 0; } pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { @@ -4403,6 +4518,7 @@ fn writeDyldInfoData(self: *MachO) !void { const base_address = text_segment.inner.vmaddr; for (self.globals.items) |sym| { + if (sym.n_type == 0) continue; const sym_name = self.getString(sym.n_strx); log.debug(" (putting '{s}' defined at 0x{x})", .{ sym_name, sym.n_value }); @@ -4655,7 +4771,7 @@ fn writeSymbolTable(self: *MachO) !void { .n_value = object.mtime orelse 0, }); - for (object.atoms.items) |atom| { + for (object.contained_atoms.items) |atom| { if (atom.stab) |stab| { const nlists = try stab.asNlists(atom.local_sym_index, self); defer self.base.allocator.free(nlists); diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index 298855934e..6dbe853451 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -645,7 +645,6 @@ const RelocContext = struct { allocator: *Allocator, object: *Object, macho_file: *MachO, - parsed_atoms: *Object.ParsedAtoms, }; fn initRelocFromObject(rel: macho.relocation_info, context: RelocContext) !Relocation { @@ -877,12 +876,16 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC .sect = context.macho_file.got_section_index.?, }; - if (context.parsed_atoms.getPtr(match)) |last| { + if (!context.object.start_atoms.contains(match)) { + try context.object.start_atoms.putNoClobber(context.allocator, match, atom); + } + + if (context.object.end_atoms.getPtr(match)) |last| { last.*.next = atom; atom.prev = last.*; last.* = atom; } else { - try context.parsed_atoms.putNoClobber(match, atom); + try context.object.end_atoms.putNoClobber(context.allocator, match, atom); } } else if (parsed_rel.payload == .unsigned) { switch (parsed_rel.where) { @@ -939,52 +942,63 @@ pub fn parseRelocs(self: *Atom, relocs: []macho.relocation_info, context: RelocC if (parsed_rel.where != .undef) break :blk; if (context.macho_file.stubs_map.contains(parsed_rel.where_index)) break :blk; - const stub_helper_atom = try context.macho_file.createStubHelperAtom(); - const laptr_atom = try context.macho_file.createLazyPointerAtom( - stub_helper_atom.local_sym_index, - parsed_rel.where_index, - ); - const stub_atom = try context.macho_file.createStubAtom(laptr_atom.local_sym_index); - try context.macho_file.stubs_map.putNoClobber(context.allocator, parsed_rel.where_index, stub_atom); // TODO clean this up! - if (context.parsed_atoms.getPtr(.{ - .seg = context.macho_file.text_segment_cmd_index.?, - .sect = context.macho_file.stub_helper_section_index.?, - })) |last| { - last.*.next = stub_helper_atom; - stub_helper_atom.prev = last.*; - last.* = stub_helper_atom; - } else { - try context.parsed_atoms.putNoClobber(.{ + const stub_helper_atom = atom: { + const atom = try context.macho_file.createStubHelperAtom(); + const match = MachO.MatchingSection{ .seg = context.macho_file.text_segment_cmd_index.?, .sect = context.macho_file.stub_helper_section_index.?, - }, stub_helper_atom); - } - if (context.parsed_atoms.getPtr(.{ - .seg = context.macho_file.text_segment_cmd_index.?, - .sect = context.macho_file.stubs_section_index.?, - })) |last| { - last.*.next = stub_atom; - stub_atom.prev = last.*; - last.* = stub_atom; - } else { - try context.parsed_atoms.putNoClobber(.{ - .seg = context.macho_file.text_segment_cmd_index.?, - .sect = context.macho_file.stubs_section_index.?, - }, stub_atom); - } - if (context.parsed_atoms.getPtr(.{ - .seg = context.macho_file.data_segment_cmd_index.?, - .sect = context.macho_file.la_symbol_ptr_section_index.?, - })) |last| { - last.*.next = laptr_atom; - laptr_atom.prev = last.*; - last.* = laptr_atom; - } else { - try context.parsed_atoms.putNoClobber(.{ + }; + if (!context.object.start_atoms.contains(match)) { + try context.object.start_atoms.putNoClobber(context.allocator, match, atom); + } + if (context.object.end_atoms.getPtr(match)) |last| { + last.*.next = atom; + atom.prev = last.*; + last.* = atom; + } else { + try context.object.end_atoms.putNoClobber(context.allocator, match, atom); + } + break :atom atom; + }; + const laptr_atom = atom: { + const atom = try context.macho_file.createLazyPointerAtom( + stub_helper_atom.local_sym_index, + parsed_rel.where_index, + ); + const match = MachO.MatchingSection{ .seg = context.macho_file.data_segment_cmd_index.?, .sect = context.macho_file.la_symbol_ptr_section_index.?, - }, laptr_atom); + }; + if (!context.object.start_atoms.contains(match)) { + try context.object.start_atoms.putNoClobber(context.allocator, match, atom); + } + if (context.object.end_atoms.getPtr(match)) |last| { + last.*.next = atom; + atom.prev = last.*; + last.* = atom; + } else { + try context.object.end_atoms.putNoClobber(context.allocator, match, atom); + } + break :atom atom; + }; + { + const atom = try context.macho_file.createStubAtom(laptr_atom.local_sym_index); + const match = MachO.MatchingSection{ + .seg = context.macho_file.text_segment_cmd_index.?, + .sect = context.macho_file.stubs_section_index.?, + }; + if (!context.object.start_atoms.contains(match)) { + try context.object.start_atoms.putNoClobber(context.allocator, match, atom); + } + if (context.object.end_atoms.getPtr(match)) |last| { + last.*.next = atom; + atom.prev = last.*; + last.* = atom; + } else { + try context.object.end_atoms.putNoClobber(context.allocator, match, atom); + } + try context.macho_file.stubs_map.putNoClobber(context.allocator, parsed_rel.where_index, atom); } } } diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 27da019be8..12c480b0f1 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -31,14 +31,12 @@ header: ?macho.mach_header_64 = null, load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, segment_cmd_index: ?u16 = null, +text_section_index: ?u16 = null, symtab_cmd_index: ?u16 = null, dysymtab_cmd_index: ?u16 = null, build_version_cmd_index: ?u16 = null, data_in_code_cmd_index: ?u16 = null, -text_section_index: ?u16 = null, -mod_init_func_section_index: ?u16 = null, - // __DWARF segment sections dwarf_debug_info_index: ?u16 = null, dwarf_debug_abbrev_index: ?u16 = null, @@ -56,7 +54,9 @@ tu_name: ?[]const u8 = null, tu_comp_dir: ?[]const u8 = null, mtime: ?u64 = null, -atoms: std.ArrayListUnmanaged(*Atom) = .{}, +contained_atoms: std.ArrayListUnmanaged(*Atom) = .{}, +start_atoms: std.AutoHashMapUnmanaged(MachO.MatchingSection, *Atom) = .{}, +end_atoms: std.AutoHashMapUnmanaged(MachO.MatchingSection, *Atom) = .{}, sections_as_symbols: std.AutoHashMapUnmanaged(u16, u32) = .{}, // TODO symbol mapping and its inverse can probably be simple arrays @@ -138,12 +138,15 @@ pub fn deinit(self: *Object, allocator: *Allocator) void { self.data_in_code_entries.deinit(allocator); self.symtab.deinit(allocator); self.strtab.deinit(allocator); - self.atoms.deinit(allocator); self.sections_as_symbols.deinit(allocator); self.symbol_mapping.deinit(allocator); self.reverse_symbol_mapping.deinit(allocator); allocator.free(self.name); + self.contained_atoms.deinit(allocator); + self.start_atoms.deinit(allocator); + self.end_atoms.deinit(allocator); + if (self.debug_info) |*db| { db.deinit(allocator); } @@ -157,6 +160,67 @@ pub fn deinit(self: *Object, allocator: *Allocator) void { } } +pub fn free(self: *Object, allocator: *Allocator, macho_file: *MachO) void { + log.debug("freeObject {*}", .{self}); + + var it = self.end_atoms.iterator(); + while (it.next()) |entry| { + const match = entry.key_ptr.*; + const first_atom = self.start_atoms.get(match).?; + const last_atom = entry.value_ptr.*; + var atom = first_atom; + + while (true) { + if (atom.local_sym_index != 0) { + macho_file.locals_free_list.append(allocator, atom.local_sym_index) catch {}; + const local = &macho_file.locals.items[atom.local_sym_index]; + local.n_type = 0; + atom.local_sym_index = 0; + } + if (atom == last_atom) { + break; + } + if (atom.next) |next| { + atom = next; + } else break; + } + } + + self.freeAtoms(macho_file); +} + +fn freeAtoms(self: *Object, macho_file: *MachO) void { + var it = self.end_atoms.iterator(); + while (it.next()) |entry| { + const match = entry.key_ptr.*; + var first_atom: *Atom = self.start_atoms.get(match).?; + var last_atom: *Atom = entry.value_ptr.*; + + if (macho_file.atoms.getPtr(match)) |atom_ptr| { + if (atom_ptr.* == last_atom) { + if (first_atom.prev) |prev| { + // TODO shrink the section size here + atom_ptr.* = prev; + } else { + _ = macho_file.atoms.fetchRemove(match); + } + } + } + + if (first_atom.prev) |prev| { + prev.next = last_atom.next; + } else { + first_atom.prev = null; + } + + if (last_atom.next) |next| { + next.prev = last_atom.prev; + } else { + last_atom.next = null; + } + } +} + pub fn parse(self: *Object, allocator: *Allocator, target: std.Target) !void { const reader = self.file.reader(); if (self.file_offset) |offset| { @@ -226,10 +290,6 @@ pub fn readLoadCommands(self: *Object, allocator: *Allocator, reader: anytype) ! if (mem.eql(u8, sectname, "__text")) { self.text_section_index = index; } - } else if (mem.eql(u8, segname, "__DATA")) { - if (mem.eql(u8, sectname, "__mod_init_func")) { - self.mod_init_func_section_index = index; - } } sect.offset += offset; @@ -320,7 +380,6 @@ const Context = struct { object: *Object, macho_file: *MachO, match: MachO.MatchingSection, - parsed_atoms: *ParsedAtoms, }; const AtomParser = struct { @@ -437,7 +496,6 @@ const AtomParser = struct { .allocator = context.allocator, .object = context.object, .macho_file = context.macho_file, - .parsed_atoms = context.parsed_atoms, }); if (context.macho_file.has_dices) { @@ -463,18 +521,10 @@ const AtomParser = struct { } }; -pub const ParsedAtoms = std.AutoHashMap(MachO.MatchingSection, *Atom); - -pub fn parseIntoAtoms( - self: *Object, - allocator: *Allocator, - object_id: u16, - macho_file: *MachO, -) !ParsedAtoms { +pub fn parseIntoAtoms(self: *Object, allocator: *Allocator, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); - var parsed_atoms = ParsedAtoms.init(allocator); const seg = self.load_commands.items[self.segment_cmd_index.?].Segment; log.debug("analysing {s}", .{self.name}); @@ -540,16 +590,6 @@ pub fn parseIntoAtoms( // Symbols within this section only. const filtered_nlists = NlistWithIndex.filterInSection(sorted_nlists, sect); - // TODO rewrite and re-enable dead-code stripping optimisation. I think it might make sense - // to do this in a standalone pass after we parse the sections as atoms. - // In release mode, if the object file was generated with dead code stripping optimisations, - // note it now and parse sections as atoms. - // const is_splittable = blk: { - // if (macho_file.base.options.optimize_mode == .Debug) break :blk false; - // break :blk self.header.?.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0; - // }; - const is_splittable = false; - macho_file.has_dices = macho_file.has_dices or blk: { if (self.text_section_index) |index| { if (index != id) break :blk false; @@ -560,237 +600,108 @@ pub fn parseIntoAtoms( }; macho_file.has_stabs = macho_file.has_stabs or self.debug_info != null; - next: { - if (is_splittable) atoms: { - if (filtered_nlists.len == 0) break :atoms; + // Since there is no symbol to refer to this atom, we create + // a temp one, unless we already did that when working out the relocations + // of other atoms. + const sym_name = try std.fmt.allocPrint(allocator, "l_{s}_{s}_{s}", .{ + self.name, + segmentName(sect), + sectionName(sect), + }); + defer allocator.free(sym_name); - // If the first nlist does not match the start of the section, - // then we need to encapsulate the memory range [section start, first symbol) - // as a temporary symbol and insert the matching Atom. - const first_nlist = filtered_nlists[0].nlist; - if (first_nlist.n_value > sect.addr) { - const sym_name = try std.fmt.allocPrint(allocator, "l_{s}_{s}_{s}", .{ - self.name, - segmentName(sect), - sectionName(sect), - }); - defer allocator.free(sym_name); - - const atom_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: { - const atom_local_sym_index = @intCast(u32, macho_file.locals.items.len); - try macho_file.locals.append(allocator, .{ - .n_strx = try macho_file.makeString(sym_name), - .n_type = macho.N_SECT, - .n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1), - .n_desc = 0, - .n_value = 0, - }); - try self.sections_as_symbols.putNoClobber(allocator, sect_id, atom_local_sym_index); - break :blk atom_local_sym_index; - }; - const atom_code = code[0 .. first_nlist.n_value - sect.addr]; - const atom_size = atom_code.len; - const atom = try macho_file.createEmptyAtom(atom_local_sym_index, atom_size, sect.@"align"); - - const is_zerofill = blk: { - const section_type = commands.sectionType(sect); - break :blk section_type == macho.S_ZEROFILL or section_type == macho.S_THREAD_LOCAL_ZEROFILL; - }; - if (!is_zerofill) { - mem.copy(u8, atom.code.items, atom_code); - } - - try atom.parseRelocs(relocs, .{ - .base_addr = sect.addr, - .base_offset = 0, - .allocator = allocator, - .object = self, - .macho_file = macho_file, - .parsed_atoms = &parsed_atoms, - }); - - if (macho_file.has_dices) { - const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + atom_size); - try atom.dices.ensureTotalCapacity(allocator, dices.len); - - for (dices) |dice| { - atom.dices.appendAssumeCapacity(.{ - .offset = dice.offset - try math.cast(u32, sect.addr), - .length = dice.length, - .kind = dice.kind, - }); - } - } - - if (parsed_atoms.getPtr(match)) |last| { - last.*.next = atom; - atom.prev = last.*; - last.* = atom; - } else { - try parsed_atoms.putNoClobber(match, atom); - } - try self.atoms.append(allocator, atom); - } - - var parser = AtomParser{ - .section = sect, - .code = code, - .relocs = relocs, - .nlists = filtered_nlists, - }; - - while (try parser.next(.{ - .allocator = allocator, - .object = self, - .macho_file = macho_file, - .match = match, - .parsed_atoms = &parsed_atoms, - })) |atom| { - const sym = macho_file.locals.items[atom.local_sym_index]; - const is_ext = blk: { - const orig_sym_id = self.reverse_symbol_mapping.get(atom.local_sym_index) orelse unreachable; - break :blk MachO.symbolIsExt(self.symtab.items[orig_sym_id]); - }; - if (is_ext) { - if (macho_file.symbol_resolver.get(sym.n_strx)) |resolv| { - assert(resolv.where == .global); - if (resolv.file != object_id) { - log.debug("deduping definition of {s} in {s}", .{ - macho_file.getString(sym.n_strx), - self.name, - }); - log.debug(" already defined in {s}", .{ - macho_file.objects.items[resolv.file].name, - }); - continue; - } - } - } - - if (sym.n_value == sect.addr) { - if (self.sections_as_symbols.get(sect_id)) |alias| { - // In x86_64 relocs, it can so happen that the compiler refers to the same - // atom by both the actual assigned symbol and the start of the section. In this - // case, we need to link the two together so add an alias. - try atom.aliases.append(allocator, alias); - } - } - - if (parsed_atoms.getPtr(match)) |last| { - last.*.next = atom; - atom.prev = last.*; - last.* = atom; - } else { - try parsed_atoms.putNoClobber(match, atom); - } - try self.atoms.append(allocator, atom); - } - - break :next; - } - - // Since there is no symbol to refer to this atom, we create - // a temp one, unless we already did that when working out the relocations - // of other atoms. - const sym_name = try std.fmt.allocPrint(allocator, "l_{s}_{s}_{s}", .{ - self.name, - segmentName(sect), - sectionName(sect), + const atom_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: { + const atom_local_sym_index = @intCast(u32, macho_file.locals.items.len); + try macho_file.locals.append(allocator, .{ + .n_strx = try macho_file.makeString(sym_name), + .n_type = macho.N_SECT, + .n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1), + .n_desc = 0, + .n_value = 0, }); - defer allocator.free(sym_name); + try self.sections_as_symbols.putNoClobber(allocator, sect_id, atom_local_sym_index); + break :blk atom_local_sym_index; + }; + const atom = try macho_file.createEmptyAtom(atom_local_sym_index, sect.size, sect.@"align"); - const atom_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: { - const atom_local_sym_index = @intCast(u32, macho_file.locals.items.len); - try macho_file.locals.append(allocator, .{ - .n_strx = try macho_file.makeString(sym_name), - .n_type = macho.N_SECT, - .n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1), - .n_desc = 0, - .n_value = 0, - }); - try self.sections_as_symbols.putNoClobber(allocator, sect_id, atom_local_sym_index); - break :blk atom_local_sym_index; - }; - const atom = try macho_file.createEmptyAtom(atom_local_sym_index, sect.size, sect.@"align"); - - const is_zerofill = blk: { - const section_type = commands.sectionType(sect); - break :blk section_type == macho.S_ZEROFILL or section_type == macho.S_THREAD_LOCAL_ZEROFILL; - }; - if (!is_zerofill) { - mem.copy(u8, atom.code.items, code); - } - - try atom.parseRelocs(relocs, .{ - .base_addr = sect.addr, - .base_offset = 0, - .allocator = allocator, - .object = self, - .macho_file = macho_file, - .parsed_atoms = &parsed_atoms, - }); - - if (macho_file.has_dices) { - const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + sect.size); - try atom.dices.ensureTotalCapacity(allocator, dices.len); - - for (dices) |dice| { - atom.dices.appendAssumeCapacity(.{ - .offset = dice.offset - try math.cast(u32, sect.addr), - .length = dice.length, - .kind = dice.kind, - }); - } - } - - // Since this is atom gets a helper local temporary symbol that didn't exist - // in the object file which encompasses the entire section, we need traverse - // the filtered symbols and note which symbol is contained within so that - // we can properly allocate addresses down the line. - // While we're at it, we need to update segment,section mapping of each symbol too. - try atom.contained.ensureTotalCapacity(allocator, filtered_nlists.len); - - for (filtered_nlists) |nlist_with_index| { - const nlist = nlist_with_index.nlist; - const local_sym_index = self.symbol_mapping.get(nlist_with_index.index) orelse unreachable; - const local = &macho_file.locals.items[local_sym_index]; - local.n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1); - - const stab: ?Atom.Stab = if (self.debug_info) |di| blk: { - // TODO there has to be a better to handle this. - for (di.inner.func_list.items) |func| { - if (func.pc_range) |range| { - if (nlist.n_value >= range.start and nlist.n_value < range.end) { - break :blk Atom.Stab{ - .function = range.end - range.start, - }; - } - } - } - // TODO - // if (zld.globals.contains(zld.getString(sym.strx))) break :blk .global; - break :blk .static; - } else null; - - atom.contained.appendAssumeCapacity(.{ - .local_sym_index = local_sym_index, - .offset = nlist.n_value - sect.addr, - .stab = stab, - }); - } - - if (parsed_atoms.getPtr(match)) |last| { - last.*.next = atom; - atom.prev = last.*; - last.* = atom; - } else { - try parsed_atoms.putNoClobber(match, atom); - } - try self.atoms.append(allocator, atom); + const is_zerofill = blk: { + const section_type = commands.sectionType(sect); + break :blk section_type == macho.S_ZEROFILL or section_type == macho.S_THREAD_LOCAL_ZEROFILL; + }; + if (!is_zerofill) { + mem.copy(u8, atom.code.items, code); } - } - return parsed_atoms; + try atom.parseRelocs(relocs, .{ + .base_addr = sect.addr, + .base_offset = 0, + .allocator = allocator, + .object = self, + .macho_file = macho_file, + }); + + if (macho_file.has_dices) { + const dices = filterDice(self.data_in_code_entries.items, sect.addr, sect.addr + sect.size); + try atom.dices.ensureTotalCapacity(allocator, dices.len); + + for (dices) |dice| { + atom.dices.appendAssumeCapacity(.{ + .offset = dice.offset - try math.cast(u32, sect.addr), + .length = dice.length, + .kind = dice.kind, + }); + } + } + + // Since this is atom gets a helper local temporary symbol that didn't exist + // in the object file which encompasses the entire section, we need traverse + // the filtered symbols and note which symbol is contained within so that + // we can properly allocate addresses down the line. + // While we're at it, we need to update segment,section mapping of each symbol too. + try atom.contained.ensureTotalCapacity(allocator, filtered_nlists.len); + + for (filtered_nlists) |nlist_with_index| { + const nlist = nlist_with_index.nlist; + const local_sym_index = self.symbol_mapping.get(nlist_with_index.index) orelse unreachable; + const local = &macho_file.locals.items[local_sym_index]; + local.n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1); + + const stab: ?Atom.Stab = if (self.debug_info) |di| blk: { + // TODO there has to be a better to handle this. + for (di.inner.func_list.items) |func| { + if (func.pc_range) |range| { + if (nlist.n_value >= range.start and nlist.n_value < range.end) { + break :blk Atom.Stab{ + .function = range.end - range.start, + }; + } + } + } + // TODO + // if (zld.globals.contains(zld.getString(sym.strx))) break :blk .global; + break :blk .static; + } else null; + + atom.contained.appendAssumeCapacity(.{ + .local_sym_index = local_sym_index, + .offset = nlist.n_value - sect.addr, + .stab = stab, + }); + } + + if (!self.start_atoms.contains(match)) { + try self.start_atoms.putNoClobber(allocator, match, atom); + } + + if (self.end_atoms.getPtr(match)) |last| { + last.*.next = atom; + atom.prev = last.*; + last.* = atom; + } else { + try self.end_atoms.putNoClobber(allocator, match, atom); + } + try self.contained_atoms.append(allocator, atom); + } } fn parseSymtab(self: *Object, allocator: *Allocator) !void { From 6f85a67987e187b8fdc29570d229bf5ad4f6a947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDiga=20=C5=BDeljko?= Date: Wed, 15 Sep 2021 21:05:40 +0200 Subject: [PATCH 013/160] stage2 Module: fix for 32 bit --- src/Module.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module.zig b/src/Module.zig index ec3bb2bbd3..24f917f939 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2372,7 +2372,7 @@ const data_has_safety_tag = @sizeOf(Zir.Inst.Data) != 8; // We need a better language feature for initializing a union with // a runtime known tag. const Stage1DataLayout = extern struct { - data: [8]u8 align(8), + data: [8]u8 align(@alignOf(Zir.Inst.Data)), safety_tag: u8, }; comptime { From dc214e041eda1461cc2317523e1656c5a0f55c1a Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Wed, 15 Sep 2021 22:42:19 -0700 Subject: [PATCH 014/160] std/special: fix 'zig test --test-evented-io Investigating hexops/zorex#4, I found that `--test-evented-io` is currently broken in the latest Zig nightly. See #9779 for a small reproduction. The issue is that allocation errors here are not correctly handled, as this function returns `void` and all other error cases `@panic`, the allocation failure should also use `@panic`. Fixes #9779 Helps hexops/zorex#4 Signed-off-by: Stephen Gutekanst --- lib/std/special/test_runner.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/special/test_runner.zig b/lib/std/special/test_runner.zig index 4ca627d133..c7375303e9 100644 --- a/lib/std/special/test_runner.zig +++ b/lib/std/special/test_runner.zig @@ -56,7 +56,7 @@ pub fn main() void { .evented => blk: { if (async_frame_buffer.len < size) { std.heap.page_allocator.free(async_frame_buffer); - async_frame_buffer = try std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size); + async_frame_buffer = std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size) catch @panic("out of memory"); } const casted_fn = @ptrCast(fn () callconv(.Async) anyerror!void, test_fn.func); break :blk await @asyncCall(async_frame_buffer, {}, casted_fn, .{}); From ab84ba39d03f59599ddfad91c00edf78a40edd70 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Sep 2021 11:21:01 -0700 Subject: [PATCH 015/160] move behavior test to "passing for stage2" section --- test/behavior.zig | 9 ++++----- test/behavior/atomics.zig | 8 ++++++++ test/behavior/atomics_stage1.zig | 8 -------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/test/behavior.zig b/test/behavior.zig index 366753c3bf..10d666f6a4 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -1,7 +1,6 @@ const builtin = @import("builtin"); test { - // Tests that pass for both. _ = @import("behavior/bool.zig"); _ = @import("behavior/basic.zig"); _ = @import("behavior/generics.zig"); @@ -13,8 +12,10 @@ test { _ = @import("behavior/usingnamespace.zig"); _ = @import("behavior/atomics.zig"); - if (!builtin.zig_is_stage2) { - // Tests that only pass for stage1. + if (builtin.zig_is_stage2) { + // When all comptime_memory.zig tests pass, #9646 can be closed. + // _ = @import("behavior/comptime_memory.zig"); + } else { _ = @import("behavior/align.zig"); _ = @import("behavior/alignof.zig"); _ = @import("behavior/array_stage1.zig"); @@ -91,8 +92,6 @@ test { _ = @import("behavior/byval_arg_var.zig"); _ = @import("behavior/call.zig"); _ = @import("behavior/cast_stage1.zig"); - // When these tests pass, #9646 can be closed. - // _ = @import("behavior/comptime_memory.zig"); _ = @import("behavior/const_slice_child.zig"); _ = @import("behavior/defer.zig"); _ = @import("behavior/enum.zig"); diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index 185108cabd..51cafdf564 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -73,3 +73,11 @@ test "cmpxchg with ptr" { try expect(@cmpxchgStrong(*i32, &x, &data3, &data2, .SeqCst, .SeqCst) == null); try expect(x == &data2); } + +test "cmpxchg with ignored result" { + var x: i32 = 1234; + + _ = @cmpxchgStrong(i32, &x, 1234, 5678, .Monotonic, .Monotonic); + + try expect(5678 == x); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index 2cdbb7b333..8c77ea75b2 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -28,14 +28,6 @@ fn test_u128_cmpxchg() !void { try expect(x == 42); } -test "cmpxchg with ignored result" { - var x: i32 = 1234; - - _ = @cmpxchgStrong(i32, &x, 1234, 5678, .Monotonic, .Monotonic); - - try expectEqual(@as(i32, 5678), x); -} - var a_global_variable = @as(u32, 1234); test "cmpxchg on a global variable" { From 2c8b201d057b556edaf8688a4e53b25cd90198f3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Sep 2021 11:21:19 -0700 Subject: [PATCH 016/160] build: make -Dskip-stage2-tests not build stage2 for the test-toolchain step --- build.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index e2665557b8..1e10ef1b8c 100644 --- a/build.zig +++ b/build.zig @@ -105,7 +105,9 @@ pub fn build(b: *Builder) !void { exe.install(); exe.setBuildMode(mode); exe.setTarget(target); - toolchain_step.dependOn(&exe.step); + if (!skip_stage2_tests) { + toolchain_step.dependOn(&exe.step); + } b.default_step.dependOn(&exe.step); exe.single_threaded = single_threaded; From 8f8294a809f9d975735377e7bfcc2c47ccfc4cb7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Sep 2021 11:21:43 -0700 Subject: [PATCH 017/160] ci: linux: enable LLVM stage2 tests --- ci/azure/linux_script | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ci/azure/linux_script b/ci/azure/linux_script index 96676b928f..7554efeaf7 100755 --- a/ci/azure/linux_script +++ b/ci/azure/linux_script @@ -71,9 +71,7 @@ make $JOBS install release/bin/zig test ../test/behavior.zig -fno-stage1 -fLLVM -I ../test -release/bin/zig build test-toolchain -Denable-qemu -Denable-wasmtime -release/bin/zig build test-std -Denable-qemu -Denable-wasmtime -release/bin/zig build docs -Denable-qemu -Denable-wasmtime +release/bin/zig build test -Denable-qemu -Denable-wasmtime -Denable-llvm # Look for HTML errors. tidy -qe ../zig-cache/langref.html From 6d37ae95edc06f15e4e77f64e8e637dd5d269183 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Sep 2021 13:09:32 -0700 Subject: [PATCH 018/160] build.zig: support -Duse-zig-libcxx This supports the case when it is known that LLVM, Clang, LLD were built with Clang (or `zig c++`). This commit updates the Linux CI script to pass this since we build using a zig tarball. --- build.zig | 52 ++++++++++++++++++++++++------------------- ci/azure/linux_script | 2 +- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/build.zig b/build.zig index 1e10ef1b8c..0fec584a27 100644 --- a/build.zig +++ b/build.zig @@ -18,6 +18,7 @@ pub fn build(b: *Builder) !void { const mode = b.standardReleaseOptions(); const target = b.standardTargetOptions(.{}); const single_threaded = b.option(bool, "single-threaded", "Build artifacts that run in single threaded mode") orelse false; + const use_zig_libcxx = b.option(bool, "use-zig-libcxx", "If libc++ is needed, use zig's bundled version, don't try to integrate with the system") orelse false; var docgen_exe = b.addExecutable("docgen", "doc/docgen.zig"); docgen_exe.single_threaded = single_threaded; @@ -160,8 +161,8 @@ pub fn build(b: *Builder) !void { b.addSearchPrefix(cfg.cmake_prefix_path); } - try addCmakeCfgOptionsToExe(b, cfg, exe); - try addCmakeCfgOptionsToExe(b, cfg, test_stage2); + try addCmakeCfgOptionsToExe(b, cfg, exe, use_zig_libcxx); + try addCmakeCfgOptionsToExe(b, cfg, test_stage2, use_zig_libcxx); } else { // Here we are -Denable-llvm but no cmake integration. try addStaticLlvmOptionsToExe(exe); @@ -408,6 +409,7 @@ fn addCmakeCfgOptionsToExe( b: *Builder, cfg: CMakeConfig, exe: *std.build.LibExeObjStep, + use_zig_libcxx: bool, ) !void { exe.addObjectFile(fs.path.join(b.allocator, &[_][]const u8{ cfg.cmake_binary_dir, @@ -420,28 +422,32 @@ fn addCmakeCfgOptionsToExe( addCMakeLibraryList(exe, cfg.lld_libraries); addCMakeLibraryList(exe, cfg.llvm_libraries); - const need_cpp_includes = true; + if (use_zig_libcxx) { + exe.linkLibCpp(); + } else { + const need_cpp_includes = true; - // System -lc++ must be used because in this code path we are attempting to link - // against system-provided LLVM, Clang, LLD. - if (exe.target.getOsTag() == .linux) { - // First we try to static link against gcc libstdc++. If that doesn't work, - // we fall back to -lc++ and cross our fingers. - addCxxKnownPath(b, cfg, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) { - error.RequiredLibraryNotFound => { - exe.linkSystemLibrary("c++"); - }, - else => |e| return e, - }; - exe.linkSystemLibrary("unwind"); - } else if (exe.target.isFreeBSD()) { - try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes); - exe.linkSystemLibrary("pthread"); - } else if (exe.target.getOsTag() == .openbsd) { - try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes); - try addCxxKnownPath(b, cfg, exe, "libc++abi.a", null, need_cpp_includes); - } else if (exe.target.isDarwin()) { - exe.linkSystemLibrary("c++"); + // System -lc++ must be used because in this code path we are attempting to link + // against system-provided LLVM, Clang, LLD. + if (exe.target.getOsTag() == .linux) { + // First we try to static link against gcc libstdc++. If that doesn't work, + // we fall back to -lc++ and cross our fingers. + addCxxKnownPath(b, cfg, exe, "libstdc++.a", "", need_cpp_includes) catch |err| switch (err) { + error.RequiredLibraryNotFound => { + exe.linkSystemLibrary("c++"); + }, + else => |e| return e, + }; + exe.linkSystemLibrary("unwind"); + } else if (exe.target.isFreeBSD()) { + try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes); + exe.linkSystemLibrary("pthread"); + } else if (exe.target.getOsTag() == .openbsd) { + try addCxxKnownPath(b, cfg, exe, "libc++.a", null, need_cpp_includes); + try addCxxKnownPath(b, cfg, exe, "libc++abi.a", null, need_cpp_includes); + } else if (exe.target.isDarwin()) { + exe.linkSystemLibrary("c++"); + } } if (cfg.dia_guids_lib.len != 0) { diff --git a/ci/azure/linux_script b/ci/azure/linux_script index 7554efeaf7..8780cb50bb 100755 --- a/ci/azure/linux_script +++ b/ci/azure/linux_script @@ -71,7 +71,7 @@ make $JOBS install release/bin/zig test ../test/behavior.zig -fno-stage1 -fLLVM -I ../test -release/bin/zig build test -Denable-qemu -Denable-wasmtime -Denable-llvm +release/bin/zig build test -Denable-qemu -Denable-wasmtime -Denable-llvm -Duse-zig-libcxx # Look for HTML errors. tidy -qe ../zig-cache/langref.html From d11f42c2b24e80388e1ea648ee97fae612fd76a4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Sep 2021 16:37:21 -0700 Subject: [PATCH 019/160] zig cc: support -S and -emit-llvm CLI parameters closes #6425 --- src/Compilation.zig | 96 ++++++++++++++++++++++++++-------- src/clang_options_data.zig | 9 +++- src/main.zig | 35 ++++++++++--- tools/update_clang_options.zig | 4 ++ 4 files changed, 116 insertions(+), 28 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index b13aabe272..cc72275293 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -849,10 +849,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (options.use_llvm) |explicit| break :blk explicit; - // If we have no zig code to compile, no need for LLVM. - if (options.main_pkg == null) - break :blk false; - // If we are outputting .c code we must use Zig backend. if (ofmt == .c) break :blk false; @@ -861,6 +857,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) break :blk true; + // If we have no zig code to compile, no need for LLVM. + if (options.main_pkg == null) + break :blk false; + // The stage1 compiler depends on the stage1 C++ LLVM backend // to compile zig code. if (use_stage1) @@ -876,9 +876,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (options.use_llvm == true) { return error.ZigCompilerNotBuiltWithLLVMExtensions; } - if (options.machine_code_model != .default) { - return error.MachineCodeModelNotSupportedWithoutLlvm; - } if (options.emit_llvm_ir != null or options.emit_llvm_bc != null) { return error.EmittingLlvmModuleRequiresUsingLlvmBackend; } @@ -1793,6 +1790,10 @@ pub fn update(self: *Compilation) !void { } } + // Flush takes care of -femit-bin, but we still have -femit-llvm-ir, -femit-llvm-bc, and + // -femit-asm to handle, in the case of C objects. + try self.emitOthers(); + // 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. // The ZIR needs to stay loaded in memory because (1) Decl objects contain references @@ -1808,6 +1809,37 @@ pub fn update(self: *Compilation) !void { } } +fn emitOthers(comp: *Compilation) !void { + if (comp.bin_file.options.output_mode != .Obj or comp.bin_file.options.module != null or + comp.c_object_table.count() == 0) + { + return; + } + const obj_path = comp.c_object_table.keys()[0].status.success.object_path; + const cwd = std.fs.cwd(); + const ext = std.fs.path.extension(obj_path); + const basename = obj_path[0 .. obj_path.len - ext.len]; + // This obj path always ends with the object file extension, but if we change the + // extension to .ll, .bc, or .s, then it will be the path to those things. + const outs = [_]struct { + emit: ?EmitLoc, + ext: []const u8, + }{ + .{ .emit = comp.emit_asm, .ext = ".s" }, + .{ .emit = comp.emit_llvm_ir, .ext = ".ll" }, + .{ .emit = comp.emit_llvm_bc, .ext = ".bc" }, + }; + for (outs) |out| { + if (out.emit) |loc| { + if (loc.directory) |directory| { + const src_path = try std.fmt.allocPrint(comp.gpa, "{s}{s}", .{ basename, out.ext }); + defer comp.gpa.free(src_path); + try cwd.copyFile(src_path, directory.handle, loc.basename, .{}); + } + } + } +} + /// Having the file open for writing is problematic as far as executing the /// binary is concerned. This will remove the write flag, or close the file, /// or whatever is needed so that it can be executed. @@ -2764,6 +2796,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P defer man.deinit(); man.hash.add(comp.clang_preprocessor_mode); + man.hash.addOptionalEmitLoc(comp.emit_asm); + man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); + man.hash.addOptionalEmitLoc(comp.emit_llvm_bc); try man.hashCSource(c_object.src); @@ -2787,16 +2822,29 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P comp.bin_file.options.root_name else c_source_basename[0 .. c_source_basename.len - std.fs.path.extension(c_source_basename).len]; - const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ - o_basename_noext, - comp.bin_file.options.object_format.fileExt(comp.bin_file.options.target.cpu.arch), - }); + const o_ext = comp.bin_file.options.object_format.fileExt(comp.bin_file.options.target.cpu.arch); const digest = if (!comp.disable_c_depfile and try man.hit()) man.final() else blk: { var argv = std.ArrayList([]const u8).init(comp.gpa); defer argv.deinit(); - // We can't know the digest until we do the C compiler invocation, so we need a temporary filename. + // In case we are doing passthrough mode, we need to detect -S and -emit-llvm. + const out_ext = e: { + if (!comp.clang_passthrough_mode) + break :e o_ext; + if (comp.emit_asm != null) + break :e ".s"; + if (comp.emit_llvm_ir != null) + break :e ".ll"; + if (comp.emit_llvm_bc != null) + break :e ".bc"; + + break :e o_ext; + }; + const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, out_ext }); + + // We can't know the digest until we do the C compiler invocation, + // so we need a temporary filename. const out_obj_path = try comp.tmpFilePath(arena, o_basename); var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{}); defer zig_cache_tmp_dir.close(); @@ -2810,15 +2858,23 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P try std.fmt.allocPrint(arena, "{s}.d", .{out_obj_path}); try comp.addCCArgs(arena, &argv, ext, out_dep_path); - try argv.ensureCapacity(argv.items.len + 3); + try argv.ensureUnusedCapacity(6 + c_object.src.extra_flags.len); switch (comp.clang_preprocessor_mode) { .no => argv.appendSliceAssumeCapacity(&[_][]const u8{ "-c", "-o", out_obj_path }), .yes => argv.appendSliceAssumeCapacity(&[_][]const u8{ "-E", "-o", out_obj_path }), .stdout => argv.appendAssumeCapacity("-E"), } - - try argv.append(c_object.src.src_path); - try argv.appendSlice(c_object.src.extra_flags); + if (comp.clang_passthrough_mode) { + if (comp.emit_asm != null) { + argv.appendAssumeCapacity("-S"); + } else if (comp.emit_llvm_ir != null) { + argv.appendSliceAssumeCapacity(&[_][]const u8{ "-emit-llvm", "-S" }); + } else if (comp.emit_llvm_bc != null) { + argv.appendAssumeCapacity("-emit-llvm"); + } + } + argv.appendAssumeCapacity(c_object.src.src_path); + argv.appendSliceAssumeCapacity(c_object.src.extra_flags); if (comp.verbose_cc) { dump_argv(argv.items); @@ -2838,8 +2894,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P switch (term) { .Exited => |code| { if (code != 0) { - // TODO https://github.com/ziglang/zig/issues/6342 - std.process.exit(1); + std.process.exit(code); } if (comp.clang_preprocessor_mode == .stdout) std.process.exit(0); @@ -2855,9 +2910,6 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P const stderr_reader = child.stderr.?.reader(); - // TODO https://github.com/ziglang/zig/issues/6343 - // Please uncomment and use stdout once this issue is fixed - // const stdout = try stdout_reader.readAllAlloc(arena, std.math.maxInt(u32)); const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024); const term = child.wait() catch |err| { @@ -2907,6 +2959,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P break :blk digest; }; + const o_basename = try std.fmt.allocPrint(arena, "{s}{s}", .{ o_basename_noext, o_ext }); + c_object.status = .{ .success = .{ .object_path = try comp.local_cache_directory.join(comp.gpa, &[_][]const u8{ diff --git a/src/clang_options_data.zig b/src/clang_options_data.zig index 524374a7e9..ecaa383b69 100644 --- a/src/clang_options_data.zig +++ b/src/clang_options_data.zig @@ -2434,7 +2434,14 @@ flagpd1("emit-codegen-only"), flagpd1("emit-header-module"), flagpd1("emit-html"), flagpd1("emit-interface-stubs"), -flagpd1("emit-llvm"), +.{ + .name = "emit-llvm", + .syntax = .flag, + .zig_equivalent = .emit_llvm, + .pd1 = true, + .pd2 = false, + .psl = false, +}, flagpd1("emit-llvm-bc"), flagpd1("emit-llvm-only"), flagpd1("emit-llvm-uselists"), diff --git a/src/main.zig b/src/main.zig index 6a76c9507f..5774bf2a67 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1151,6 +1151,7 @@ fn buildOutputType( var is_shared_lib = false; var linker_args = std.ArrayList([]const u8).init(arena); var it = ClangArgIterator.init(arena, all_args); + var emit_llvm = false; while (it.has_next) { it.next() catch |err| { fatal("unable to parse command line parameters: {s}", .{@errorName(err)}); @@ -1161,6 +1162,7 @@ fn buildOutputType( .c => c_out_mode = .object, // -c .asm_only => c_out_mode = .assembly, // -S .preprocess_only => c_out_mode = .preprocessor, // -E + .emit_llvm => emit_llvm = true, .other => { try clang_argv.appendSlice(it.other_args); }, @@ -1518,22 +1520,42 @@ fn buildOutputType( output_mode = if (is_shared_lib) .Lib else .Exe; emit_bin = if (out_path) |p| .{ .yes = p } else EmitBin.yes_a_out; enable_cache = true; + if (emit_llvm) { + fatal("-emit-llvm cannot be used when linking", .{}); + } }, .object => { output_mode = .Obj; - if (out_path) |p| { - emit_bin = .{ .yes = p }; + if (emit_llvm) { + emit_bin = .no; + if (out_path) |p| { + emit_llvm_bc = .{ .yes = p }; + } else { + emit_llvm_bc = .yes_default_path; + } } else { - emit_bin = .yes_default_path; + if (out_path) |p| { + emit_bin = .{ .yes = p }; + } else { + emit_bin = .yes_default_path; + } } }, .assembly => { output_mode = .Obj; emit_bin = .no; - if (out_path) |p| { - emit_asm = .{ .yes = p }; + if (emit_llvm) { + if (out_path) |p| { + emit_llvm_ir = .{ .yes = p }; + } else { + emit_llvm_ir = .yes_default_path; + } } else { - emit_asm = .yes_default_path; + if (out_path) |p| { + emit_asm = .{ .yes = p }; + } else { + emit_asm = .yes_default_path; + } } }, .preprocessor => { @@ -3663,6 +3685,7 @@ pub const ClangArgIterator = struct { no_red_zone, strip, exec_model, + emit_llvm, }; const Args = struct { diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index 407e813ef3..2a0da66578 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -376,6 +376,10 @@ const known_options = [_]KnownOpt{ .name = "mexec-model", .ident = "exec_model", }, + .{ + .name = "emit-llvm", + .ident = "emit_llvm", + }, }; const blacklisted_options = [_][]const u8{}; From dc9d76b630e0fe9a465cec67dc4dc66e8cce7c58 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Sep 2021 16:40:06 -0700 Subject: [PATCH 020/160] ci: go back to passing state for linux This commit reverts 6d37ae95edc06f15e4e77f64e8e637dd5d269183 and 8f8294a809f9d975735377e7bfcc2c47ccfc4cb7. I don't know why they caused a failure but that investigation can happen while the CI is green. --- ci/azure/linux_script | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/azure/linux_script b/ci/azure/linux_script index 8780cb50bb..96676b928f 100755 --- a/ci/azure/linux_script +++ b/ci/azure/linux_script @@ -71,7 +71,9 @@ make $JOBS install release/bin/zig test ../test/behavior.zig -fno-stage1 -fLLVM -I ../test -release/bin/zig build test -Denable-qemu -Denable-wasmtime -Denable-llvm -Duse-zig-libcxx +release/bin/zig build test-toolchain -Denable-qemu -Denable-wasmtime +release/bin/zig build test-std -Denable-qemu -Denable-wasmtime +release/bin/zig build docs -Denable-qemu -Denable-wasmtime # Look for HTML errors. tidy -qe ../zig-cache/langref.html From dbe9a5114e2d56f847b674539ffa0d28fc57ea78 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Sep 2021 21:03:55 -0700 Subject: [PATCH 021/160] stage2: implement `@setAlignStack` and 128-bit cmpxchg * test runner is improved to respect `error.SkipZigTest` * start code is improved to `@setAlignStack(16)` before calling main() * the newly passing behavior test has a workaround for the fact that stage2 cannot yet call `std.Target.x86.featureSetHas()` at comptime. This is blocking on comptime closures. The workaround is that there is a new decl `@import("builtin").stage2_x86_cx16` which is a `bool`. * Implement `@setAlignStack`. This language feature should be re-evaluated at some point - I'll file an issue for it. * LLVM backend: apply/remove the cold attribute and noinline attribute where appropriate. * LLVM backend: loads and stores are properly annotated with alignment and volatile attributes. * LLVM backend: allocas are properly annotated with alignment. * Type: fix integers reporting wrong alignment for 256-bit integers and beyond. Once you get to 16 byte aligned, there is no further alignment for larger integers. --- lib/std/special/test_runner.zig | 10 +++- lib/std/start.zig | 5 ++ src/Compilation.zig | 5 ++ src/Module.zig | 22 ++++++++- src/Sema.zig | 47 ++++++++++++++++++- src/codegen/llvm.zig | 79 +++++++++++++++++++++++++------- src/codegen/llvm/bindings.zig | 9 ++++ src/type.zig | 6 ++- test/behavior/atomics.zig | 30 ++++++++++++ test/behavior/atomics_stage1.zig | 25 ---------- test/cases.zig | 2 +- test/stage2/darwin.zig | 4 +- 12 files changed, 195 insertions(+), 49 deletions(-) diff --git a/lib/std/special/test_runner.zig b/lib/std/special/test_runner.zig index c7375303e9..b762e7784e 100644 --- a/lib/std/special/test_runner.zig +++ b/lib/std/special/test_runner.zig @@ -123,8 +123,16 @@ pub fn log( } pub fn main2() anyerror!void { + var bad = false; // Simpler main(), exercising fewer language features, so that stage2 can handle it. for (builtin.test_functions) |test_fn| { - try test_fn.func(); + test_fn.func() catch |err| { + if (err != error.SkipZigTest) { + bad = true; + } + }; + } + if (bad) { + return error.TestsFailed; } } diff --git a/lib/std/start.zig b/lib/std/start.zig index d8e5796d37..057fde62f6 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -87,6 +87,11 @@ fn main2() callconv(.C) c_int { } fn _start2() callconv(.Naked) noreturn { + callMain2(); +} + +fn callMain2() noreturn { + @setAlignStack(16); root.main(); exit2(0); } diff --git a/src/Compilation.zig b/src/Compilation.zig index cc72275293..8edbb2dd73 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3714,6 +3714,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) Alloc const target = comp.getTarget(); const generic_arch_name = target.cpu.arch.genericName(); const use_stage1 = build_options.is_stage1 and comp.bin_file.options.use_stage1; + const stage2_x86_cx16 = target.cpu.arch == .x86_64 and + std.Target.x86.featureSetHas(target.cpu.features, .cx16); @setEvalBranchQuota(4000); try buffer.writer().print( @@ -3725,6 +3727,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) Alloc \\pub const zig_is_stage2 = {}; \\/// Temporary until self-hosted supports the `cpu.arch` value. \\pub const stage2_arch: std.Target.Cpu.Arch = .{}; + \\/// Temporary until self-hosted can call `std.Target.x86.featureSetHas` at comptime. + \\pub const stage2_x86_cx16 = {}; \\ \\pub const output_mode = std.builtin.OutputMode.{}; \\pub const link_mode = std.builtin.LinkMode.{}; @@ -3740,6 +3744,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) Alloc build_options.version, !use_stage1, std.zig.fmtId(@tagName(target.cpu.arch)), + stage2_x86_cx16, std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)), std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)), comp.bin_file.options.is_test, diff --git a/src/Module.zig b/src/Module.zig index 24f917f939..473c27e338 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -64,11 +64,16 @@ import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{}, /// The set of all the generic function instantiations. This is used so that when a generic /// function is called twice with the same comptime parameter arguments, both calls dispatch /// to the same function. +/// TODO: remove functions from this set when they are destroyed. monomorphed_funcs: MonomorphedFuncsSet = .{}, - /// The set of all comptime function calls that have been cached so that future calls /// with the same parameters will get the same return value. memoized_calls: MemoizedCallSet = .{}, +/// Contains the values from `@setAlignStack`. A sparse table is used here +/// instead of a field of `Fn` because usage of `@setAlignStack` is rare, while +/// functions are many. +/// TODO: remove functions from this set when they are destroyed. +align_stack_fns: std.AutoHashMapUnmanaged(*const Fn, SetAlignStack) = .{}, /// We optimize memory usage for a compilation with no compile errors by storing the /// error messages and mapping outside of `Decl`. @@ -215,6 +220,13 @@ pub const MemoizedCall = struct { } }; +pub const SetAlignStack = struct { + alignment: u32, + /// TODO: This needs to store a non-lazy source location for the case of an inline function + /// which does `@setAlignStack` (applying it to the caller). + src: LazySrcLoc, +}; + /// A `Module` has zero or one of these depending on whether `-femit-h` is enabled. pub const GlobalEmitH = struct { /// Where to put the output. @@ -881,6 +893,7 @@ pub const Fn = struct { state: Analysis, is_cold: bool = false, + is_noinline: bool = false, pub const Analysis = enum { queued, @@ -2347,6 +2360,7 @@ pub fn deinit(mod: *Module) void { mod.error_name_list.deinit(gpa); mod.test_functions.deinit(gpa); + mod.align_stack_fns.deinit(gpa); mod.monomorphed_funcs.deinit(gpa); { @@ -3977,6 +3991,12 @@ fn markOutdatedDecl(mod: *Module, decl: *Decl) !void { if (mod.failed_decls.fetchSwapRemove(decl)) |kv| { kv.value.destroy(mod.gpa); } + if (decl.has_tv and decl.owns_tv) { + if (decl.val.castTag(.function)) |payload| { + const func = payload.data; + _ = mod.align_stack_fns.remove(func); + } + } if (mod.emit_h) |emit_h| { if (emit_h.failed_decls.fetchSwapRemove(decl)) |kv| { kv.value.destroy(mod.gpa); diff --git a/src/Sema.zig b/src/Sema.zig index 19e0f41b98..58aa7ca1c0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -833,6 +833,7 @@ fn failWithUseOfUndef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) Compile /// Appropriate to call when the coercion has already been done by result /// location semantics. Asserts the value fits in the provided `Int` type. /// Only supports `Int` types 64 bits or less. +/// TODO don't ever call this since we're migrating towards ResultLoc.coerced_ty. fn resolveAlreadyCoercedInt( sema: *Sema, block: *Scope.Block, @@ -849,6 +850,23 @@ fn resolveAlreadyCoercedInt( } } +fn resolveAlign( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + zir_ref: Zir.Inst.Ref, +) !u16 { + const alignment_big = try sema.resolveInt(block, src, zir_ref, Type.initTag(.u16)); + const alignment = @intCast(u16, alignment_big); // We coerce to u16 in the prev line. + if (alignment == 0) return sema.mod.fail(&block.base, src, "alignment must be >= 1", .{}); + if (!std.math.isPowerOfTwo(alignment)) { + return sema.mod.fail(&block.base, src, "alignment value {d} is not a power of two", .{ + alignment, + }); + } + return alignment; +} + fn resolveInt( sema: *Sema, block: *Scope.Block, @@ -2285,9 +2303,36 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro } fn zirSetAlignStack(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { + const mod = sema.mod; const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const src: LazySrcLoc = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirSetAlignStack", .{}); + const alignment = try sema.resolveAlign(block, operand_src, inst_data.operand); + if (alignment > 256) { + return mod.fail(&block.base, src, "attempt to @setAlignStack({d}); maximum is 256", .{ + alignment, + }); + } + const func = sema.owner_func orelse + return mod.fail(&block.base, src, "@setAlignStack outside function body", .{}); + + switch (func.owner_decl.ty.fnCallingConvention()) { + .Naked => return mod.fail(&block.base, src, "@setAlignStack in naked function", .{}), + .Inline => return mod.fail(&block.base, src, "@setAlignStack in inline function", .{}), + else => {}, + } + + const gop = try mod.align_stack_fns.getOrPut(mod.gpa, func); + if (gop.found_existing) { + const msg = msg: { + const msg = try mod.errMsg(&block.base, src, "multiple @setAlignStack in the same function body", .{}); + errdefer msg.destroy(mod.gpa); + try mod.errNote(&block.base, src, msg, "other instance here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + } + gop.value_ptr.* = .{ .alignment = alignment, .src = src }; } fn zirSetCold(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5720756cc2..ecc1790e6d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -355,6 +355,20 @@ pub const Object = struct { const llvm_func = try dg.resolveLlvmFunction(decl); + if (module.align_stack_fns.get(func)) |align_info| { + dg.addFnAttrInt(llvm_func, "alignstack", align_info.alignment); + dg.addFnAttr(llvm_func, "noinline"); + } else { + DeclGen.removeFnAttr(llvm_func, "alignstack"); + if (!func.is_noinline) DeclGen.removeFnAttr(llvm_func, "noinline"); + } + + if (func.is_cold) { + dg.addFnAttr(llvm_func, "cold"); + } else { + DeclGen.removeFnAttr(llvm_func, "cold"); + } + // This gets the LLVM values from the function and stores them in `dg.args`. const fn_param_len = decl.ty.fnParamLen(); var args = try dg.gpa.alloc(*const llvm.Value, fn_param_len); @@ -512,7 +526,9 @@ pub const DeclGen = struct { } } - /// If the llvm function does not exist, create it + /// If the llvm function does not exist, create it. + /// Note that this can be called before the function's semantic analysis has + /// completed, so if any attributes rely on that, they must be done in updateFunc, not here. fn resolveLlvmFunction(self: *DeclGen, decl: *Module.Decl) !*const llvm.Value { if (self.llvmModule().getNamedFunction(decl.name)) |llvm_fn| return llvm_fn; @@ -895,17 +911,39 @@ pub const DeclGen = struct { } } - // Helper functions - fn addAttr(self: *DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { + fn addAttr(dg: *DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { + return dg.addAttrInt(val, index, name, 0); + } + + fn removeAttr(val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void { const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len); assert(kind_id != 0); - const llvm_attr = self.context.createEnumAttribute(kind_id, 0); + val.removeEnumAttributeAtIndex(index, kind_id); + } + + fn addAttrInt( + dg: *DeclGen, + val: *const llvm.Value, + index: llvm.AttributeIndex, + name: []const u8, + int: u64, + ) void { + const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len); + assert(kind_id != 0); + const llvm_attr = dg.context.createEnumAttribute(kind_id, int); val.addAttributeAtIndex(index, llvm_attr); } - fn addFnAttr(self: *DeclGen, val: *const llvm.Value, attr_name: []const u8) void { - // TODO: improve this API, `addAttr(-1, attr_name)` - self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name); + fn addFnAttr(dg: *DeclGen, val: *const llvm.Value, name: []const u8) void { + dg.addAttr(val, std.math.maxInt(llvm.AttributeIndex), name); + } + + fn removeFnAttr(fn_val: *const llvm.Value, name: []const u8) void { + removeAttr(fn_val, std.math.maxInt(llvm.AttributeIndex), name); + } + + fn addFnAttrInt(dg: *DeclGen, fn_val: *const llvm.Value, name: []const u8, int: u64) void { + return dg.addAttrInt(fn_val, std.math.maxInt(llvm.AttributeIndex), name, int); } /// If the operand type of an atomic operation is not byte sized we need to @@ -1975,12 +2013,13 @@ pub const FuncGen = struct { return null; // buildAlloca expects the pointee type, not the pointer type, so assert that // a Payload.PointerSimple is passed to the alloc instruction. - const inst_ty = self.air.typeOfIndex(inst); - const pointee_type = inst_ty.castPointer().?.data; - - // TODO: figure out a way to get the name of the var decl. - // TODO: set alignment and volatile - return self.buildAlloca(try self.dg.llvmType(pointee_type)); + const ptr_ty = self.air.typeOfIndex(inst); + const pointee_type = ptr_ty.elemType(); + const pointee_llvm_ty = try self.dg.llvmType(pointee_type); + const target = self.dg.module.getTarget(); + const alloca_inst = self.buildAlloca(pointee_llvm_ty); + alloca_inst.setAlignment(ptr_ty.ptrAlignment(target)); + return alloca_inst; } /// Use this instead of builder.buildAlloca, because this function makes sure to @@ -2200,8 +2239,11 @@ pub const FuncGen = struct { } fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) *const llvm.Value { - _ = ptr_ty; // TODO set volatile and alignment on this load properly - return self.builder.buildLoad(ptr, ""); + const llvm_inst = self.builder.buildLoad(ptr, ""); + const target = self.dg.module.getTarget(); + llvm_inst.setAlignment(ptr_ty.ptrAlignment(target)); + llvm_inst.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr())); + return llvm_inst; } fn store( @@ -2210,8 +2252,11 @@ pub const FuncGen = struct { ptr_ty: Type, elem: *const llvm.Value, ) *const llvm.Value { - _ = ptr_ty; // TODO set volatile and alignment on this store properly - return self.builder.buildStore(elem, ptr); + const llvm_inst = self.builder.buildStore(elem, ptr); + const target = self.dg.module.getTarget(); + llvm_inst.setAlignment(ptr_ty.ptrAlignment(target)); + llvm_inst.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr())); + return llvm_inst; } }; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 3bbc24e174..db1dcd22f2 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -85,6 +85,9 @@ pub const Value = opaque { pub const addAttributeAtIndex = LLVMAddAttributeAtIndex; extern fn LLVMAddAttributeAtIndex(*const Value, Idx: AttributeIndex, A: *const Attribute) void; + pub const removeEnumAttributeAtIndex = LLVMRemoveEnumAttributeAtIndex; + extern fn LLVMRemoveEnumAttributeAtIndex(F: *const Value, Idx: AttributeIndex, KindID: c_uint) void; + pub const getFirstBasicBlock = LLVMGetFirstBasicBlock; extern fn LLVMGetFirstBasicBlock(Fn: *const Value) ?*const BasicBlock; @@ -136,6 +139,12 @@ pub const Value = opaque { pub const setOrdering = LLVMSetOrdering; extern fn LLVMSetOrdering(MemoryAccessInst: *const Value, Ordering: AtomicOrdering) void; + + pub const setVolatile = LLVMSetVolatile; + extern fn LLVMSetVolatile(MemoryAccessInst: *const Value, IsVolatile: Bool) void; + + pub const setAlignment = LLVMSetAlignment; + extern fn LLVMSetAlignment(V: *const Value, Bytes: c_uint) void; }; pub const Type = opaque { diff --git a/src/type.zig b/src/type.zig index d9a641474f..ec73ae1196 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1583,7 +1583,11 @@ pub const Type = extern union { .int_signed, .int_unsigned => { const bits: u16 = self.cast(Payload.Bits).?.data; - return std.math.ceilPowerOfTwoPromote(u16, (bits + 7) / 8); + if (bits <= 8) return 1; + if (bits <= 16) return 2; + if (bits <= 32) return 4; + if (bits <= 64) return 8; + return 16; }, .optional => { diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index 51cafdf564..aae187739b 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -81,3 +81,33 @@ test "cmpxchg with ignored result" { try expect(5678 == x); } + +test "128-bit cmpxchg" { + try test_u128_cmpxchg(); + comptime try test_u128_cmpxchg(); +} + +fn test_u128_cmpxchg() !void { + if (builtin.zig_is_stage2) { + if (builtin.stage2_arch != .x86_64) return error.SkipZigTest; + if (!builtin.stage2_x86_cx16) return error.SkipZigTest; + } else { + if (builtin.cpu.arch != .x86_64) return error.SkipZigTest; + if (comptime !std.Target.x86.featureSetHas(builtin.cpu.features, .cx16)) return error.SkipZigTest; + } + + var x: u128 = 1234; + if (@cmpxchgWeak(u128, &x, 99, 5678, .SeqCst, .SeqCst)) |x1| { + try expect(x1 == 1234); + } else { + @panic("cmpxchg should have failed"); + } + + while (@cmpxchgWeak(u128, &x, 1234, 5678, .SeqCst, .SeqCst)) |x1| { + try expect(x1 == 1234); + } + try expect(x == 5678); + + try expect(@cmpxchgStrong(u128, &x, 5678, 42, .SeqCst, .SeqCst) == null); + try expect(x == 42); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index 8c77ea75b2..c9e0ea1a60 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -3,31 +3,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const builtin = @import("builtin"); -test "128-bit cmpxchg" { - try test_u128_cmpxchg(); - comptime try test_u128_cmpxchg(); -} - -fn test_u128_cmpxchg() !void { - if (std.Target.current.cpu.arch != .x86_64) return error.SkipZigTest; - if (comptime !std.Target.x86.featureSetHas(std.Target.current.cpu.features, .cx16)) return error.SkipZigTest; - - var x: u128 = 1234; - if (@cmpxchgWeak(u128, &x, 99, 5678, .SeqCst, .SeqCst)) |x1| { - try expect(x1 == 1234); - } else { - @panic("cmpxchg should have failed"); - } - - while (@cmpxchgWeak(u128, &x, 1234, 5678, .SeqCst, .SeqCst)) |x1| { - try expect(x1 == 1234); - } - try expect(x == 5678); - - try expect(@cmpxchgStrong(u128, &x, 5678, 42, .SeqCst, .SeqCst) == null); - try expect(x == 42); -} - var a_global_variable = @as(u32, 1234); test "cmpxchg on a global variable" { diff --git a/test/cases.zig b/test/cases.zig index 64fe39e07b..59f0ef7146 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -26,7 +26,7 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("hello world with updates", linux_x64); case.addError("", &[_][]const u8{ - ":90:9: error: struct 'tmp.tmp' has no member named 'main'", + ":95:9: error: struct 'tmp.tmp' has no member named 'main'", }); // Incorrect return type diff --git a/test/stage2/darwin.zig b/test/stage2/darwin.zig index 86c2d313a0..959313f021 100644 --- a/test/stage2/darwin.zig +++ b/test/stage2/darwin.zig @@ -12,9 +12,9 @@ pub fn addCases(ctx: *TestContext) !void { .os_tag = .macos, }; { - var case = ctx.exe("hello world with updates", target); + var case = ctx.exe("darwin hello world with updates", target); case.addError("", &[_][]const u8{ - ":90:9: error: struct 'tmp.tmp' has no member named 'main'", + ":95:9: error: struct 'tmp.tmp' has no member named 'main'", }); // Incorrect return type From 091a98f524b41cac1fb299cdfdf911ae3b31c6ae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Sep 2021 21:43:01 -0700 Subject: [PATCH 022/160] stage2: fix global variables with inferred type Also, when a global variable does have a type, perform coercion on it. --- src/Sema.zig | 21 +++++++++++++++++---- test/behavior/atomics.zig | 7 +++++++ test/behavior/atomics_stage1.zig | 7 ------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 58aa7ca1c0..645e68c6ef 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7920,7 +7920,6 @@ fn zirVarExtended( const mut_src: LazySrcLoc = src; // TODO add a LazySrcLoc that points at mut token const init_src: LazySrcLoc = src; // TODO add a LazySrcLoc that points at init expr const small = @bitCast(Zir.Inst.ExtendedVar.Small, extended.small); - const var_ty = try sema.resolveType(block, ty_src, extra.data.var_type); var extra_index: usize = extra.end; @@ -7940,11 +7939,25 @@ fn zirVarExtended( // break :blk align_tv.val; //} else Value.initTag(.null_value); - const init_val: Value = if (small.has_init) blk: { + const uncasted_init: Air.Inst.Ref = if (small.has_init) blk: { const init_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const init_air_inst = sema.resolveInst(init_ref); - break :blk (try sema.resolveMaybeUndefVal(block, init_src, init_air_inst)) orelse + break :blk sema.resolveInst(init_ref); + } else .none; + + const have_ty = extra.data.var_type != .none; + const var_ty = if (have_ty) + try sema.resolveType(block, ty_src, extra.data.var_type) + else + sema.typeOf(uncasted_init); + + const init_val = if (uncasted_init != .none) blk: { + const init = if (have_ty) + try sema.coerce(block, var_ty, uncasted_init, init_src) + else + uncasted_init; + + break :blk (try sema.resolveMaybeUndefVal(block, init_src, init)) orelse return sema.failWithNeededComptime(block, init_src); } else Value.initTag(.unreachable_value); diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index aae187739b..4e48913fef 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -111,3 +111,10 @@ fn test_u128_cmpxchg() !void { try expect(@cmpxchgStrong(u128, &x, 5678, 42, .SeqCst, .SeqCst) == null); try expect(x == 42); } + +var a_global_variable = @as(u32, 1234); + +test "cmpxchg on a global variable" { + _ = @cmpxchgWeak(u32, &a_global_variable, 1234, 42, .Acquire, .Monotonic); + try expect(a_global_variable == 42); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index c9e0ea1a60..86082825c7 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -3,13 +3,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const builtin = @import("builtin"); -var a_global_variable = @as(u32, 1234); - -test "cmpxchg on a global variable" { - _ = @cmpxchgWeak(u32, &a_global_variable, 1234, 42, .Acquire, .Monotonic); - try expectEqual(@as(u32, 42), a_global_variable); -} - test "atomic load and rmw with enum" { const Value = enum(u8) { a, From b58d8aa05f01e53003be32f220548cba69cf25dd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 16 Sep 2021 21:57:46 -0700 Subject: [PATCH 023/160] stage2: improve LLVM backend for enums * support lowering enum types and constants to LLVM IR * fix cmp instruction to support enum operands --- src/codegen/llvm.zig | 63 ++++++++++++++++++++++++-------- test/behavior/atomics.zig | 12 ++++++ test/behavior/atomics_stage1.zig | 16 -------- 3 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index ecc1790e6d..53e57ee219 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -588,6 +588,12 @@ pub const DeclGen = struct { const info = t.intInfo(self.module.getTarget()); return self.context.intType(info.bits); }, + .Enum => { + var buffer: Type.Payload.Bits = undefined; + const int_ty = t.enumTagType(&buffer); + const bit_count = int_ty.intInfo(self.module.getTarget()).bits; + return self.context.intType(bit_count); + }, .Float => switch (t.floatBits(self.module.getTarget())) { 16 => return self.context.halfType(), 32 => return self.context.floatType(), @@ -686,7 +692,6 @@ pub const DeclGen = struct { .BoundFn => @panic("TODO remove BoundFn from the language"), - .Enum, .Union, .Opaque, .Frame, @@ -723,6 +728,17 @@ pub const DeclGen = struct { } return llvm_int; }, + .Enum => { + const llvm_type = try self.llvmType(tv.ty); + const uint: u64 = uint: { + if (tv.val.castTag(.enum_field_index)) |payload| { + break :uint payload.data; + } + break :uint tv.val.toUnsignedInt(); + }; + const llvm_int = llvm_type.constInt(uint, .False); + return llvm_int; + }, .Float => { if (tv.ty.floatBits(self.module.getTarget()) <= 64) { const llvm_ty = try self.llvmType(tv.ty); @@ -907,7 +923,18 @@ pub const DeclGen = struct { .ComptimeFloat => unreachable, .Type => unreachable, .EnumLiteral => unreachable, - else => return self.todo("implement const of type '{}'", .{tv.ty}), + .Void => unreachable, + .NoReturn => unreachable, + .Undefined => unreachable, + .Null => unreachable, + .BoundFn => unreachable, + .Opaque => unreachable, + + .Union, + .Frame, + .AnyFrame, + .Vector, + => return self.todo("implement const of type '{}'", .{tv.ty}), } } @@ -1195,21 +1222,15 @@ pub const FuncGen = struct { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); - const inst_ty = self.air.typeOfIndex(inst); + const operand_ty = self.air.typeOf(bin_op.lhs); - switch (self.air.typeOf(bin_op.lhs).zigTypeTag()) { - .Int, .Bool, .Pointer, .ErrorSet => { - const is_signed = inst_ty.isSignedInt(); - const operation = switch (op) { - .eq => .EQ, - .neq => .NE, - .lt => @as(llvm.IntPredicate, if (is_signed) .SLT else .ULT), - .lte => @as(llvm.IntPredicate, if (is_signed) .SLE else .ULE), - .gt => @as(llvm.IntPredicate, if (is_signed) .SGT else .UGT), - .gte => @as(llvm.IntPredicate, if (is_signed) .SGE else .UGE), - }; - return self.builder.buildICmp(operation, lhs, rhs, ""); + const int_ty = switch (operand_ty.zigTypeTag()) { + .Enum => blk: { + var buffer: Type.Payload.Bits = undefined; + const int_ty = operand_ty.enumTagType(&buffer); + break :blk int_ty; }, + .Int, .Bool, .Pointer, .ErrorSet => operand_ty, .Float => { const operation: llvm.RealPredicate = switch (op) { .eq => .OEQ, @@ -1222,7 +1243,17 @@ pub const FuncGen = struct { return self.builder.buildFCmp(operation, lhs, rhs, ""); }, else => unreachable, - } + }; + const is_signed = int_ty.isSignedInt(); + const operation = switch (op) { + .eq => .EQ, + .neq => .NE, + .lt => @as(llvm.IntPredicate, if (is_signed) .SLT else .ULT), + .lte => @as(llvm.IntPredicate, if (is_signed) .SLE else .ULE), + .gt => @as(llvm.IntPredicate, if (is_signed) .SGT else .UGT), + .gte => @as(llvm.IntPredicate, if (is_signed) .SGE else .UGE), + }; + return self.builder.buildICmp(operation, lhs, rhs, ""); } fn airBlock(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index 4e48913fef..efd63f1ac5 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -118,3 +118,15 @@ test "cmpxchg on a global variable" { _ = @cmpxchgWeak(u32, &a_global_variable, 1234, 42, .Acquire, .Monotonic); try expect(a_global_variable == 42); } + +test "atomic load and rmw with enum" { + const Value = enum(u8) { a, b, c }; + var x = Value.a; + + try expect(@atomicLoad(Value, &x, .SeqCst) != .b); + + _ = @atomicRmw(Value, &x, .Xchg, .c, .SeqCst); + try expect(@atomicLoad(Value, &x, .SeqCst) == .c); + try expect(@atomicLoad(Value, &x, .SeqCst) != .a); + try expect(@atomicLoad(Value, &x, .SeqCst) != .b); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index 86082825c7..936c06b155 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -3,22 +3,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const builtin = @import("builtin"); -test "atomic load and rmw with enum" { - const Value = enum(u8) { - a, - b, - c, - }; - var x = Value.a; - - try expect(@atomicLoad(Value, &x, .SeqCst) != .b); - - _ = @atomicRmw(Value, &x, .Xchg, .c, .SeqCst); - try expect(@atomicLoad(Value, &x, .SeqCst) == .c); - try expect(@atomicLoad(Value, &x, .SeqCst) != .a); - try expect(@atomicLoad(Value, &x, .SeqCst) != .b); -} - test "atomic store" { var x: u32 = 0; @atomicStore(u32, &x, 1, .SeqCst); From d8375696f668d7ed5922515e8cc12d67e2790b70 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 17 Sep 2021 12:12:50 +0200 Subject: [PATCH 024/160] elf: add a couple missing special section indexes SHN_ --- lib/std/elf.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 69f04868e8..3213a26942 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -1591,3 +1591,13 @@ pub const PF_MASKOS = 0x0ff00000; /// Bits for processor-specific semantics. pub const PF_MASKPROC = 0xf0000000; + +// Special section indexes used in Elf{32,64}_Sym. +pub const SHN_UNDEF = 0; +pub const SHN_LORESERVE = 0xff00; +pub const SHN_LOPROC = 0xff00; +pub const SHN_HIPROC = 0xff1f; +pub const SHN_LIVEPATCH = 0xff20; +pub const SHN_ABS = 0xfff1; +pub const SHN_COMMON = 0xfff2; +pub const SHN_HIRESERVE = 0xffff; From f0b1eec8095e442218ff9171e284393102596dc4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 17 Sep 2021 11:12:11 -0700 Subject: [PATCH 025/160] ci: update to new sourcehut access token --- .builds/freebsd.yml | 2 +- .builds/netbsd.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 37e6a65680..a149d060d1 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -1,7 +1,7 @@ image: freebsd/latest secrets: - 51bfddf5-86a6-4e01-8576-358c72a4a0a4 - - 5cfede76-914e-4071-893e-e5e2e6ae3cea + - 512ed797-0927-475a-83fd-bc997792860c sources: - https://github.com/ziglang/zig tasks: diff --git a/.builds/netbsd.yml b/.builds/netbsd.yml index 69395b5e05..f1c1c19385 100644 --- a/.builds/netbsd.yml +++ b/.builds/netbsd.yml @@ -1,7 +1,7 @@ image: netbsd/latest secrets: - 51bfddf5-86a6-4e01-8576-358c72a4a0a4 - - 5cfede76-914e-4071-893e-e5e2e6ae3cea + - 512ed797-0927-475a-83fd-bc997792860c sources: - https://github.com/ziglang/zig tasks: From d2b5105f54ac83fdd874df9408157773ae203798 Mon Sep 17 00:00:00 2001 From: Jens Goldberg Date: Sat, 18 Sep 2021 08:56:11 +0200 Subject: [PATCH 026/160] Add Linux ioctl creation utilities (#9748) * Add Linux ioctl creation utilities * Apply suggestions from code review Co-authored-by: Veikka Tuominen * Update lib/std/os/linux.zig Co-authored-by: zigazeljko Co-authored-by: Veikka Tuominen Co-authored-by: zigazeljko --- lib/std/os/linux.zig | 15 +++++----- lib/std/os/linux/ioctl.zig | 56 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 lib/std/os/linux/ioctl.zig diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 2ea127a522..f3e4495220 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -89,6 +89,7 @@ pub const user_desc = arch_bits.user_desc; pub const tls = @import("linux/tls.zig"); pub const pie = @import("linux/start_pie.zig"); pub const BPF = @import("linux/bpf.zig"); +pub const IOCTL = @import("linux/ioctl.zig"); pub const MAP = struct { pub usingnamespace arch_bits.MAP; @@ -2585,18 +2586,18 @@ pub const T = struct { pub const IOCGSID = 0x5429; pub const IOCGRS485 = 0x542E; pub const IOCSRS485 = 0x542F; - pub const IOCGPTN = 0x80045430; - pub const IOCSPTLCK = 0x40045431; - pub const IOCGDEV = 0x80045432; + pub const IOCGPTN = IOCTL.IOR('T', 0x30, c_uint); + pub const IOCSPTLCK = IOCTL.IOW('T', 0x31, c_int); + pub const IOCGDEV = IOCTL.IOR('T', 0x32, c_uint); pub const CGETX = 0x5432; pub const CSETX = 0x5433; pub const CSETXF = 0x5434; pub const CSETXW = 0x5435; - pub const IOCSIG = 0x40045436; + pub const IOCSIG = IOCTL.IOW('T', 0x36, c_int); pub const IOCVHANGUP = 0x5437; - pub const IOCGPKT = 0x80045438; - pub const IOCGPTLCK = 0x80045439; - pub const IOCGEXCL = 0x80045440; + pub const IOCGPKT = IOCTL.IOR('T', 0x38, c_int); + pub const IOCGPTLCK = IOCTL.IOR('T', 0x39, c_int); + pub const IOCGEXCL = IOCTL.IOR('T', 0x40, c_int); }; pub const EPOLL = struct { diff --git a/lib/std/os/linux/ioctl.zig b/lib/std/os/linux/ioctl.zig new file mode 100644 index 0000000000..35ff1bfc32 --- /dev/null +++ b/lib/std/os/linux/ioctl.zig @@ -0,0 +1,56 @@ +const std = @import("../../std.zig"); + +const bits = switch (@import("builtin").cpu.arch) { + .mips, + .mipsel, + .mips64, + .mips64el, + .powerpc, + .powerpcle, + .powerpc64, + .powerpc64le, + .sparc, + .sparcv9, + .sparcel, + => .{ .size = 13, .dir = 3, .none = 1, .read = 2, .write = 4 }, + else => .{ .size = 14, .dir = 2, .none = 0, .read = 2, .write = 1 }, +}; + +const Direction = std.meta.Int(.unsigned, bits.dir); + +pub const Request = packed struct { + nr: u8, + io_type: u8, + size: std.meta.Int(.unsigned, bits.size), + dir: Direction, +}; + +fn io_impl(dir: Direction, io_type: u8, nr: u8, comptime T: type) u32 { + const request = Request{ + .dir = dir, + .size = @sizeOf(T), + .io_type = io_type, + .nr = nr, + }; + return @bitCast(u32, request); +} + +pub fn IO(io_type: u8, nr: u8) u32 { + return io_impl(bits.none, io_type, nr, void); +} + +pub fn IOR(io_type: u8, nr: u8, comptime T: type) u32 { + return io_impl(bits.read, io_type, nr, T); +} + +pub fn IOW(io_type: u8, nr: u8, comptime T: type) u32 { + return io_impl(bits.write, io_type, nr, T); +} + +pub fn IOWR(io_type: u8, nr: u8, comptime T: type) u32 { + return io_impl(bits.read | bits.write, io_type, nr, T); +} + +comptime { + std.debug.assert(@bitSizeOf(Request) == 32); +} From f388b575533b8e36999bc5ee406421feb7e80baa Mon Sep 17 00:00:00 2001 From: Jacob G-W Date: Sun, 29 Aug 2021 15:11:01 -0400 Subject: [PATCH 027/160] plan9: emit line debug info in codegen --- src/Module.zig | 4 +++- src/codegen.zig | 42 +++++++++++++++++++++++++++++++++++++++-- src/link/Plan9.zig | 24 +++++++++++++++++++++-- src/link/Plan9/aout.zig | 9 +++++++++ 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 473c27e338..ff9560e2de 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3674,7 +3674,9 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi mod.comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl }); }, .plan9 => { - // TODO implement for plan9 + // TODO Look into detecting when this would be unnecessary by storing enough state + // in `Decl` to notice that the line number did not change. + mod.comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl }); }, .c, .wasm, .spirv => {}, } diff --git a/src/codegen.zig b/src/codegen.zig index 08ee358bff..7dc605ba73 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -48,6 +48,21 @@ pub const DebugInfoOutput = union(enum) { dbg_info: *std.ArrayList(u8), dbg_info_type_relocs: *link.File.DbgInfoTypeRelocsTable, }, + /// the plan9 debuginfo output is a bytecode with 4 opcodes + /// assume all numbers/variables are bytes + /// 0 w x y z -> interpret w x y z as a big-endian i32, and add it to the line offset + /// x when x < 65 -> add x to line offset + /// x when x < 129 -> subtract 64 from x and add it to the line offset + /// x -> subtract 129 from x, multiply it by the quanta of the instruction size + /// (1 on x86_64), and add it to the pc + /// after every opcode, add the quanta of the instruction size to the pc + plan9: struct { + /// the actual opcodes + dbg_line: *std.ArrayList(u8), + /// what the line count ends on after codegen + /// this helps because the linker might have to insert some opcodes to make sure that the line count starts at the right amount for the next decl + end_line: *u32, + }, none, }; @@ -913,6 +928,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try dbg_out.dbg_line.append(DW.LNS.set_prologue_end); try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column); }, + .plan9 => {}, .none => {}, } } @@ -923,15 +939,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin); try self.dbgAdvancePCAndLine(self.prev_di_line, self.prev_di_column); }, + .plan9 => {}, .none => {}, } } fn dbgAdvancePCAndLine(self: *Self, line: u32, column: u32) InnerError!void { + const delta_line = @intCast(i32, line) - @intCast(i32, self.prev_di_line); + const delta_pc = self.code.items.len - self.prev_di_pc; switch (self.debug_output) { .dwarf => |dbg_out| { - const delta_line = @intCast(i32, line) - @intCast(i32, self.prev_di_line); - const delta_pc = self.code.items.len - self.prev_di_pc; // TODO Look into using the DWARF special opcodes to compress this data. // It lets you emit single-byte opcodes that add different numbers to // both the PC and the line number at the same time. @@ -944,6 +961,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy); }, + .plan9 => |dbg_out| { + // we have already checked the target in the linker to make sure it is compatable + const quant = @import("link/Plan9/aout.zig").getPCQuant(self.target.cpu.arch) catch unreachable; + + // increasing the line number + if (delta_line > 0 and delta_line < 65) { + try dbg_out.dbg_line.append(@intCast(u8, delta_line)); + } else if (delta_line < 0 and delta_line > -65) { + try dbg_out.dbg_line.append(@intCast(u8, -delta_line + 64)); + } else if (delta_line != 0) { + try dbg_out.dbg_line.writer().writeIntBig(i32, delta_line); + } + // increasing the pc + if (delta_pc - quant != 0) { + try dbg_out.dbg_line.append(@intCast(u8, delta_pc - quant + 129)); + } + dbg_out.end_line.* = line; + }, .none => {}, } self.prev_di_line = line; @@ -1032,6 +1067,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } try gop.value_ptr.relocs.append(self.gpa, @intCast(u32, index)); }, + .plan9 => {}, .none => {}, } } @@ -2457,6 +2493,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.addDbgInfoTypeReloc(ty); // DW.AT.type, DW.FORM.ref4 dbg_out.dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string }, + .plan9 => {}, .none => {}, } }, @@ -2491,6 +2528,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { else => {}, } }, + .plan9 => {}, .none => {}, } }, diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index bf49a238b6..cf6b05739d 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -34,6 +34,8 @@ data_decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, []const u8) = .{}, hdr: aout.ExecHdr = undefined, +magic: u32, + entry_val: ?u64 = null, got_len: usize = 0, @@ -113,6 +115,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Plan9 { }, .sixtyfour_bit = sixtyfour_bit, .bases = undefined, + .magic = try aout.magicFromArch(self.base.options.target.cpu.arch), }; return self; } @@ -127,7 +130,24 @@ pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liv var code_buffer = std.ArrayList(u8).init(self.base.allocator); defer code_buffer.deinit(); - const res = try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{ .none = .{} }); + var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator); + defer dbg_line_buffer.deinit(); + var end_line: u32 = 0; + + const res = try codegen.generateFunction( + &self.base, + decl.srcLoc(), + func, + air, + liveness, + &code_buffer, + .{ + .plan9 = .{ + .dbg_line = &dbg_line_buffer, + .end_line = &end_line, + }, + }, + ); const code = switch (res) { .appended => code_buffer.toOwnedSlice(), .fail => |em| { @@ -313,7 +333,7 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void { iovecs_i += 1; // generate the header self.hdr = .{ - .magic = try aout.magicFromArch(self.base.options.target.cpu.arch), + .magic = self.magic, .text = @intCast(u32, text_i), .data = @intCast(u32, data_i), .syms = @intCast(u32, sym_buf.items.len), diff --git a/src/link/Plan9/aout.zig b/src/link/Plan9/aout.zig index f6dff7437c..b4fafbc31d 100644 --- a/src/link/Plan9/aout.zig +++ b/src/link/Plan9/aout.zig @@ -112,3 +112,12 @@ pub fn magicFromArch(arch: std.Target.Cpu.Arch) !u32 { else => error.ArchNotSupportedByPlan9, }; } + +/// gets the quantization of pc for the arch +pub fn getPCQuant(arch: std.Target.Cpu.Arch) !u8 { + return switch (arch) { + .i386, .x86_64 => 1, + .powerpc, .powerpc64, .mips, .sparc, .arm, .aarch64 => 4, + else => error.ArchNotSupportedByPlan9, + }; +} From 4cb2d6bc3e3ab7c27cabdc7318abaea1afc34654 Mon Sep 17 00:00:00 2001 From: Jacob G-W Date: Sun, 29 Aug 2021 16:35:52 -0400 Subject: [PATCH 028/160] plan9 linker: add free lists for got_index and sym_index This allows the same global offset and symbol table index to be re-used if a decl is freed. --- src/link/Plan9.zig | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index cf6b05739d..004d12ad22 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -39,6 +39,11 @@ magic: u32, entry_val: ?u64 = null, got_len: usize = 0, +// A list of all the free got indexes, so when making a new decl +// don't make a new one, just use one from here. +got_index_free_list: std.ArrayListUnmanaged(u64) = .{}, + +syms_index_free_list: std.ArrayListUnmanaged(u64) = .{}, const Bases = struct { text: u64, @@ -212,8 +217,12 @@ fn updateFinish(self: *Plan9, decl: *Module.Decl) !void { if (decl.link.plan9.sym_index) |s| { self.syms.items[s] = sym; } else { - try self.syms.append(self.base.allocator, sym); - decl.link.plan9.sym_index = self.syms.items.len - 1; + if (self.syms_index_free_list.popOrNull()) |i| { + decl.link.plan9.sym_index = i; + } else { + try self.syms.append(self.base.allocator, sym); + decl.link.plan9.sym_index = self.syms.items.len - 1; + } } } @@ -244,14 +253,12 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void { const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; - // TODO I changed this assert from == to >= but this code all needs to be audited; see - // the comment in `freeDecl`. - assert(self.got_len >= self.fn_decl_table.count() + self.data_decl_table.count()); + assert(self.got_len == self.fn_decl_table.count() + self.data_decl_table.count() + self.got_index_free_list.items.len); const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8; var got_table = try self.base.allocator.alloc(u8, got_size); defer self.base.allocator.free(got_table); - // + 2 for header, got, symbols + // + 3 for header, got, symbols var iovecs = try self.base.allocator.alloc(std.os.iovec_const, self.fn_decl_table.count() + self.data_decl_table.count() + 3); defer self.base.allocator.free(iovecs); @@ -380,18 +387,24 @@ fn addDeclExports( } pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { - // TODO this is not the correct check for being function body, - // it could just be a function pointer. // TODO audit the lifetimes of decls table entries. It's possible to get // allocateDeclIndexes and then freeDecl without any updateDecl in between. // However that is planned to change, see the TODO comment in Module.zig // in the deleteUnusedDecl function. - const is_fn = (decl.ty.zigTypeTag() == .Fn); + const is_fn = (decl.val.tag() == .function); if (is_fn) { _ = self.fn_decl_table.swapRemove(decl); } else { _ = self.data_decl_table.swapRemove(decl); } + if (decl.link.plan9.got_index) |i| { + // TODO: if this catch {} is triggered, an assertion in flushModule will be triggered, because got_index_free_list will have the wrong length + self.got_index_free_list.append(self.base.allocator, i) catch {}; + } + if (decl.link.plan9.sym_index) |i| { + self.syms_index_free_list.append(self.base.allocator, i) catch {}; + self.syms.items[i] = undefined; + } } pub fn updateDeclExports( @@ -418,6 +431,8 @@ pub fn deinit(self: *Plan9) void { } self.data_decl_table.deinit(self.base.allocator); self.syms.deinit(self.base.allocator); + self.got_index_free_list.deinit(self.base.allocator); + self.syms_index_free_list.deinit(self.base.allocator); } pub const Export = ?usize; @@ -481,7 +496,11 @@ pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void { pub fn allocateDeclIndexes(self: *Plan9, decl: *Module.Decl) !void { if (decl.link.plan9.got_index == null) { - self.got_len += 1; - decl.link.plan9.got_index = self.got_len - 1; + if (self.got_index_free_list.popOrNull()) |i| { + decl.link.plan9.got_index = i; + } else { + self.got_len += 1; + decl.link.plan9.got_index = self.got_len - 1; + } } } From 84ab03a875b2a1b9d38f094242ddbd54f133c1a5 Mon Sep 17 00:00:00 2001 From: Jacob G-W Date: Mon, 30 Aug 2021 08:52:29 -0400 Subject: [PATCH 029/160] plan9 linker: get ready to delete allocateDeclIndexes --- src/codegen.zig | 3 +++ src/link/Plan9.zig | 27 +++++++++++++++++++-------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 7dc605ba73..20243ba861 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2965,6 +2965,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { + try p9.seeDecl(func_payload.data.owner_decl); const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const got_addr = p9.bases.data; @@ -3012,6 +3013,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } if (self.air.value(callee)) |func_value| { if (func_value.castTag(.function)) |func_payload| { + try p9.seeDecl(func_payload.data.owner_decl); const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); const got_addr = p9.bases.data; @@ -4939,6 +4941,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { + try p9.seeDecl(decl); const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; return MCValue{ .memory = got_addr }; } else { diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 004d12ad22..a51ab08e95 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -131,6 +131,8 @@ pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liv } const decl = func.owner_decl; + + try self.seeDecl(decl); log.debug("codegen decl {*} ({s})", .{ decl, decl.name }); var code_buffer = std.ArrayList(u8).init(self.base.allocator); @@ -176,6 +178,8 @@ pub fn updateDecl(self: *Plan9, module: *Module, decl: *Module.Decl) !void { } } + try self.seeDecl(decl); + log.debug("codegen decl {*} ({s})", .{ decl, decl.name }); var code_buffer = std.ArrayList(u8).init(self.base.allocator); @@ -407,12 +411,24 @@ pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { } } +pub fn seeDecl(self: *Plan9, decl: *Module.Decl) !void { + if (decl.link.plan9.got_index == null) { + if (self.got_index_free_list.popOrNull()) |i| { + decl.link.plan9.got_index = i; + } else { + self.got_len += 1; + decl.link.plan9.got_index = self.got_len - 1; + } + } +} + pub fn updateDeclExports( self: *Plan9, module: *Module, decl: *Module.Decl, exports: []const *Module.Export, ) !void { + try self.seeDecl(decl); // we do all the things in flush _ = self; _ = module; @@ -494,13 +510,8 @@ pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void { } } +/// this will be removed, moved to updateFinish pub fn allocateDeclIndexes(self: *Plan9, decl: *Module.Decl) !void { - if (decl.link.plan9.got_index == null) { - if (self.got_index_free_list.popOrNull()) |i| { - decl.link.plan9.got_index = i; - } else { - self.got_len += 1; - decl.link.plan9.got_index = self.got_len - 1; - } - } + _ = self; + _ = decl; } From cfe71cb67a3cf3814ce12d2a2d87192ef905b9fd Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 16 Sep 2021 17:19:41 -0700 Subject: [PATCH 030/160] std.fifo.LinearFifo: ensureUnusedCapacity and ensureTotalCapacity Same as c8ae581fef6506a8234cdba1355ba7f0f449031a, but for LinearFifo. --- lib/std/fifo.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index 93ef50b861..e4b2cd8be9 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -119,8 +119,11 @@ pub fn LinearFifo( } } + /// Deprecated: call `ensureUnusedCapacity` or `ensureTotalCapacity`. + pub const ensureCapacity = ensureTotalCapacity; + /// Ensure that the buffer can fit at least `size` items - pub fn ensureCapacity(self: *Self, size: usize) !void { + pub fn ensureTotalCapacity(self: *Self, size: usize) !void { if (self.buf.len >= size) return; if (buffer_type == .Dynamic) { self.realign(); @@ -135,7 +138,7 @@ pub fn LinearFifo( pub fn ensureUnusedCapacity(self: *Self, size: usize) error{OutOfMemory}!void { if (self.writableLength() >= size) return; - return try self.ensureCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory); + return try self.ensureTotalCapacity(math.add(usize, self.count, size) catch return error.OutOfMemory); } /// Returns number of items currently in fifo @@ -471,7 +474,7 @@ test "LinearFifo(u8, .Dynamic)" { } { - try fifo.ensureCapacity(1); + try fifo.ensureTotalCapacity(1); var in_fbs = std.io.fixedBufferStream("pump test"); var out_buf: [50]u8 = undefined; var out_fbs = std.io.fixedBufferStream(&out_buf); From 2be3b1d2bfd4bab61a481226fd1d714512ea2f44 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 16 Sep 2021 17:20:42 -0700 Subject: [PATCH 031/160] std.PriorityQueue: ensureUnusedCapacity and ensureTotalCapacity Same as c8ae581fef6506a8234cdba1355ba7f0f449031a, but for PriorityQueue. --- lib/std/priority_queue.zig | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/std/priority_queue.zig b/lib/std/priority_queue.zig index 228c07cadb..fcdd81b1dd 100644 --- a/lib/std/priority_queue.zig +++ b/lib/std/priority_queue.zig @@ -42,7 +42,7 @@ pub fn PriorityQueue(comptime T: type) type { /// Insert a new element, maintaining priority. pub fn add(self: *Self, elem: T) !void { - try ensureCapacity(self, self.len + 1); + try self.ensureUnusedCapacity(1); addUnchecked(self, elem); } @@ -69,7 +69,7 @@ pub fn PriorityQueue(comptime T: type) type { /// Add each element in `items` to the queue. pub fn addSlice(self: *Self, items: []const T) !void { - try self.ensureCapacity(self.len + items.len); + try self.ensureUnusedCapacity(items.len); for (items) |e| { self.addUnchecked(e); } @@ -175,7 +175,11 @@ pub fn PriorityQueue(comptime T: type) type { return queue; } - pub fn ensureCapacity(self: *Self, new_capacity: usize) !void { + /// Deprecated: call `ensureUnusedCapacity` or `ensureTotalCapacity`. + pub const ensureCapacity = ensureTotalCapacity; + + /// Ensure that the queue can fit at least `new_capacity` items. + pub fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void { var better_capacity = self.capacity(); if (better_capacity >= new_capacity) return; while (true) { @@ -185,6 +189,11 @@ pub fn PriorityQueue(comptime T: type) type { self.items = try self.allocator.realloc(self.items, better_capacity); } + /// Ensure that the queue can fit at least `additional_count` **more** item. + pub fn ensureUnusedCapacity(self: *Self, additional_count: usize) !void { + return self.ensureTotalCapacity(self.len + additional_count); + } + /// Reduce allocated capacity to `new_len`. pub fn shrinkAndFree(self: *Self, new_len: usize) void { assert(new_len <= self.items.len); @@ -483,7 +492,7 @@ test "std.PriorityQueue: shrinkAndFree" { var queue = PQ.init(testing.allocator, lessThan); defer queue.deinit(); - try queue.ensureCapacity(4); + try queue.ensureTotalCapacity(4); try expect(queue.capacity() >= 4); try queue.add(1); From feeb25908bebd5d09cf05128fad7d7c1a8a803a1 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 16 Sep 2021 17:21:19 -0700 Subject: [PATCH 032/160] std.PriorityDequeue: ensureUnusedCapacity and ensureTotalCapacity Same as c8ae581fef6506a8234cdba1355ba7f0f449031a, but for PriorityDequeue. --- lib/std/priority_dequeue.zig | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/std/priority_dequeue.zig b/lib/std/priority_dequeue.zig index d154f5df5e..5bde0a36d0 100644 --- a/lib/std/priority_dequeue.zig +++ b/lib/std/priority_dequeue.zig @@ -43,13 +43,13 @@ pub fn PriorityDequeue(comptime T: type) type { /// Insert a new element, maintaining priority. pub fn add(self: *Self, elem: T) !void { - try ensureCapacity(self, self.len + 1); + try self.ensureUnusedCapacity(1); addUnchecked(self, elem); } /// Add each element in `items` to the dequeue. pub fn addSlice(self: *Self, items: []const T) !void { - try self.ensureCapacity(self.len + items.len); + try self.ensureUnusedCapacity(items.len); for (items) |e| { self.addUnchecked(e); } @@ -359,7 +359,11 @@ pub fn PriorityDequeue(comptime T: type) type { return queue; } - pub fn ensureCapacity(self: *Self, new_capacity: usize) !void { + /// Deprecated: call `ensureUnusedCapacity` or `ensureTotalCapacity`. + pub const ensureCapacity = ensureTotalCapacity; + + /// Ensure that the dequeue can fit at least `new_capacity` items. + pub fn ensureTotalCapacity(self: *Self, new_capacity: usize) !void { var better_capacity = self.capacity(); if (better_capacity >= new_capacity) return; while (true) { @@ -369,6 +373,11 @@ pub fn PriorityDequeue(comptime T: type) type { self.items = try self.allocator.realloc(self.items, better_capacity); } + /// Ensure that the dequeue can fit at least `additional_count` **more** items. + pub fn ensureUnusedCapacity(self: *Self, additional_count: usize) !void { + return self.ensureTotalCapacity(self.len + additional_count); + } + /// Reduce allocated capacity to `new_len`. pub fn shrinkAndFree(self: *Self, new_len: usize) void { assert(new_len <= self.items.len); @@ -824,7 +833,7 @@ test "std.PriorityDequeue: shrinkAndFree" { var queue = PDQ.init(testing.allocator, lessThanComparison); defer queue.deinit(); - try queue.ensureCapacity(4); + try queue.ensureTotalCapacity(4); try expect(queue.capacity() >= 4); try queue.add(1); @@ -940,7 +949,7 @@ fn fuzzTestMinMax(rng: *std.rand.Random, queue_size: usize) !void { fn generateRandomSlice(allocator: *std.mem.Allocator, rng: *std.rand.Random, size: usize) ![]u32 { var array = std.ArrayList(u32).init(allocator); - try array.ensureCapacity(size); + try array.ensureTotalCapacity(size); var i: usize = 0; while (i < size) : (i += 1) { From 59f5053beda7087a73983835e9f7e00dc3143d59 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 16 Sep 2021 18:22:04 -0700 Subject: [PATCH 033/160] Update all ensureCapacity calls to the relevant non-deprecated version --- lib/std/array_hash_map.zig | 20 ++++++------- lib/std/child_process.zig | 10 +++---- lib/std/coff.zig | 2 +- lib/std/hash_map.zig | 18 ++++++------ lib/std/heap/general_purpose_allocator.zig | 5 +--- lib/std/io/reader.zig | 4 +-- lib/std/json.zig | 2 +- lib/std/multi_array_list.zig | 4 +-- lib/std/unicode.zig | 2 +- lib/std/zig/parse.zig | 6 ++-- lib/std/zig/string_literal.zig | 2 +- src/AstGen.zig | 4 +-- src/Cache.zig | 2 +- src/Compilation.zig | 18 ++++++------ src/Liveness.zig | 2 +- src/Module.zig | 4 +-- src/Package.zig | 2 +- src/Sema.zig | 8 ++--- src/codegen.zig | 34 +++++++++++----------- src/codegen/spirv.zig | 2 +- src/libcxx.zig | 4 +-- src/libtsan.zig | 12 ++++---- src/link.zig | 2 +- src/link/C.zig | 6 ++-- src/link/Coff.zig | 6 ++-- src/link/Elf.zig | 30 +++++++++---------- src/link/MachO/CodeSignature.zig | 2 +- src/link/MachO/DebugSymbols.zig | 16 +++++----- src/link/MachO/Dylib.zig | 2 +- src/link/MachO/Object.zig | 2 +- src/link/MachO/Trie.zig | 2 +- src/link/MachO/commands.zig | 2 +- src/link/Wasm.zig | 4 +-- src/main.zig | 12 ++++---- src/mingw.zig | 2 +- src/musl.zig | 4 +-- src/translate_c.zig | 2 +- src/translate_c/ast.zig | 10 +++---- 38 files changed, 134 insertions(+), 137 deletions(-) diff --git a/lib/std/array_hash_map.zig b/lib/std/array_hash_map.zig index 91e0c4d883..42443f2138 100644 --- a/lib/std/array_hash_map.zig +++ b/lib/std/array_hash_map.zig @@ -90,7 +90,7 @@ pub fn ArrayHashMap( /// Modifying the key is allowed only if it does not change the hash. /// Modifying the value is allowed. /// Entry pointers become invalid whenever this ArrayHashMap is modified, - /// unless `ensureCapacity` was previously used. + /// unless `ensureTotalCapacity`/`ensureUnusedCapacity` was previously used. pub const Entry = Unmanaged.Entry; /// A KV pair which has been copied out of the backing store @@ -110,7 +110,7 @@ pub fn ArrayHashMap( /// Modifying the key is allowed only if it does not change the hash. /// Modifying the value is allowed. /// Entry pointers become invalid whenever this ArrayHashMap is modified, - /// unless `ensureCapacity` was previously used. + /// unless `ensureTotalCapacity`/`ensureUnusedCapacity` was previously used. pub const GetOrPutResult = Unmanaged.GetOrPutResult; /// An Iterator over Entry pointers. @@ -478,7 +478,7 @@ pub fn ArrayHashMapUnmanaged( /// Modifying the key is allowed only if it does not change the hash. /// Modifying the value is allowed. /// Entry pointers become invalid whenever this ArrayHashMap is modified, - /// unless `ensureCapacity` was previously used. + /// unless `ensureTotalCapacity`/`ensureUnusedCapacity` was previously used. pub const Entry = struct { key_ptr: *K, value_ptr: *V, @@ -509,7 +509,7 @@ pub fn ArrayHashMapUnmanaged( /// Modifying the key is allowed only if it does not change the hash. /// Modifying the value is allowed. /// Entry pointers become invalid whenever this ArrayHashMap is modified, - /// unless `ensureCapacity` was previously used. + /// unless `ensureTotalCapacity`/`ensureUnusedCapacity` was previously used. pub const GetOrPutResult = struct { key_ptr: *K, value_ptr: *V, @@ -759,20 +759,20 @@ pub fn ArrayHashMapUnmanaged( } pub fn ensureTotalCapacityContext(self: *Self, allocator: *Allocator, new_capacity: usize, ctx: Context) !void { if (new_capacity <= linear_scan_max) { - try self.entries.ensureCapacity(allocator, new_capacity); + try self.entries.ensureTotalCapacity(allocator, new_capacity); return; } if (self.index_header) |header| { if (new_capacity <= header.capacity()) { - try self.entries.ensureCapacity(allocator, new_capacity); + try self.entries.ensureTotalCapacity(allocator, new_capacity); return; } } const new_bit_index = try IndexHeader.findBitIndex(new_capacity); const new_header = try IndexHeader.alloc(allocator, new_bit_index); - try self.entries.ensureCapacity(allocator, new_capacity); + try self.entries.ensureTotalCapacity(allocator, new_capacity); if (self.index_header) |old_header| old_header.free(allocator); self.insertAllEntriesIntoNewHeader(if (store_hash) {} else ctx, new_header); @@ -1441,7 +1441,7 @@ pub fn ArrayHashMapUnmanaged( unreachable; } - /// Must ensureCapacity before calling this. + /// Must `ensureTotalCapacity`/`ensureUnusedCapacity` before calling this. fn getOrPutInternal(self: *Self, key: anytype, ctx: anytype, header: *IndexHeader, comptime I: type) GetOrPutResult { const slice = self.entries.slice(); const hashes_array = if (store_hash) slice.items(.hash) else {}; @@ -1485,7 +1485,7 @@ pub fn ArrayHashMapUnmanaged( } // This pointer survives the following append because we call - // entries.ensureCapacity before getOrPutInternal. + // entries.ensureTotalCapacity before getOrPutInternal. const hash_match = if (store_hash) h == hashes_array[slot_data.entry_index] else true; if (hash_match and checkedEql(ctx, key, keys_array[slot_data.entry_index])) { return .{ @@ -1946,7 +1946,7 @@ test "iterator hash map" { var reset_map = AutoArrayHashMap(i32, i32).init(std.testing.allocator); defer reset_map.deinit(); - // test ensureCapacity with a 0 parameter + // test ensureTotalCapacity with a 0 parameter try reset_map.ensureTotalCapacity(0); try reset_map.putNoClobber(0, 11); diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 530a9b68a6..11b95a6e36 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -195,7 +195,7 @@ pub const ChildProcess = struct { }; var dead_fds: usize = 0; - // We ask for ensureCapacity with this much extra space. This has more of an + // We ask for ensureTotalCapacity with this much extra space. This has more of an // effect on small reads because once the reads start to get larger the amount // of space an ArrayList will allocate grows exponentially. const bump_amt = 512; @@ -215,7 +215,7 @@ pub const ChildProcess = struct { if (poll_fds[0].revents & os.POLL.IN != 0) { // stdout is ready. const new_capacity = std.math.min(stdout.items.len + bump_amt, max_output_bytes); - try stdout.ensureCapacity(new_capacity); + try stdout.ensureTotalCapacity(new_capacity); const buf = stdout.unusedCapacitySlice(); if (buf.len == 0) return error.StdoutStreamTooLong; const nread = try os.read(poll_fds[0].fd, buf); @@ -230,7 +230,7 @@ pub const ChildProcess = struct { if (poll_fds[1].revents & os.POLL.IN != 0) { // stderr is ready. const new_capacity = std.math.min(stderr.items.len + bump_amt, max_output_bytes); - try stderr.ensureCapacity(new_capacity); + try stderr.ensureTotalCapacity(new_capacity); const buf = stderr.unusedCapacitySlice(); if (buf.len == 0) return error.StderrStreamTooLong; const nread = try os.read(poll_fds[1].fd, buf); @@ -276,7 +276,7 @@ pub const ChildProcess = struct { // Windows Async IO requires an initial call to ReadFile before waiting on the handle for ([_]u1{ 0, 1 }) |i| { - try outs[i].ensureCapacity(bump_amt); + try outs[i].ensureTotalCapacity(bump_amt); const buf = outs[i].unusedCapacitySlice(); _ = windows.kernel32.ReadFile(handles[i], buf.ptr, math.cast(u32, buf.len) catch maxInt(u32), null, &overlapped[i]); wait_objects[wait_object_count] = handles[i]; @@ -318,7 +318,7 @@ pub const ChildProcess = struct { outs[i].items.len += read_bytes; const new_capacity = std.math.min(outs[i].items.len + bump_amt, max_output_bytes); - try outs[i].ensureCapacity(new_capacity); + try outs[i].ensureTotalCapacity(new_capacity); const buf = outs[i].unusedCapacitySlice(); if (buf.len == 0) return if (i == 0) error.StdoutStreamTooLong else error.StderrStreamTooLong; _ = windows.kernel32.ReadFile(handles[i], buf.ptr, math.cast(u32, buf.len) catch maxInt(u32), null, &overlapped[i]); diff --git a/lib/std/coff.zig b/lib/std/coff.zig index 6caf214728..c8b0a44044 100644 --- a/lib/std/coff.zig +++ b/lib/std/coff.zig @@ -277,7 +277,7 @@ pub const Coff = struct { if (self.sections.items.len == self.coff_header.number_of_sections) return; - try self.sections.ensureCapacity(self.coff_header.number_of_sections); + try self.sections.ensureTotalCapacity(self.coff_header.number_of_sections); const in = self.in_file.reader(); diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index 644429f871..a75178d428 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -1568,11 +1568,11 @@ test "std.hash_map basic usage" { try expectEqual(total, sum); } -test "std.hash_map ensureCapacity" { +test "std.hash_map ensureTotalCapacity" { var map = AutoHashMap(i32, i32).init(std.testing.allocator); defer map.deinit(); - try map.ensureCapacity(20); + try map.ensureTotalCapacity(20); const initial_capacity = map.capacity(); try testing.expect(initial_capacity >= 20); var i: i32 = 0; @@ -1583,13 +1583,13 @@ test "std.hash_map ensureCapacity" { try testing.expect(initial_capacity == map.capacity()); } -test "std.hash_map ensureCapacity with tombstones" { +test "std.hash_map ensureUnusedCapacity with tombstones" { var map = AutoHashMap(i32, i32).init(std.testing.allocator); defer map.deinit(); var i: i32 = 0; while (i < 100) : (i += 1) { - try map.ensureCapacity(@intCast(u32, map.count() + 1)); + try map.ensureUnusedCapacity(1); map.putAssumeCapacity(i, i); // Remove to create tombstones that still count as load in the hashmap. _ = map.remove(i); @@ -1669,7 +1669,7 @@ test "std.hash_map clone" { try expectEqual(b.get(3).?, 3); } -test "std.hash_map ensureCapacity with existing elements" { +test "std.hash_map ensureTotalCapacity with existing elements" { var map = AutoHashMap(u32, u32).init(std.testing.allocator); defer map.deinit(); @@ -1677,16 +1677,16 @@ test "std.hash_map ensureCapacity with existing elements" { try expectEqual(map.count(), 1); try expectEqual(map.capacity(), @TypeOf(map).Unmanaged.minimal_capacity); - try map.ensureCapacity(65); + try map.ensureTotalCapacity(65); try expectEqual(map.count(), 1); try expectEqual(map.capacity(), 128); } -test "std.hash_map ensureCapacity satisfies max load factor" { +test "std.hash_map ensureTotalCapacity satisfies max load factor" { var map = AutoHashMap(u32, u32).init(std.testing.allocator); defer map.deinit(); - try map.ensureCapacity(127); + try map.ensureTotalCapacity(127); try expectEqual(map.capacity(), 256); } @@ -1870,7 +1870,7 @@ test "std.hash_map putAssumeCapacity" { var map = AutoHashMap(u32, u32).init(std.testing.allocator); defer map.deinit(); - try map.ensureCapacity(20); + try map.ensureTotalCapacity(20); var i: u32 = 0; while (i < 20) : (i += 1) { map.putAssumeCapacityNoClobber(i, i); diff --git a/lib/std/heap/general_purpose_allocator.zig b/lib/std/heap/general_purpose_allocator.zig index d51c349be2..3dd6b9db3d 100644 --- a/lib/std/heap/general_purpose_allocator.zig +++ b/lib/std/heap/general_purpose_allocator.zig @@ -746,10 +746,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { const new_aligned_size = math.max(len, ptr_align); if (new_aligned_size > largest_bucket_object_size) { - try self.large_allocations.ensureCapacity( - self.backing_allocator, - self.large_allocations.count() + 1, - ); + try self.large_allocations.ensureUnusedCapacity(self.backing_allocator, 1); const slice = try self.backing_allocator.allocFn(self.backing_allocator, len, ptr_align, len_align, ret_addr); diff --git a/lib/std/io/reader.zig b/lib/std/io/reader.zig index 3e8eb8ec24..f8e7650bc4 100644 --- a/lib/std/io/reader.zig +++ b/lib/std/io/reader.zig @@ -61,7 +61,7 @@ pub fn Reader( array_list: *std.ArrayListAligned(u8, alignment), max_append_size: usize, ) !void { - try array_list.ensureCapacity(math.min(max_append_size, 4096)); + try array_list.ensureTotalCapacity(math.min(max_append_size, 4096)); const original_len = array_list.items.len; var start_index: usize = original_len; while (true) { @@ -81,7 +81,7 @@ pub fn Reader( } // This will trigger ArrayList to expand superlinearly at whatever its growth rate is. - try array_list.ensureCapacity(start_index + 1); + try array_list.ensureTotalCapacity(start_index + 1); } } diff --git a/lib/std/json.zig b/lib/std/json.zig index e8f9d9d395..acae9e7d1f 100644 --- a/lib/std/json.zig +++ b/lib/std/json.zig @@ -1838,7 +1838,7 @@ fn parseInternal( else => {}, } - try arraylist.ensureCapacity(arraylist.items.len + 1); + try arraylist.ensureUnusedCapacity(1); const v = try parseInternal(ptrInfo.child, tok, tokens, options); arraylist.appendAssumeCapacity(v); } diff --git a/lib/std/multi_array_list.zig b/lib/std/multi_array_list.zig index 693937b399..243a7413fa 100644 --- a/lib/std/multi_array_list.zig +++ b/lib/std/multi_array_list.zig @@ -189,7 +189,7 @@ pub fn MultiArrayList(comptime S: type) type { /// sets the given index to the specified element. May reallocate /// and invalidate iterators. pub fn insert(self: *Self, gpa: *Allocator, index: usize, elem: S) void { - try self.ensureCapacity(gpa, self.len + 1); + try self.ensureUnusedCapacity(gpa, 1); self.insertAssumeCapacity(index, elem); } @@ -376,7 +376,7 @@ pub fn MultiArrayList(comptime S: type) type { pub fn clone(self: Self, gpa: *Allocator) !Self { var result = Self{}; errdefer result.deinit(gpa); - try result.ensureCapacity(gpa, self.len); + try result.ensureTotalCapacity(gpa, self.len); result.len = self.len; const self_slice = self.slice(); const result_slice = result.slice(); diff --git a/lib/std/unicode.zig b/lib/std/unicode.zig index 25f1ba1b48..5da7686d66 100644 --- a/lib/std/unicode.zig +++ b/lib/std/unicode.zig @@ -668,7 +668,7 @@ pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![:0]u var result = std.ArrayList(u16).init(allocator); errdefer result.deinit(); // optimistically guess that it will not require surrogate pairs - try result.ensureCapacity(utf8.len + 1); + try result.ensureTotalCapacity(utf8.len + 1); const view = try Utf8View.init(utf8); var it = view.iterator(); diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 842847a295..5bd5a6dfeb 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -17,7 +17,7 @@ pub fn parse(gpa: *Allocator, source: [:0]const u8) Allocator.Error!Ast { // Empirically, the zig std lib has an 8:1 ratio of source bytes to token count. const estimated_token_count = source.len / 8; - try tokens.ensureCapacity(gpa, estimated_token_count); + try tokens.ensureTotalCapacity(gpa, estimated_token_count); var tokenizer = std.zig.Tokenizer.init(source); while (true) { @@ -48,7 +48,7 @@ pub fn parse(gpa: *Allocator, source: [:0]const u8) Allocator.Error!Ast { // Empirically, Zig source code has a 2:1 ratio of tokens to AST nodes. // Make sure at least 1 so we can use appendAssumeCapacity on the root node below. const estimated_node_count = (tokens.len + 2) / 2; - try parser.nodes.ensureCapacity(gpa, estimated_node_count); + try parser.nodes.ensureTotalCapacity(gpa, estimated_node_count); // Root node must be index 0. // Root <- skip ContainerMembers eof @@ -138,7 +138,7 @@ const Parser = struct { fn addExtra(p: *Parser, extra: anytype) Allocator.Error!Node.Index { const fields = std.meta.fields(@TypeOf(extra)); - try p.extra_data.ensureCapacity(p.gpa, p.extra_data.items.len + fields.len); + try p.extra_data.ensureUnusedCapacity(p.gpa, fields.len); const result = @intCast(u32, p.extra_data.items.len); inline for (fields) |field| { comptime assert(field.field_type == Node.Index); diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig index 64242038d3..2a38195b1f 100644 --- a/lib/std/zig/string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -29,7 +29,7 @@ pub fn parseAppend(buf: *std.ArrayList(u8), bytes: []const u8) error{OutOfMemory const slice = bytes[1..]; const prev_len = buf.items.len; - try buf.ensureCapacity(prev_len + slice.len - 1); + try buf.ensureUnusedCapacity(slice.len - 1); errdefer buf.shrinkRetainingCapacity(prev_len); const State = enum { diff --git a/src/AstGen.zig b/src/AstGen.zig index b176136ba4..4bdfa1811c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -3515,7 +3515,7 @@ fn structDeclInner( defer wip_decls.deinit(gpa); // We don't know which members are fields until we iterate, so cannot do - // an accurate ensureCapacity yet. + // an accurate ensureTotalCapacity yet. var fields_data = ArrayListUnmanaged(u32){}; defer fields_data.deinit(gpa); @@ -3791,7 +3791,7 @@ fn unionDeclInner( defer wip_decls.deinit(gpa); // We don't know which members are fields until we iterate, so cannot do - // an accurate ensureCapacity yet. + // an accurate ensureTotalCapacity yet. var fields_data = ArrayListUnmanaged(u32){}; defer fields_data.deinit(gpa); diff --git a/src/Cache.zig b/src/Cache.zig index 28401c3d18..8a3b801e71 100644 --- a/src/Cache.zig +++ b/src/Cache.zig @@ -210,7 +210,7 @@ pub const Manifest = struct { pub fn addFile(self: *Manifest, file_path: []const u8, max_file_size: ?usize) !usize { assert(self.manifest_file == null); - try self.files.ensureCapacity(self.cache.gpa, self.files.items.len + 1); + try self.files.ensureUnusedCapacity(self.cache.gpa, 1); const resolved_path = try fs.path.resolve(self.cache.gpa, &[_][]const u8{file_path}); const idx = self.files.items.len; diff --git a/src/Compilation.zig b/src/Compilation.zig index 8edbb2dd73..c1dfe91dc2 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1097,7 +1097,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (feature.llvm_name) |llvm_name| { const plus_or_minus = "-+"[@boolToInt(is_enabled)]; - try buf.ensureCapacity(buf.items.len + 2 + llvm_name.len); + try buf.ensureUnusedCapacity(2 + llvm_name.len); buf.appendAssumeCapacity(plus_or_minus); buf.appendSliceAssumeCapacity(llvm_name); buf.appendSliceAssumeCapacity(","); @@ -1347,7 +1347,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { var system_libs: std.StringArrayHashMapUnmanaged(void) = .{}; errdefer system_libs.deinit(gpa); - try system_libs.ensureCapacity(gpa, options.system_libs.len); + try system_libs.ensureTotalCapacity(gpa, options.system_libs.len); for (options.system_libs) |lib_name| { system_libs.putAssumeCapacity(lib_name, {}); } @@ -1483,7 +1483,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { errdefer comp.astgen_wait_group.deinit(); // Add a `CObject` for each `c_source_files`. - try comp.c_object_table.ensureCapacity(gpa, options.c_source_files.len); + try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len); for (options.c_source_files) |c_source_file| { const c_object = try gpa.create(CObject); errdefer gpa.destroy(c_object); @@ -3084,7 +3084,7 @@ pub fn addCCArgs( // It would be really nice if there was a more compact way to communicate this info to Clang. const all_features_list = target.cpu.arch.allFeaturesList(); - try argv.ensureCapacity(argv.items.len + all_features_list.len * 4); + try argv.ensureUnusedCapacity(all_features_list.len * 4); for (all_features_list) |feature, index_usize| { const index = @intCast(std.Target.Cpu.Feature.Set.Index, index_usize); const is_enabled = target.cpu.features.isEnabled(index); @@ -3334,7 +3334,7 @@ fn failCObjWithOwnedErrorMsg( defer lock.release(); { errdefer err_msg.destroy(comp.gpa); - try comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.count() + 1); + try comp.failed_c_objects.ensureUnusedCapacity(comp.gpa, 1); } comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, err_msg); } @@ -3585,7 +3585,7 @@ fn detectLibCIncludeDirs( fn detectLibCFromLibCInstallation(arena: *Allocator, target: Target, lci: *const LibCInstallation) !LibCDirs { var list = std.ArrayList([]const u8).init(arena); - try list.ensureCapacity(4); + try list.ensureTotalCapacity(4); list.appendAssumeCapacity(lci.include_dir.?); @@ -3692,7 +3692,7 @@ fn setMiscFailure( comptime format: []const u8, args: anytype, ) Allocator.Error!void { - try comp.misc_failures.ensureCapacity(comp.gpa, comp.misc_failures.count() + 1); + try comp.misc_failures.ensureUnusedCapacity(comp.gpa, 1); const msg = try std.fmt.allocPrint(comp.gpa, format, args); comp.misc_failures.putAssumeCapacityNoClobber(tag, .{ .msg = msg }); } @@ -4027,7 +4027,7 @@ fn buildOutputFromZig( defer if (!keep_errors) errors.deinit(sub_compilation.gpa); if (errors.list.len != 0) { - try comp.misc_failures.ensureCapacity(comp.gpa, comp.misc_failures.count() + 1); + try comp.misc_failures.ensureUnusedCapacity(comp.gpa, 1); comp.misc_failures.putAssumeCapacityNoClobber(misc_task_tag, .{ .msg = try std.fmt.allocPrint(comp.gpa, "sub-compilation of {s} failed", .{ @tagName(misc_task_tag), @@ -4459,7 +4459,7 @@ pub fn build_crt_file( try sub_compilation.updateSubCompilation(); - try comp.crt_files.ensureCapacity(comp.gpa, comp.crt_files.count() + 1); + try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1); comp.crt_files.putAssumeCapacityNoClobber(basename, .{ .full_object_path = try sub_compilation.bin_file.options.emit.?.directory.join(comp.gpa, &[_][]const u8{ diff --git a/src/Liveness.zig b/src/Liveness.zig index 599507500e..6e6a3ccf1f 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -454,7 +454,7 @@ fn analyzeInst( } // Now we have to correctly populate new_set. if (new_set) |ns| { - try ns.ensureCapacity(gpa, @intCast(u32, ns.count() + then_table.count() + else_table.count())); + try ns.ensureUnusedCapacity(gpa, @intCast(u32, then_table.count() + else_table.count())); var it = then_table.keyIterator(); while (it.next()) |key| { _ = ns.putAssumeCapacity(key.*, {}); diff --git a/src/Module.zig b/src/Module.zig index 473c27e338..add0562d93 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3504,7 +3504,7 @@ pub fn scanNamespace( const zir = namespace.file_scope.zir; try mod.comp.work_queue.ensureUnusedCapacity(decls_len); - try namespace.decls.ensureCapacity(gpa, decls_len); + try namespace.decls.ensureTotalCapacity(gpa, decls_len); const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable; var extra_index = extra_start + bit_bags_count; @@ -4071,7 +4071,7 @@ pub fn getErrorValue(mod: *Module, name: []const u8) !std.StringHashMapUnmanaged } errdefer assert(mod.global_error_set.remove(name)); - try mod.error_name_list.ensureCapacity(mod.gpa, mod.error_name_list.items.len + 1); + try mod.error_name_list.ensureUnusedCapacity(mod.gpa, 1); gop.key_ptr.* = try mod.gpa.dupe(u8, name); gop.value_ptr.* = @intCast(ErrorInt, mod.error_name_list.items.len); mod.error_name_list.appendAssumeCapacity(gop.key_ptr.*); diff --git a/src/Package.zig b/src/Package.zig index 1f19c1d43a..3814f0eb95 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -111,7 +111,7 @@ pub fn destroy(pkg: *Package, gpa: *Allocator) void { } pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package) !void { - try pkg.table.ensureCapacity(gpa, pkg.table.count() + 1); + try pkg.table.ensureUnusedCapacity(gpa, 1); const name_dupe = try mem.dupe(gpa, u8, name); pkg.table.putAssumeCapacityNoClobber(name_dupe, package); } diff --git a/src/Sema.zig b/src/Sema.zig index 645e68c6ef..80cab6d00f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1130,7 +1130,7 @@ fn zirEnumDecl( const body_end = extra_index; extra_index += bit_bags_count; - try enum_obj.fields.ensureCapacity(&new_decl_arena.allocator, fields_len); + try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len); const any_values = for (sema.code.extra[body_end..][0..bit_bags_count]) |bag| { if (bag != 0) break true; } else false; @@ -3484,7 +3484,7 @@ fn zirMergeErrorSets(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Com }, .error_set => { const lhs_set = lhs_ty.castTag(.error_set).?.data; - try set.ensureCapacity(sema.gpa, set.count() + lhs_set.names_len); + try set.ensureUnusedCapacity(sema.gpa, lhs_set.names_len); for (lhs_set.names_ptr[0..lhs_set.names_len]) |name| { set.putAssumeCapacityNoClobber(name, {}); } @@ -3498,7 +3498,7 @@ fn zirMergeErrorSets(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Com }, .error_set => { const rhs_set = rhs_ty.castTag(.error_set).?.data; - try set.ensureCapacity(sema.gpa, set.count() + rhs_set.names_len); + try set.ensureUnusedCapacity(sema.gpa, rhs_set.names_len); for (rhs_set.names_ptr[0..rhs_set.names_len]) |name| { set.putAssumeCapacity(name, {}); } @@ -10361,7 +10361,7 @@ fn analyzeUnionFields( var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa); defer union_obj.owner_decl.value_arena.?.* = decl_arena.state; - try union_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len); + try union_obj.fields.ensureTotalCapacity(&decl_arena.allocator, fields_len); if (body.len != 0) { _ = try sema.analyzeBody(block, body); diff --git a/src/codegen.zig b/src/codegen.zig index 08ee358bff..e0047de1f7 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -141,7 +141,7 @@ pub fn generateSymbol( // TODO populate .debug_info for the array if (typed_value.val.castTag(.bytes)) |payload| { if (typed_value.ty.sentinel()) |sentinel| { - try code.ensureCapacity(code.items.len + payload.data.len + 1); + try code.ensureUnusedCapacity(payload.data.len + 1); code.appendSliceAssumeCapacity(payload.data); switch (try generateSymbol(bin_file, src_loc, .{ .ty = typed_value.ty.elemType(), @@ -568,7 +568,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { fn gen(self: *Self) !void { switch (arch) { .x86_64 => { - try self.code.ensureCapacity(self.code.items.len + 11); + try self.code.ensureUnusedCapacity(11); const cc = self.fn_type.fnCallingConvention(); if (cc != .Naked) { @@ -607,7 +607,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // Important to be after the possible self.code.items.len -= 5 above. try self.dbgSetEpilogueBegin(); - try self.code.ensureCapacity(self.code.items.len + 9); + try self.code.ensureUnusedCapacity(9); // add rsp, x if (aligned_stack_end > math.maxInt(i8)) { // example: 48 81 c4 ff ff ff 7f add rsp,0x7fffffff @@ -1960,7 +1960,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // // TODO: make this algorithm less bad - try self.code.ensureCapacity(self.code.items.len + 8); + try self.code.ensureUnusedCapacity(8); const lhs = try self.resolveInst(op_lhs); const rhs = try self.resolveInst(op_rhs); @@ -2447,13 +2447,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .register => |reg| { switch (self.debug_output) { .dwarf => |dbg_out| { - try dbg_out.dbg_info.ensureCapacity(dbg_out.dbg_info.items.len + 3); + try dbg_out.dbg_info.ensureUnusedCapacity(3); dbg_out.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); dbg_out.dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc 1, // ULEB128 dwarf expression length reg.dwarfLocOp(), }); - try dbg_out.dbg_info.ensureCapacity(dbg_out.dbg_info.items.len + 5 + name_with_null.len); + try dbg_out.dbg_info.ensureUnusedCapacity(5 + name_with_null.len); try self.addDbgInfoTypeReloc(ty); // DW.AT.type, DW.FORM.ref4 dbg_out.dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string }, @@ -2484,7 +2484,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try dbg_out.dbg_info.append(DW.OP.breg11); try leb128.writeILEB128(dbg_out.dbg_info.writer(), adjusted_stack_offset); - try dbg_out.dbg_info.ensureCapacity(dbg_out.dbg_info.items.len + 5 + name_with_null.len); + try dbg_out.dbg_info.ensureUnusedCapacity(5 + name_with_null.len); try self.addDbgInfoTypeReloc(ty); // DW.AT.type, DW.FORM.ref4 dbg_out.dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string }, @@ -2626,7 +2626,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { unreachable; // ff 14 25 xx xx xx xx call [addr] - try self.code.ensureCapacity(self.code.items.len + 7); + try self.code.ensureUnusedCapacity(7); self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 }); mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr); } else if (func_value.castTag(.extern_fn)) |_| { @@ -2839,7 +2839,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .memory = func.owner_decl.link.macho.local_sym_index, }); // callq *%rax - try self.code.ensureCapacity(self.code.items.len + 2); + try self.code.ensureUnusedCapacity(2); self.code.appendSliceAssumeCapacity(&[2]u8{ 0xff, 0xd0 }); }, .aarch64 => { @@ -2858,7 +2858,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (arch) { .x86_64 => { // callq - try self.code.ensureCapacity(self.code.items.len + 5); + try self.code.ensureUnusedCapacity(5); self.code.appendSliceAssumeCapacity(&[5]u8{ 0xe8, 0x0, 0x0, 0x0, 0x0 }); break :blk @intCast(u32, self.code.items.len) - 4; }, @@ -2932,7 +2932,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const got_addr = p9.bases.data; const got_index = func_payload.data.owner_decl.link.plan9.got_index.?; // ff 14 25 xx xx xx xx call [addr] - try self.code.ensureCapacity(self.code.items.len + 7); + try self.code.ensureUnusedCapacity(7); self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 }); const fn_got_addr = got_addr + got_index * ptr_bytes; mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), @intCast(u32, fn_got_addr)); @@ -3075,7 +3075,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const rhs = try self.resolveInst(bin_op.rhs); const result: MCValue = switch (arch) { .x86_64 => result: { - try self.code.ensureCapacity(self.code.items.len + 8); + try self.code.ensureUnusedCapacity(8); // There are 2 operands, destination and source. // Either one, but not both, can be a memory operand. @@ -3159,7 +3159,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const reloc: Reloc = switch (arch) { .i386, .x86_64 => reloc: { - try self.code.ensureCapacity(self.code.items.len + 6); + try self.code.ensureUnusedCapacity(6); const opcode: u8 = switch (cond) { .compare_flags_signed => |cmp_op| blk: { @@ -3519,7 +3519,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { fn jump(self: *Self, index: usize) !void { switch (arch) { .i386, .x86_64 => { - try self.code.ensureCapacity(self.code.items.len + 5); + try self.code.ensureUnusedCapacity(5); if (math.cast(i8, @intCast(i32, index) - (@intCast(i32, self.code.items.len + 2)))) |delta| { self.code.appendAssumeCapacity(0xeb); // jmp rel8 self.code.appendAssumeCapacity(@bitCast(u8, delta)); @@ -3657,7 +3657,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const block_data = self.blocks.getPtr(block).?; // Emit a jump with a relocation. It will be patched up after the block ends. - try block_data.relocs.ensureCapacity(self.gpa, block_data.relocs.items.len + 1); + try block_data.relocs.ensureUnusedCapacity(self.gpa, 1); switch (arch) { .i386, .x86_64 => { @@ -4041,7 +4041,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (adj_off > 128) { return self.fail("TODO implement set stack variable with large stack offset", .{}); } - try self.code.ensureCapacity(self.code.items.len + 8); + try self.code.ensureUnusedCapacity(8); switch (abi_size) { 1 => { return self.fail("TODO implement set abi_size=1 stack variable with immediate", .{}); @@ -4067,7 +4067,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // 64 bit write to memory would take two mov's anyways so we // insted just use two 32 bit writes to avoid register allocation - try self.code.ensureCapacity(self.code.items.len + 14); + try self.code.ensureUnusedCapacity(14); var buf: [8]u8 = undefined; mem.writeIntLittle(u64, &buf, x_big); diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 7429e3c3b0..f5796b06bc 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -629,7 +629,7 @@ pub const DeclGen = struct { const params = decl.ty.fnParamLen(); var i: usize = 0; - try self.args.ensureCapacity(params); + try self.args.ensureTotalCapacity(params); while (i < params) : (i += 1) { const param_type_id = self.spv.types.get(decl.ty.fnParamType(i)).?; const arg_result_id = self.spv.allocResultId(); diff --git a/src/libcxx.zig b/src/libcxx.zig index 0b29eda3ca..5ba6e47ece 100644 --- a/src/libcxx.zig +++ b/src/libcxx.zig @@ -108,7 +108,7 @@ pub fn buildLibCXX(comp: *Compilation) !void { const cxxabi_include_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libcxxabi", "include" }); const cxx_include_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libcxx", "include" }); var c_source_files = std.ArrayList(Compilation.CSourceFile).init(arena); - try c_source_files.ensureCapacity(libcxx_files.len); + try c_source_files.ensureTotalCapacity(libcxx_files.len); for (libcxx_files) |cxx_src| { var cflags = std.ArrayList([]const u8).init(arena); @@ -246,7 +246,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void { const cxxabi_include_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libcxxabi", "include" }); const cxx_include_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "libcxx", "include" }); var c_source_files = std.ArrayList(Compilation.CSourceFile).init(arena); - try c_source_files.ensureCapacity(libcxxabi_files.len); + try c_source_files.ensureTotalCapacity(libcxxabi_files.len); for (libcxxabi_files) |cxxabi_src| { var cflags = std.ArrayList([]const u8).init(arena); diff --git a/src/libtsan.zig b/src/libtsan.zig index 5b91dd5a38..2f288df9c2 100644 --- a/src/libtsan.zig +++ b/src/libtsan.zig @@ -34,7 +34,7 @@ pub fn buildTsan(comp: *Compilation) !void { }; var c_source_files = std.ArrayList(Compilation.CSourceFile).init(arena); - try c_source_files.ensureCapacity(c_source_files.items.len + tsan_sources.len); + try c_source_files.ensureUnusedCapacity(tsan_sources.len); const tsan_include_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{"tsan"}); for (tsan_sources) |tsan_src| { @@ -58,7 +58,7 @@ pub fn buildTsan(comp: *Compilation) !void { &darwin_tsan_sources else &unix_tsan_sources; - try c_source_files.ensureCapacity(c_source_files.items.len + platform_tsan_sources.len); + try c_source_files.ensureUnusedCapacity(platform_tsan_sources.len); for (platform_tsan_sources) |tsan_src| { var cflags = std.ArrayList([]const u8).init(arena); @@ -96,7 +96,7 @@ pub fn buildTsan(comp: *Compilation) !void { }); } - try c_source_files.ensureCapacity(c_source_files.items.len + sanitizer_common_sources.len); + try c_source_files.ensureUnusedCapacity(sanitizer_common_sources.len); const sanitizer_common_include_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{ "tsan", "sanitizer_common", }); @@ -123,7 +123,7 @@ pub fn buildTsan(comp: *Compilation) !void { &sanitizer_libcdep_sources else &sanitizer_nolibc_sources; - try c_source_files.ensureCapacity(c_source_files.items.len + to_c_or_not_to_c_sources.len); + try c_source_files.ensureUnusedCapacity(to_c_or_not_to_c_sources.len); for (to_c_or_not_to_c_sources) |c_src| { var cflags = std.ArrayList([]const u8).init(arena); @@ -143,7 +143,7 @@ pub fn buildTsan(comp: *Compilation) !void { }); } - try c_source_files.ensureCapacity(c_source_files.items.len + sanitizer_symbolizer_sources.len); + try c_source_files.ensureUnusedCapacity(sanitizer_symbolizer_sources.len); for (sanitizer_symbolizer_sources) |c_src| { var cflags = std.ArrayList([]const u8).init(arena); @@ -168,7 +168,7 @@ pub fn buildTsan(comp: *Compilation) !void { &[_][]const u8{"interception"}, ); - try c_source_files.ensureCapacity(c_source_files.items.len + interception_sources.len); + try c_source_files.ensureUnusedCapacity(interception_sources.len); for (interception_sources) |c_src| { var cflags = std.ArrayList([]const u8).init(arena); diff --git a/src/link.zig b/src/link.zig index 88159496f4..e649101f08 100644 --- a/src/link.zig +++ b/src/link.zig @@ -635,7 +635,7 @@ pub const File = struct { var object_files = std.ArrayList([*:0]const u8).init(base.allocator); defer object_files.deinit(); - try object_files.ensureCapacity(base.options.objects.len + comp.c_object_table.count() + 2); + try object_files.ensureTotalCapacity(base.options.objects.len + comp.c_object_table.count() + 2); for (base.options.objects) |obj_path| { object_files.appendAssumeCapacity(try arena.dupeZ(u8, obj_path)); } diff --git a/src/link/C.zig b/src/link/C.zig index 09f789f7d1..103cb60901 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -197,7 +197,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { defer all_buffers.deinit(); // This is at least enough until we get to the function bodies without error handling. - try all_buffers.ensureCapacity(self.decl_table.count() + 2); + try all_buffers.ensureTotalCapacity(self.decl_table.count() + 2); var file_size: u64 = zig_h.len; all_buffers.appendAssumeCapacity(.{ @@ -258,7 +258,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { file_size += err_typedef_buf.items.len; // Now the function bodies. - try all_buffers.ensureCapacity(all_buffers.items.len + fn_count); + try all_buffers.ensureUnusedCapacity(fn_count); for (self.decl_table.keys()) |decl| { if (!decl.has_tv) continue; if (decl.val.castTag(.function)) |_| { @@ -286,7 +286,7 @@ pub fn flushEmitH(module: *Module) !void { var all_buffers = std.ArrayList(std.os.iovec_const).init(module.gpa); defer all_buffers.deinit(); - try all_buffers.ensureCapacity(emit_h.decl_table.count() + 1); + try all_buffers.ensureTotalCapacity(emit_h.decl_table.count() + 1); var file_size: u64 = zig_h.len; all_buffers.appendAssumeCapacity(.{ diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 4f5df73f8d..41b88881c4 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -418,7 +418,7 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Coff { pub fn allocateDeclIndexes(self: *Coff, decl: *Module.Decl) !void { if (self.llvm_object) |_| return; - try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); + try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1); if (self.offset_table_free_list.popOrNull()) |i| { decl.link.coff.offset_table_index = i; @@ -793,7 +793,7 @@ pub fn updateDeclExports( for (exports) |exp| { if (exp.options.section) |section_name| { if (!mem.eql(u8, section_name, ".text")) { - try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.count() + 1); + try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); module.failed_exports.putAssumeCapacityNoClobber( exp, try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), @@ -804,7 +804,7 @@ pub fn updateDeclExports( if (mem.eql(u8, exp.options.name, "_start")) { self.entry_addr = decl.link.coff.getVAddr(self.*) - default_image_base; } else { - try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.count() + 1); + try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); module.failed_exports.putAssumeCapacityNoClobber( exp, try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: Exports other than '_start'", .{}), diff --git a/src/link/Elf.zig b/src/link/Elf.zig index f8cf70104f..98eb0815a7 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -411,7 +411,7 @@ fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u16) u64 { /// TODO Improve this to use a table. fn makeString(self: *Elf, bytes: []const u8) !u32 { - try self.shstrtab.ensureCapacity(self.base.allocator, self.shstrtab.items.len + bytes.len + 1); + try self.shstrtab.ensureUnusedCapacity(self.base.allocator, bytes.len + 1); const result = self.shstrtab.items.len; self.shstrtab.appendSliceAssumeCapacity(bytes); self.shstrtab.appendAssumeCapacity(0); @@ -420,7 +420,7 @@ fn makeString(self: *Elf, bytes: []const u8) !u32 { /// TODO Improve this to use a table. fn makeDebugString(self: *Elf, bytes: []const u8) !u32 { - try self.debug_strtab.ensureCapacity(self.base.allocator, self.debug_strtab.items.len + bytes.len + 1); + try self.debug_strtab.ensureUnusedCapacity(self.base.allocator, bytes.len + 1); const result = self.debug_strtab.items.len; self.debug_strtab.appendSliceAssumeCapacity(bytes); self.debug_strtab.appendAssumeCapacity(0); @@ -856,7 +856,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { // We have a function to compute the upper bound size, because it's needed // for determining where to put the offset of the first `LinkBlock`. - try di_buf.ensureCapacity(self.dbgInfoNeededHeaderBytes()); + try di_buf.ensureTotalCapacity(self.dbgInfoNeededHeaderBytes()); // initial length - length of the .debug_info contribution for this compilation unit, // not including the initial length itself. @@ -925,7 +925,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { // Enough for all the data without resizing. When support for more compilation units // is added, the size of this section will become more variable. - try di_buf.ensureCapacity(100); + try di_buf.ensureTotalCapacity(100); // initial length - length of the .debug_aranges contribution for this compilation unit, // not including the initial length itself. @@ -1004,7 +1004,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void { // The size of this header is variable, depending on the number of directories, // files, and padding. We have a function to compute the upper bound size, however, // because it's needed for determining where to put the offset of the first `SrcFn`. - try di_buf.ensureCapacity(self.dbgLineNeededHeaderBytes()); + try di_buf.ensureTotalCapacity(self.dbgLineNeededHeaderBytes()); // initial length - length of the .debug_line contribution for this compilation unit, // not including the initial length itself. @@ -1639,7 +1639,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { // Shared libraries. if (is_exe_or_dyn_lib) { const system_libs = self.base.options.system_libs.keys(); - try argv.ensureCapacity(argv.items.len + system_libs.len); + try argv.ensureUnusedCapacity(system_libs.len); for (system_libs) |link_lib| { // By this time, we depend on these libs being dynamically linked libraries and not static libraries // (the check for that needs to be earlier), but they could be full paths to .so files, in which @@ -2113,8 +2113,8 @@ pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { if (decl.link.elf.local_sym_index != 0) return; - try self.local_symbols.ensureCapacity(self.base.allocator, self.local_symbols.items.len + 1); - try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); + try self.local_symbols.ensureUnusedCapacity(self.base.allocator, 1); + try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1); if (self.local_symbol_free_list.popOrNull()) |i| { log.debug("reusing symbol index {d} for {s}", .{ i, decl.name }); @@ -2316,7 +2316,7 @@ pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liven defer deinitRelocs(self.base.allocator, &dbg_info_type_relocs); // For functions we need to add a prologue to the debug line program. - try dbg_line_buffer.ensureCapacity(26); + try dbg_line_buffer.ensureTotalCapacity(26); const decl = func.owner_decl; const line_off = @intCast(u28, decl.src_line + func.lbrace_line); @@ -2351,7 +2351,7 @@ pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liven // .debug_info subprogram const decl_name_with_null = decl.name[0 .. mem.lenZ(decl.name) + 1]; - try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 25 + decl_name_with_null.len); + try dbg_info_buffer.ensureUnusedCapacity(25 + decl_name_with_null.len); const fn_ret_type = decl.ty.fnReturnType(); const fn_ret_has_bits = fn_ret_type.hasCodeGenBits(); @@ -2593,7 +2593,7 @@ fn addDbgInfoType(self: *Elf, ty: Type, dbg_info_buffer: *std.ArrayList(u8)) !vo }, .Int => { const info = ty.intInfo(self.base.options.target); - try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 12); + try dbg_info_buffer.ensureUnusedCapacity(12); dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); // DW.AT.encoding, DW.FORM.data1 dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) { @@ -2607,7 +2607,7 @@ fn addDbgInfoType(self: *Elf, ty: Type, dbg_info_buffer: *std.ArrayList(u8)) !vo }, .Optional => { if (ty.isPtrLikeOptional()) { - try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 12); + try dbg_info_buffer.ensureUnusedCapacity(12); dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); // DW.AT.encoding, DW.FORM.data1 dbg_info_buffer.appendAssumeCapacity(DW.ATE.address); @@ -2747,14 +2747,14 @@ pub fn updateDeclExports( const tracy = trace(@src()); defer tracy.end(); - try self.global_symbols.ensureCapacity(self.base.allocator, self.global_symbols.items.len + exports.len); + try self.global_symbols.ensureUnusedCapacity(self.base.allocator, exports.len); if (decl.link.elf.local_sym_index == 0) return; const decl_sym = self.local_symbols.items[decl.link.elf.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.gpa, module.failed_exports.count() + 1); + try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); module.failed_exports.putAssumeCapacityNoClobber( exp, try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: ExportOptions.section", .{}), @@ -2772,7 +2772,7 @@ pub fn updateDeclExports( }, .Weak => elf.STB_WEAK, .LinkOnce => { - try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.count() + 1); + try module.failed_exports.ensureUnusedCapacity(module.gpa, 1); module.failed_exports.putAssumeCapacityNoClobber( exp, try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "Unimplemented: GlobalLinkage.LinkOnce", .{}), diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 6dd7e556b5..845122f5e3 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -102,7 +102,7 @@ pub fn calcAdhocSignature( var buffer = try allocator.alloc(u8, page_size); defer allocator.free(buffer); - try cdir.data.ensureCapacity(allocator, total_pages * hash_size + id.len + 1); + try cdir.data.ensureTotalCapacity(allocator, total_pages * hash_size + id.len + 1); // 1. Save the identifier and update offsets cdir.inner.identOffset = cdir.inner.length; diff --git a/src/link/MachO/DebugSymbols.zig b/src/link/MachO/DebugSymbols.zig index a8c0138f60..3e940da85d 100644 --- a/src/link/MachO/DebugSymbols.zig +++ b/src/link/MachO/DebugSymbols.zig @@ -353,7 +353,7 @@ pub fn flushModule(self: *DebugSymbols, allocator: *Allocator, options: link.Opt // We have a function to compute the upper bound size, because it's needed // for determining where to put the offset of the first `LinkBlock`. - try di_buf.ensureCapacity(self.dbgInfoNeededHeaderBytes()); + try di_buf.ensureTotalCapacity(self.dbgInfoNeededHeaderBytes()); // initial length - length of the .debug_info contribution for this compilation unit, // not including the initial length itself. @@ -408,7 +408,7 @@ pub fn flushModule(self: *DebugSymbols, allocator: *Allocator, options: link.Opt // Enough for all the data without resizing. When support for more compilation units // is added, the size of this section will become more variable. - try di_buf.ensureCapacity(100); + try di_buf.ensureTotalCapacity(100); // initial length - length of the .debug_aranges contribution for this compilation unit, // not including the initial length itself. @@ -479,7 +479,7 @@ pub fn flushModule(self: *DebugSymbols, allocator: *Allocator, options: link.Opt // The size of this header is variable, depending on the number of directories, // files, and padding. We have a function to compute the upper bound size, however, // because it's needed for determining where to put the offset of the first `SrcFn`. - try di_buf.ensureCapacity(self.dbgLineNeededHeaderBytes(module)); + try di_buf.ensureTotalCapacity(self.dbgLineNeededHeaderBytes(module)); // initial length - length of the .debug_line contribution for this compilation unit, // not including the initial length itself. @@ -607,7 +607,7 @@ fn copySegmentCommand(self: *DebugSymbols, allocator: *Allocator, base_cmd: Segm }; mem.copy(u8, &cmd.inner.segname, &base_cmd.inner.segname); - try cmd.sections.ensureCapacity(allocator, cmd.inner.nsects); + try cmd.sections.ensureTotalCapacity(allocator, cmd.inner.nsects); for (base_cmd.sections.items) |base_sect, i| { var sect = macho.section_64{ .sectname = undefined, @@ -855,7 +855,7 @@ pub fn initDeclDebugBuffers( switch (decl.ty.zigTypeTag()) { .Fn => { // For functions we need to add a prologue to the debug line program. - try dbg_line_buffer.ensureCapacity(26); + try dbg_line_buffer.ensureTotalCapacity(26); const func = decl.val.castTag(.function).?.data; const line_off = @intCast(u28, decl.src_line + func.lbrace_line); @@ -889,7 +889,7 @@ pub fn initDeclDebugBuffers( // .debug_info subprogram const decl_name_with_null = decl.name[0 .. mem.lenZ(decl.name) + 1]; - try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 27 + decl_name_with_null.len); + try dbg_info_buffer.ensureUnusedCapacity(27 + decl_name_with_null.len); const fn_ret_type = decl.ty.fnReturnType(); const fn_ret_has_bits = fn_ret_type.hasCodeGenBits(); @@ -1124,7 +1124,7 @@ fn addDbgInfoType( }, .Int => { const info = ty.intInfo(target); - try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 12); + try dbg_info_buffer.ensureUnusedCapacity(12); dbg_info_buffer.appendAssumeCapacity(abbrev_base_type); // DW.AT.encoding, DW.FORM.data1 dbg_info_buffer.appendAssumeCapacity(switch (info.signedness) { @@ -1261,7 +1261,7 @@ fn getDebugLineProgramEnd(self: DebugSymbols) u32 { /// TODO Improve this to use a table. fn makeDebugString(self: *DebugSymbols, allocator: *Allocator, bytes: []const u8) !u32 { - try self.debug_string_table.ensureCapacity(allocator, self.debug_string_table.items.len + bytes.len + 1); + try self.debug_string_table.ensureUnusedCapacity(allocator, bytes.len + 1); const result = self.debug_string_table.items.len; self.debug_string_table.appendSliceAssumeCapacity(bytes); self.debug_string_table.appendAssumeCapacity(0); diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 05d44559ce..6a1a74f79a 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -180,7 +180,7 @@ pub fn parse(self: *Dylib, allocator: *Allocator, target: std.Target) !void { fn readLoadCommands(self: *Dylib, allocator: *Allocator, reader: anytype) !void { const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0; - try self.load_commands.ensureCapacity(allocator, self.header.?.ncmds); + try self.load_commands.ensureTotalCapacity(allocator, self.header.?.ncmds); var i: u16 = 0; while (i < self.header.?.ncmds) : (i += 1) { diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 12c480b0f1..d71c549d77 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -261,7 +261,7 @@ pub fn readLoadCommands(self: *Object, allocator: *Allocator, reader: anytype) ! const header = self.header orelse unreachable; // Unreachable here signifies a fatal unexplored condition. const offset = self.file_offset orelse 0; - try self.load_commands.ensureCapacity(allocator, header.ncmds); + try self.load_commands.ensureTotalCapacity(allocator, header.ncmds); var i: u16 = 0; while (i < header.ncmds) : (i += 1) { diff --git a/src/link/MachO/Trie.zig b/src/link/MachO/Trie.zig index ab3a97eb33..7bf451f2c8 100644 --- a/src/link/MachO/Trie.zig +++ b/src/link/MachO/Trie.zig @@ -326,7 +326,7 @@ pub fn finalize(self: *Trie, allocator: *Allocator) !void { if (!self.trie_dirty) return; self.ordered_nodes.shrinkRetainingCapacity(0); - try self.ordered_nodes.ensureCapacity(allocator, self.node_count); + try self.ordered_nodes.ensureTotalCapacity(allocator, self.node_count); var fifo = std.fifo.LinearFifo(*Node, .Dynamic).init(allocator); defer fifo.deinit(); diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index d9ca056c8e..35512886d4 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -223,7 +223,7 @@ pub const SegmentCommand = struct { var segment = SegmentCommand{ .inner = inner, }; - try segment.sections.ensureCapacity(alloc, inner.nsects); + try segment.sections.ensureTotalCapacity(alloc, inner.nsects); var i: usize = 0; while (i < inner.nsects) : (i += 1) { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index fc559948c4..e3ec0bf255 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -172,8 +172,8 @@ pub fn deinit(self: *Wasm) void { pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void { if (decl.link.wasm.init) return; - try self.offset_table.ensureCapacity(self.base.allocator, self.offset_table.items.len + 1); - try self.symbols.ensureCapacity(self.base.allocator, self.symbols.items.len + 1); + try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1); + try self.symbols.ensureUnusedCapacity(self.base.allocator, 1); const block = &decl.link.wasm; block.init = true; diff --git a/src/main.zig b/src/main.zig index 5774bf2a67..b7b5d06264 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1704,22 +1704,22 @@ fn buildOutputType( } else true; if (!should_get_sdk_path) break :outer false; if (try std.zig.system.darwin.getSDKPath(arena, target_info.target)) |sdk_path| { - try clang_argv.ensureCapacity(clang_argv.items.len + 2); + try clang_argv.ensureUnusedCapacity(2); clang_argv.appendAssumeCapacity("-isysroot"); clang_argv.appendAssumeCapacity(sdk_path); break :outer true; } else break :outer false; } else false; - try clang_argv.ensureCapacity(clang_argv.items.len + paths.include_dirs.items.len * 2); + try clang_argv.ensureUnusedCapacity(paths.include_dirs.items.len * 2); const isystem_flag = if (has_sysroot) "-iwithsysroot" else "-isystem"; for (paths.include_dirs.items) |include_dir| { clang_argv.appendAssumeCapacity(isystem_flag); clang_argv.appendAssumeCapacity(include_dir); } - try clang_argv.ensureCapacity(clang_argv.items.len + paths.framework_dirs.items.len * 2); - try framework_dirs.ensureCapacity(framework_dirs.items.len + paths.framework_dirs.items.len); + try clang_argv.ensureUnusedCapacity(paths.framework_dirs.items.len * 2); + try framework_dirs.ensureUnusedCapacity(paths.framework_dirs.items.len); const iframework_flag = if (has_sysroot) "-iframeworkwithsysroot" else "-iframework"; for (paths.framework_dirs.items) |framework_dir| { clang_argv.appendAssumeCapacity(iframework_flag); @@ -2783,7 +2783,7 @@ pub fn cmdInit( fatal("unable to read template file 'build.zig': {s}", .{@errorName(err)}); }; var modified_build_zig_contents = std.ArrayList(u8).init(arena); - try modified_build_zig_contents.ensureCapacity(build_zig_contents.len); + try modified_build_zig_contents.ensureTotalCapacity(build_zig_contents.len); for (build_zig_contents) |c| { if (c == '$') { try modified_build_zig_contents.appendSlice(cwd_basename); @@ -3464,7 +3464,7 @@ fn fmtPathFile( // As a heuristic, we make enough capacity for the same as the input source. fmt.out_buffer.shrinkRetainingCapacity(0); - try fmt.out_buffer.ensureCapacity(source_code.len); + try fmt.out_buffer.ensureTotalCapacity(source_code.len); try tree.renderToArrayList(&fmt.out_buffer); if (mem.eql(u8, fmt.out_buffer.items, source_code)) diff --git a/src/mingw.zig b/src/mingw.zig index 84857df5b5..7771065a5a 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -312,7 +312,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { if (try man.hit()) { const digest = man.final(); - try comp.crt_files.ensureCapacity(comp.gpa, comp.crt_files.count() + 1); + try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1); comp.crt_files.putAssumeCapacityNoClobber(final_lib_basename, .{ .full_object_path = try comp.global_cache_directory.join(comp.gpa, &[_][]const u8{ "o", &digest, final_lib_basename, diff --git a/src/musl.zig b/src/musl.zig index 7dbaf3ba3f..3b5915719b 100644 --- a/src/musl.zig +++ b/src/musl.zig @@ -112,7 +112,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { var source_table = std.StringArrayHashMap(Ext).init(comp.gpa); defer source_table.deinit(); - try source_table.ensureCapacity(compat_time32_files.len + src_files.len); + try source_table.ensureTotalCapacity(compat_time32_files.len + src_files.len); for (src_files) |src_file| { try addSrcFile(arena, &source_table, src_file); @@ -231,7 +231,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { try sub_compilation.updateSubCompilation(); - try comp.crt_files.ensureCapacity(comp.gpa, comp.crt_files.count() + 1); + try comp.crt_files.ensureUnusedCapacity(comp.gpa, 1); const basename = try comp.gpa.dupe(u8, "libc.so"); errdefer comp.gpa.free(basename); diff --git a/src/translate_c.zig b/src/translate_c.zig index d980fa657e..7247ed50a9 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -4882,7 +4882,7 @@ fn finishTransFnProto( var fn_params = std.ArrayList(ast.Payload.Param).init(c.gpa); defer fn_params.deinit(); const param_count: usize = if (fn_proto_ty != null) fn_proto_ty.?.getNumParams() else 0; - try fn_params.ensureCapacity(param_count); + try fn_params.ensureTotalCapacity(param_count); var i: usize = 0; while (i < param_count) : (i += 1) { diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 3686b90bda..93acd464f4 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -728,13 +728,13 @@ pub fn render(gpa: *Allocator, nodes: []const Node) !std.zig.Ast { // Estimate that each top level node has 10 child nodes. const estimated_node_count = nodes.len * 10; - try ctx.nodes.ensureCapacity(gpa, estimated_node_count); + try ctx.nodes.ensureTotalCapacity(gpa, estimated_node_count); // Estimate that each each node has 2 tokens. const estimated_tokens_count = estimated_node_count * 2; - try ctx.tokens.ensureCapacity(gpa, estimated_tokens_count); + try ctx.tokens.ensureTotalCapacity(gpa, estimated_tokens_count); // Estimate that each each token is 3 bytes long. const estimated_buf_len = estimated_tokens_count * 3; - try ctx.buf.ensureCapacity(estimated_buf_len); + try ctx.buf.ensureTotalCapacity(estimated_buf_len); ctx.nodes.appendAssumeCapacity(.{ .tag = .root, @@ -839,7 +839,7 @@ const Context = struct { fn addExtra(c: *Context, extra: anytype) Allocator.Error!NodeIndex { const fields = std.meta.fields(@TypeOf(extra)); - try c.extra_data.ensureCapacity(c.gpa, c.extra_data.items.len + fields.len); + try c.extra_data.ensureUnusedCapacity(c.gpa, fields.len); const result = @intCast(u32, c.extra_data.items.len); inline for (fields) |field| { comptime std.debug.assert(field.field_type == NodeIndex); @@ -2797,7 +2797,7 @@ fn renderParams(c: *Context, params: []Payload.Param, is_var_args: bool) !std.Ar _ = try c.addToken(.l_paren, "("); var rendered = std.ArrayList(NodeIndex).init(c.gpa); errdefer rendered.deinit(); - try rendered.ensureCapacity(std.math.max(params.len, 1)); + try rendered.ensureTotalCapacity(std.math.max(params.len, 1)); for (params) |param, i| { if (i != 0) _ = try c.addToken(.comma, ","); From 224d4de747d02f4b7add6a7e18512467b6d33569 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 17 Sep 2021 12:43:47 -0700 Subject: [PATCH 034/160] Improve ensureTotalCapacity call in ChildProcess.collectOutputWindows Take current len and max_output_bytes into account instead of unconditionally using bump_amt --- lib/std/child_process.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 11b95a6e36..b6f4f4c516 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -276,7 +276,8 @@ pub const ChildProcess = struct { // Windows Async IO requires an initial call to ReadFile before waiting on the handle for ([_]u1{ 0, 1 }) |i| { - try outs[i].ensureTotalCapacity(bump_amt); + const new_capacity = std.math.min(outs[i].items.len + bump_amt, max_output_bytes); + try outs[i].ensureTotalCapacity(new_capacity); const buf = outs[i].unusedCapacitySlice(); _ = windows.kernel32.ReadFile(handles[i], buf.ptr, math.cast(u32, buf.len) catch maxInt(u32), null, &overlapped[i]); wait_objects[wait_object_count] = handles[i]; From 2a0c44fff3506cdc072d67374e7ffca495cebdc7 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 18 Sep 2021 22:32:09 +0200 Subject: [PATCH 035/160] elf: add amd64 relocation types I believe these are Linux specific so they will need to be os-gated in `elf.zig` at some point, but I reckon it should be fine to have them as-is right now since the ELF linker work will mainly be done on x86-64 Linux at first. --- lib/std/elf.zig | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 3213a26942..52d03c6c01 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -1601,3 +1601,90 @@ pub const SHN_LIVEPATCH = 0xff20; pub const SHN_ABS = 0xfff1; pub const SHN_COMMON = 0xfff2; pub const SHN_HIRESERVE = 0xffff; + +/// AMD x86-64 relocations. +/// No reloc +pub const R_X86_64_NONE = 0; +/// Direct 64 bit +pub const R_X86_64_64 = 1; +/// PC relative 32 bit signed +pub const R_X86_64_PC32 = 2; +/// 32 bit GOT entry +pub const R_X86_64_GOT32 = 3; +/// 32 bit PLT address +pub const R_X86_64_PLT32 = 4; +/// Copy symbol at runtime +pub const R_X86_64_COPY = 5; +/// Create GOT entry +pub const R_X86_64_GLOB_DAT = 6; +/// Create PLT entry +pub const R_X86_64_JUMP_SLOT = 7; +/// Adjust by program base +pub const R_X86_64_RELATIVE = 8; +/// 32 bit signed PC relative offset to GOT +pub const R_X86_64_GOTPCREL = 9; +/// Direct 32 bit zero extended +pub const R_X86_64_32 = 10; +/// Direct 32 bit sign extended +pub const R_X86_64_32S = 11; +/// Direct 16 bit zero extended +pub const R_X86_64_16 = 12; +/// 16 bit sign extended pc relative +pub const R_X86_64_PC16 = 13; +/// Direct 8 bit sign extended +pub const R_X86_64_8 = 14; +/// 8 bit sign extended pc relative +pub const R_X86_64_PC8 = 15; +/// ID of module containing symbol +pub const R_X86_64_DTPMOD64 = 16; +/// Offset in module's TLS block +pub const R_X86_64_DTPOFF64 = 17; +/// Offset in initial TLS block +pub const R_X86_64_TPOFF64 = 18; +/// 32 bit signed PC relative offset to two GOT entries for GD symbol +pub const R_X86_64_TLSGD = 19; +/// 32 bit signed PC relative offset to two GOT entries for LD symbol +pub const R_X86_64_TLSLD = 20; +/// Offset in TLS block +pub const R_X86_64_DTPOFF32 = 21; +/// 32 bit signed PC relative offset to GOT entry for IE symbol +pub const R_X86_64_GOTTPOFF = 22; +/// Offset in initial TLS block +pub const R_X86_64_TPOFF32 = 23; +/// PC relative 64 bit +pub const R_X86_64_PC64 = 24; +/// 64 bit offset to GOT +pub const R_X86_64_GOTOFF64 = 25; +/// 32 bit signed pc relative offset to GOT +pub const R_X86_64_GOTPC32 = 26; +/// 64 bit GOT entry offset +pub const R_X86_64_GOT64 = 27; +/// 64 bit PC relative offset to GOT entry +pub const R_X86_64_GOTPCREL64 = 28; +/// 64 bit PC relative offset to GOT +pub const R_X86_64_GOTPC64 = 29; +/// Like GOT64, says PLT entry needed +pub const R_X86_64_GOTPLT64 = 30; +/// 64-bit GOT relative offset to PLT entry +pub const R_X86_64_PLTOFF64 = 31; +/// Size of symbol plus 32-bit addend +pub const R_X86_64_SIZE32 = 32; +/// Size of symbol plus 64-bit addend +pub const R_X86_64_SIZE64 = 33; +/// GOT offset for TLS descriptor +pub const R_X86_64_GOTPC32_TLSDESC = 34; +/// Marker for call through TLS descriptor +pub const R_X86_64_TLSDESC_CALL = 35; +/// TLS descriptor +pub const R_X86_64_TLSDESC = 36; +/// Adjust indirectly by program base +pub const R_X86_64_IRELATIVE = 37; +/// 64-bit adjust by program base +pub const R_X86_64_RELATIVE64 = 38; +/// 39 Reserved was R_X86_64_PC32_BND +/// 40 Reserved was R_X86_64_PLT32_BND +/// Load from 32 bit signed pc relative offset to GOT entry without REX prefix, relaxable +pub const R_X86_64_GOTPCRELX = 41; +/// Load from 32 bit signed PC relative offset to GOT entry with REX prefix, relaxable +pub const R_X86_64_REX_GOTPCRELX = 42; +pub const R_X86_64_NUM = 43; From 9fa723ee5043a9d2cb017e417f2e27041f671146 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 19 Sep 2021 15:07:51 -0700 Subject: [PATCH 036/160] stage2: implement `@atomicStore` --- src/AstGen.zig | 2 +- src/Sema.zig | 70 +++++++++++++++++++++++++++----- src/Zir.zig | 19 +++++++-- test/behavior/atomics.zig | 8 ++++ test/behavior/atomics_stage1.zig | 8 ---- 5 files changed, 84 insertions(+), 23 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 4bdfa1811c..be3613dfb9 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2118,7 +2118,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .select, .atomic_load, .atomic_rmw, - .atomic_store, .mul_add, .builtin_call, .field_ptr_type, @@ -2164,6 +2163,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .@"export", .set_eval_branch_quota, .ensure_err_payload_void, + .atomic_store, .store, .store_node, .store_to_block_ptr, diff --git a/src/Sema.zig b/src/Sema.zig index 80cab6d00f..5471795317 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -314,7 +314,6 @@ pub fn analyzeBody( .select => try sema.zirSelect(block, inst), .atomic_load => try sema.zirAtomicLoad(block, inst), .atomic_rmw => try sema.zirAtomicRmw(block, inst), - .atomic_store => try sema.zirAtomicStore(block, inst), .mul_add => try sema.zirMulAdd(block, inst), .builtin_call => try sema.zirBuiltinCall(block, inst), .field_ptr_type => try sema.zirFieldPtrType(block, inst), @@ -413,6 +412,11 @@ pub fn analyzeBody( i += 1; continue; }, + .atomic_store => { + try sema.zirAtomicStore(block, inst); + i += 1; + continue; + }, .store => { try sema.zirStore(block, inst); i += 1; @@ -7669,6 +7673,8 @@ fn zirCmpxchg( if (try sema.resolveMaybeUndefVal(block, expected_src, expected_value)) |expected_val| { if (try sema.resolveMaybeUndefVal(block, new_value_src, new_value)) |new_val| { if (expected_val.isUndef() or new_val.isUndef()) { + // TODO: this should probably cause the memory stored at the pointer + // to become undef as well return sema.addConstUndef(result_ty); } const stored_val = (try ptr_val.pointerDeref(sema.arena)) orelse break :rs ptr_src; @@ -7830,10 +7836,38 @@ fn zirAtomicRmw(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE }); } -fn zirAtomicStore(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirAtomicStore(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.AtomicStore, inst_data.payload_index).data; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirAtomicStore", .{}); + // zig fmt: off + const operand_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const operand_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node }; + // zig fmt: on + const ptr = sema.resolveInst(extra.ptr); + const operand_ty = sema.typeOf(ptr).elemType(); + try sema.checkAtomicOperandType(block, operand_ty_src, operand_ty); + const operand = try sema.coerce(block, operand_ty, sema.resolveInst(extra.operand), operand_src); + const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering); + + const air_tag: Air.Inst.Tag = switch (order) { + .Acquire, .AcqRel => { + return sema.mod.fail( + &block.base, + order_src, + "@atomicStore atomic ordering must not be Acquire or AcqRel", + .{}, + ); + }, + .Unordered => .atomic_store_unordered, + .Monotonic => .atomic_store_monotonic, + .Release => .atomic_store_release, + .SeqCst => .atomic_store_seq_cst, + }; + + return sema.storePtr2(block, src, ptr, ptr_src, operand, operand_src, air_tag); } fn zirMulAdd(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -9310,25 +9344,39 @@ fn coerceVarArgParam( return inst; } +// TODO migrate callsites to use storePtr2 instead. fn storePtr( sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ptr: Air.Inst.Ref, - uncasted_value: Air.Inst.Ref, + uncasted_operand: Air.Inst.Ref, +) !void { + return sema.storePtr2(block, src, ptr, src, uncasted_operand, src, .store); +} + +fn storePtr2( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + ptr: Air.Inst.Ref, + ptr_src: LazySrcLoc, + uncasted_operand: Air.Inst.Ref, + operand_src: LazySrcLoc, + air_tag: Air.Inst.Tag, ) !void { const ptr_ty = sema.typeOf(ptr); if (ptr_ty.isConstPtr()) return sema.mod.fail(&block.base, src, "cannot assign to constant", .{}); const elem_ty = ptr_ty.elemType(); - const value = try sema.coerce(block, elem_ty, uncasted_value, src); + const operand = try sema.coerce(block, elem_ty, uncasted_operand, operand_src); if ((try sema.typeHasOnePossibleValue(block, src, elem_ty)) != null) return; - if (try sema.resolveDefinedValue(block, src, ptr)) |ptr_val| { + const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: { if (ptr_val.castTag(.decl_ref_mut)) |decl_ref_mut| { - const const_val = (try sema.resolveMaybeUndefVal(block, src, value)) orelse + const const_val = (try sema.resolveMaybeUndefVal(block, operand_src, operand)) orelse return sema.mod.fail(&block.base, src, "cannot store runtime value in compile time variable", .{}); if (decl_ref_mut.data.runtime_index < block.runtime_index) { @@ -9365,11 +9413,13 @@ fn storePtr( old_arena.deinit(); return; } - } + break :rs operand_src; + } else ptr_src; + // TODO handle if the element type requires comptime - try sema.requireRuntimeBlock(block, src); - _ = try block.addBinOp(.store, ptr, value); + try sema.requireRuntimeBlock(block, runtime_src); + _ = try block.addBinOp(air_tag, ptr, operand); } fn bitcast( diff --git a/src/Zir.zig b/src/Zir.zig index fcc2d5f330..f8dbef2534 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -3058,7 +3058,6 @@ const Writer = struct { .shuffle, .select, .atomic_rmw, - .atomic_store, .mul_add, .builtin_call, .field_parent_ptr, @@ -3071,9 +3070,8 @@ const Writer = struct { .struct_init_ref, => try self.writeStructInit(stream, inst), - .cmpxchg_strong, - .cmpxchg_weak, - => try self.writeCmpxchg(stream, inst), + .cmpxchg_strong, .cmpxchg_weak => try self.writeCmpxchg(stream, inst), + .atomic_store => try self.writeAtomicStore(stream, inst), .struct_init_anon, .struct_init_anon_ref, @@ -3493,6 +3491,19 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writeAtomicStore(self: *Writer, stream: anytype, inst: Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Inst.AtomicStore, inst_data.payload_index).data; + + try self.writeInstRef(stream, extra.ptr); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.operand); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.ordering); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writeStructInitAnon(self: *Writer, stream: anytype, inst: Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Inst.StructInitAnon, inst_data.payload_index); diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index efd63f1ac5..75e33477bc 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -130,3 +130,11 @@ test "atomic load and rmw with enum" { try expect(@atomicLoad(Value, &x, .SeqCst) != .a); try expect(@atomicLoad(Value, &x, .SeqCst) != .b); } + +test "atomic store" { + var x: u32 = 0; + @atomicStore(u32, &x, 1, .SeqCst); + try expect(@atomicLoad(u32, &x, .SeqCst) == 1); + @atomicStore(u32, &x, 12345678, .SeqCst); + try expect(@atomicLoad(u32, &x, .SeqCst) == 12345678); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index 936c06b155..22e3ae5939 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -3,14 +3,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const builtin = @import("builtin"); -test "atomic store" { - var x: u32 = 0; - @atomicStore(u32, &x, 1, .SeqCst); - try expect(@atomicLoad(u32, &x, .SeqCst) == 1); - @atomicStore(u32, &x, 12345678, .SeqCst); - try expect(@atomicLoad(u32, &x, .SeqCst) == 12345678); -} - test "atomic store comptime" { comptime try testAtomicStore(); try testAtomicStore(); From ccc7f9987debd2112d1432f7aa58a81a0814e81d Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 2 Sep 2021 14:45:00 +0200 Subject: [PATCH 037/160] Address spaces: addrspace(A) parsing The grammar for function prototypes, (global) variable declarations, and pointer types now accepts an optional addrspace(A) modifier. --- lib/std/zig/Ast.zig | 54 ++++++++++++++++-- lib/std/zig/parse.zig | 115 ++++++++++++++++++++++++++++---------- lib/std/zig/tokenizer.zig | 3 + src/translate_c/ast.zig | 1 + 4 files changed, 139 insertions(+), 34 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 35e75f4db2..a62e9c105d 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -262,6 +262,9 @@ pub fn renderError(tree: Tree, parse_error: Error, stream: anytype) !void { token_tags[parse_error.token].symbol(), }); }, + .extra_addrspace_qualifier => { + return stream.writeAll("extra addrspace qualifier"); + }, .extra_align_qualifier => { return stream.writeAll("extra align qualifier"); }, @@ -1021,7 +1024,7 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { }, .fn_proto_one => { const extra = tree.extraData(datas[n].lhs, Node.FnProtoOne); - // linksection, callconv, align can appear in any order, so we + // addrspace, linksection, callconv, align can appear in any order, so we // find the last one here. var max_node: Node.Index = datas[n].rhs; var max_start = token_starts[main_tokens[max_node]]; @@ -1034,6 +1037,14 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { max_offset = 1; // for the rparen } } + if (extra.addrspace_expr != 0) { + const start = token_starts[main_tokens[extra.addrspace_expr]]; + if (start > max_start) { + max_node = extra.addrspace_expr; + max_start = start; + max_offset = 1; // for the rparen + } + } if (extra.section_expr != 0) { const start = token_starts[main_tokens[extra.section_expr]]; if (start > max_start) { @@ -1055,7 +1066,7 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { }, .fn_proto => { const extra = tree.extraData(datas[n].lhs, Node.FnProto); - // linksection, callconv, align can appear in any order, so we + // addrspace, linksection, callconv, align can appear in any order, so we // find the last one here. var max_node: Node.Index = datas[n].rhs; var max_start = token_starts[main_tokens[max_node]]; @@ -1068,6 +1079,14 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { max_offset = 1; // for the rparen } } + if (extra.addrspace_expr != 0) { + const start = token_starts[main_tokens[extra.addrspace_expr]]; + if (start > max_start) { + max_node = extra.addrspace_expr; + max_start = start; + max_offset = 1; // for the rparen + } + } if (extra.section_expr != 0) { const start = token_starts[main_tokens[extra.section_expr]]; if (start > max_start) { @@ -1138,6 +1157,7 @@ pub fn globalVarDecl(tree: Tree, node: Node.Index) full.VarDecl { return tree.fullVarDecl(.{ .type_node = extra.type_node, .align_node = extra.align_node, + .addrspace_node = extra.addrspace_node, .section_node = extra.section_node, .init_node = data.rhs, .mut_token = tree.nodes.items(.main_token)[node], @@ -1151,6 +1171,7 @@ pub fn localVarDecl(tree: Tree, node: Node.Index) full.VarDecl { return tree.fullVarDecl(.{ .type_node = extra.type_node, .align_node = extra.align_node, + .addrspace_node = 0, .section_node = 0, .init_node = data.rhs, .mut_token = tree.nodes.items(.main_token)[node], @@ -1163,6 +1184,7 @@ pub fn simpleVarDecl(tree: Tree, node: Node.Index) full.VarDecl { return tree.fullVarDecl(.{ .type_node = data.lhs, .align_node = 0, + .addrspace_node = 0, .section_node = 0, .init_node = data.rhs, .mut_token = tree.nodes.items(.main_token)[node], @@ -1175,6 +1197,7 @@ pub fn alignedVarDecl(tree: Tree, node: Node.Index) full.VarDecl { return tree.fullVarDecl(.{ .type_node = 0, .align_node = data.lhs, + .addrspace_node = 0, .section_node = 0, .init_node = data.rhs, .mut_token = tree.nodes.items(.main_token)[node], @@ -1249,6 +1272,7 @@ pub fn fnProtoSimple(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full. .return_type = data.rhs, .params = params, .align_expr = 0, + .addrspace_expr = 0, .section_expr = 0, .callconv_expr = 0, }); @@ -1265,6 +1289,7 @@ pub fn fnProtoMulti(tree: Tree, node: Node.Index) full.FnProto { .return_type = data.rhs, .params = params, .align_expr = 0, + .addrspace_expr = 0, .section_expr = 0, .callconv_expr = 0, }); @@ -1282,6 +1307,7 @@ pub fn fnProtoOne(tree: Tree, buffer: *[1]Node.Index, node: Node.Index) full.FnP .return_type = data.rhs, .params = params, .align_expr = extra.align_expr, + .addrspace_expr = extra.addrspace_expr, .section_expr = extra.section_expr, .callconv_expr = extra.callconv_expr, }); @@ -1298,6 +1324,7 @@ pub fn fnProto(tree: Tree, node: Node.Index) full.FnProto { .return_type = data.rhs, .params = params, .align_expr = extra.align_expr, + .addrspace_expr = extra.addrspace_expr, .section_expr = extra.section_expr, .callconv_expr = extra.callconv_expr, }); @@ -1453,6 +1480,7 @@ pub fn ptrTypeAligned(tree: Tree, node: Node.Index) full.PtrType { return tree.fullPtrType(.{ .main_token = tree.nodes.items(.main_token)[node], .align_node = data.lhs, + .addrspace_node = 0, .sentinel = 0, .bit_range_start = 0, .bit_range_end = 0, @@ -1466,6 +1494,7 @@ pub fn ptrTypeSentinel(tree: Tree, node: Node.Index) full.PtrType { return tree.fullPtrType(.{ .main_token = tree.nodes.items(.main_token)[node], .align_node = 0, + .addrspace_node = 0, .sentinel = data.lhs, .bit_range_start = 0, .bit_range_end = 0, @@ -1480,6 +1509,7 @@ pub fn ptrType(tree: Tree, node: Node.Index) full.PtrType { return tree.fullPtrType(.{ .main_token = tree.nodes.items(.main_token)[node], .align_node = extra.align_node, + .addrspace_node = extra.addrspace_node, .sentinel = extra.sentinel, .bit_range_start = 0, .bit_range_end = 0, @@ -1494,6 +1524,7 @@ pub fn ptrTypeBitRange(tree: Tree, node: Node.Index) full.PtrType { return tree.fullPtrType(.{ .main_token = tree.nodes.items(.main_token)[node], .align_node = extra.align_node, + .addrspace_node = extra.addrspace_node, .sentinel = extra.sentinel, .bit_range_start = extra.bit_range_start, .bit_range_end = extra.bit_range_end, @@ -2063,6 +2094,7 @@ pub const full = struct { mut_token: TokenIndex, type_node: Node.Index, align_node: Node.Index, + addrspace_node: Node.Index, section_node: Node.Index, init_node: Node.Index, }; @@ -2130,6 +2162,7 @@ pub const full = struct { return_type: Node.Index, params: []const Node.Index, align_expr: Node.Index, + addrspace_expr: Node.Index, section_expr: Node.Index, callconv_expr: Node.Index, }; @@ -2288,6 +2321,7 @@ pub const full = struct { pub const Components = struct { main_token: TokenIndex, align_node: Node.Index, + addrspace_node: Node.Index, sentinel: Node.Index, bit_range_start: Node.Index, bit_range_end: Node.Index, @@ -2397,6 +2431,7 @@ pub const Error = struct { expected_var_decl_or_fn, expected_loop_payload, expected_container, + extra_addrspace_qualifier, extra_align_qualifier, extra_allowzero_qualifier, extra_const_qualifier, @@ -2723,13 +2758,13 @@ pub const Node = struct { /// main_token is the `fn` keyword. /// extern function declarations use this tag. fn_proto_multi, - /// `fn(a: b) rhs linksection(e) callconv(f)`. `FnProtoOne[lhs]`. + /// `fn(a: b) rhs addrspace(e) linksection(f) callconv(g)`. `FnProtoOne[lhs]`. /// zero or one parameters. /// anytype and ... parameters are omitted from the AST tree. /// main_token is the `fn` keyword. /// extern function declarations use this tag. fn_proto_one, - /// `fn(a: b, c: d) rhs linksection(e) callconv(f)`. `FnProto[lhs]`. + /// `fn(a: b, c: d) rhs addrspace(e) linksection(f) callconv(g)`. `FnProto[lhs]`. /// anytype and ... parameters are omitted from the AST tree. /// main_token is the `fn` keyword. /// extern function declarations use this tag. @@ -2893,11 +2928,13 @@ pub const Node = struct { pub const PtrType = struct { sentinel: Index, align_node: Index, + addrspace_node: Index, }; pub const PtrTypeBitRange = struct { sentinel: Index, align_node: Index, + addrspace_node: Index, bit_range_start: Index, bit_range_end: Index, }; @@ -2920,8 +2957,13 @@ pub const Node = struct { }; pub const GlobalVarDecl = struct { + /// Populated if there is an explicit type ascription. type_node: Index, + /// Populated if align(A) is present. align_node: Index, + /// Populated if linksection(A) is present. + addrspace_node: Index, + /// Populated if addrspace(A) is present. section_node: Index, }; @@ -2954,6 +2996,8 @@ pub const Node = struct { /// Populated if align(A) is present. align_expr: Index, /// Populated if linksection(A) is present. + addrspace_expr: Index, + /// Populated if addrspace(A) is present. section_expr: Index, /// Populated if callconv(A) is present. callconv_expr: Index, @@ -2964,6 +3008,8 @@ pub const Node = struct { params_end: Index, /// Populated if align(A) is present. align_expr: Index, + /// Populated if addrspace(A) is present. + addrspace_expr: Index, /// Populated if linksection(A) is present. section_expr: Index, /// Populated if callconv(A) is present. diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 5bd5a6dfeb..f7697027a3 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -629,7 +629,7 @@ const Parser = struct { }; } - /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr + /// FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr fn parseFnProto(p: *Parser) !Node.Index { const fn_token = p.eatToken(.keyword_fn) orelse return null_node; @@ -639,6 +639,7 @@ const Parser = struct { _ = p.eatToken(.identifier); const params = try p.parseParamDeclList(); const align_expr = try p.parseByteAlign(); + const addrspace_expr = try p.parseAddrSpace(); const section_expr = try p.parseLinkSection(); const callconv_expr = try p.parseCallconv(); _ = p.eatToken(.bang); @@ -650,7 +651,7 @@ const Parser = struct { try p.warn(.expected_return_type); } - if (align_expr == 0 and section_expr == 0 and callconv_expr == 0) { + if (align_expr == 0 and section_expr == 0 and callconv_expr == 0 and addrspace_expr == 0) { switch (params) { .zero_or_one => |param| return p.setNode(fn_proto_index, .{ .tag = .fn_proto_simple, @@ -683,6 +684,7 @@ const Parser = struct { .lhs = try p.addExtra(Node.FnProtoOne{ .param = param, .align_expr = align_expr, + .addrspace_expr = addrspace_expr, .section_expr = section_expr, .callconv_expr = callconv_expr, }), @@ -698,6 +700,7 @@ const Parser = struct { .params_start = span.start, .params_end = span.end, .align_expr = align_expr, + .addrspace_expr = addrspace_expr, .section_expr = section_expr, .callconv_expr = callconv_expr, }), @@ -708,7 +711,7 @@ const Parser = struct { } } - /// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON + /// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? (EQUAL Expr)? SEMICOLON fn parseVarDecl(p: *Parser) !Node.Index { const mut_token = p.eatToken(.keyword_const) orelse p.eatToken(.keyword_var) orelse @@ -717,9 +720,10 @@ const Parser = struct { _ = try p.expectToken(.identifier); const type_node: Node.Index = if (p.eatToken(.colon) == null) 0 else try p.expectTypeExpr(); const align_node = try p.parseByteAlign(); + const addrspace_node = try p.parseAddrSpace(); const section_node = try p.parseLinkSection(); const init_node: Node.Index = if (p.eatToken(.equal) == null) 0 else try p.expectExpr(); - if (section_node == 0) { + if (section_node == 0 and addrspace_node == 0) { if (align_node == 0) { return p.addNode(.{ .tag = .simple_var_decl, @@ -759,6 +763,7 @@ const Parser = struct { .lhs = try p.addExtra(Node.GlobalVarDecl{ .type_node = type_node, .align_node = align_node, + .addrspace_node = addrspace_node, .section_node = section_node, }), .rhs = init_node, @@ -1440,8 +1445,8 @@ const Parser = struct { /// PrefixTypeOp /// <- QUESTIONMARK /// / KEYWORD_anyframe MINUSRARROW - /// / SliceTypeStart (ByteAlign / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* - /// / PtrTypeStart (KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* + /// / SliceTypeStart (ByteAlign / AddrSpace / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* + /// / PtrTypeStart (AddrSpace / KEYWORD_align LPAREN Expr (COLON INTEGER COLON INTEGER)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)* /// / ArrayTypeStart /// SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET /// PtrTypeStart @@ -1474,16 +1479,7 @@ const Parser = struct { const asterisk = p.nextToken(); const mods = try p.parsePtrModifiers(); const elem_type = try p.expectTypeExpr(); - if (mods.bit_range_start == 0) { - return p.addNode(.{ - .tag = .ptr_type_aligned, - .main_token = asterisk, - .data = .{ - .lhs = mods.align_node, - .rhs = elem_type, - }, - }); - } else { + if (mods.bit_range_start != 0) { return p.addNode(.{ .tag = .ptr_type_bit_range, .main_token = asterisk, @@ -1491,12 +1487,35 @@ const Parser = struct { .lhs = try p.addExtra(Node.PtrTypeBitRange{ .sentinel = 0, .align_node = mods.align_node, + .addrspace_node = mods.addrspace_node, .bit_range_start = mods.bit_range_start, .bit_range_end = mods.bit_range_end, }), .rhs = elem_type, }, }); + } else if (mods.addrspace_node != 0) { + return p.addNode(.{ + .tag = .ptr_type, + .main_token = asterisk, + .data = .{ + .lhs = try p.addExtra(Node.PtrType{ + .sentinel = 0, + .align_node = mods.align_node, + .addrspace_node = mods.addrspace_node, + }), + .rhs = elem_type, + }, + }); + } else { + return p.addNode(.{ + .tag = .ptr_type_aligned, + .main_token = asterisk, + .data = .{ + .lhs = mods.align_node, + .rhs = elem_type, + }, + }); } }, .asterisk_asterisk => { @@ -1504,16 +1523,7 @@ const Parser = struct { const mods = try p.parsePtrModifiers(); const elem_type = try p.expectTypeExpr(); const inner: Node.Index = inner: { - if (mods.bit_range_start == 0) { - break :inner try p.addNode(.{ - .tag = .ptr_type_aligned, - .main_token = asterisk, - .data = .{ - .lhs = mods.align_node, - .rhs = elem_type, - }, - }); - } else { + if (mods.bit_range_start != 0) { break :inner try p.addNode(.{ .tag = .ptr_type_bit_range, .main_token = asterisk, @@ -1521,12 +1531,35 @@ const Parser = struct { .lhs = try p.addExtra(Node.PtrTypeBitRange{ .sentinel = 0, .align_node = mods.align_node, + .addrspace_node = mods.addrspace_node, .bit_range_start = mods.bit_range_start, .bit_range_end = mods.bit_range_end, }), .rhs = elem_type, }, }); + } else if (mods.addrspace_node != 0) { + break :inner try p.addNode(.{ + .tag = .ptr_type, + .main_token = asterisk, + .data = .{ + .lhs = try p.addExtra(Node.PtrType{ + .sentinel = 0, + .align_node = mods.align_node, + .addrspace_node = mods.addrspace_node, + }), + .rhs = elem_type, + }, + }); + } else { + break :inner try p.addNode(.{ + .tag = .ptr_type_aligned, + .main_token = asterisk, + .data = .{ + .lhs = mods.align_node, + .rhs = elem_type, + }, + }); } }; return p.addNode(.{ @@ -1560,7 +1593,7 @@ const Parser = struct { const mods = try p.parsePtrModifiers(); const elem_type = try p.expectTypeExpr(); if (mods.bit_range_start == 0) { - if (sentinel == 0) { + if (sentinel == 0 and mods.addrspace_node == 0) { return p.addNode(.{ .tag = .ptr_type_aligned, .main_token = asterisk, @@ -1569,7 +1602,7 @@ const Parser = struct { .rhs = elem_type, }, }); - } else if (mods.align_node == 0) { + } else if (mods.align_node == 0 and mods.addrspace_node == 0) { return p.addNode(.{ .tag = .ptr_type_sentinel, .main_token = asterisk, @@ -1586,6 +1619,7 @@ const Parser = struct { .lhs = try p.addExtra(Node.PtrType{ .sentinel = sentinel, .align_node = mods.align_node, + .addrspace_node = mods.addrspace_node, }), .rhs = elem_type, }, @@ -1599,6 +1633,7 @@ const Parser = struct { .lhs = try p.addExtra(Node.PtrTypeBitRange{ .sentinel = sentinel, .align_node = mods.align_node, + .addrspace_node = mods.addrspace_node, .bit_range_start = mods.bit_range_start, .bit_range_end = mods.bit_range_end, }), @@ -1624,7 +1659,7 @@ const Parser = struct { .token = p.nodes.items(.main_token)[mods.bit_range_start], }); } - if (sentinel == 0) { + if (sentinel == 0 and mods.addrspace_node == 0) { return p.addNode(.{ .tag = .ptr_type_aligned, .main_token = lbracket, @@ -1633,7 +1668,7 @@ const Parser = struct { .rhs = elem_type, }, }); - } else if (mods.align_node == 0) { + } else if (mods.align_node == 0 and mods.addrspace_node == 0) { return p.addNode(.{ .tag = .ptr_type_sentinel, .main_token = lbracket, @@ -1650,6 +1685,7 @@ const Parser = struct { .lhs = try p.addExtra(Node.PtrType{ .sentinel = sentinel, .align_node = mods.align_node, + .addrspace_node = mods.addrspace_node, }), .rhs = elem_type, }, @@ -1661,6 +1697,7 @@ const Parser = struct { .keyword_const, .keyword_volatile, .keyword_allowzero, + .keyword_addrspace, => return p.fail(.ptr_mod_on_array_child_type), else => {}, } @@ -2879,6 +2916,15 @@ const Parser = struct { return expr_node; } + /// AddrSpace <- KEYWORD_addrspace LPAREN Expr RPAREN + fn parseAddrSpace(p: *Parser) !Node.Index { + _ = p.eatToken(.keyword_addrspace) orelse return null_node; + _ = try p.expectToken(.l_paren); + const expr_node = try p.expectExpr(); + _ = try p.expectToken(.r_paren); + return expr_node; + } + /// ParamDecl /// <- (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType /// / DOT3 @@ -3011,6 +3057,7 @@ const Parser = struct { const PtrModifiers = struct { align_node: Node.Index, + addrspace_node: Node.Index, bit_range_start: Node.Index, bit_range_end: Node.Index, }; @@ -3018,12 +3065,14 @@ const Parser = struct { fn parsePtrModifiers(p: *Parser) !PtrModifiers { var result: PtrModifiers = .{ .align_node = 0, + .addrspace_node = 0, .bit_range_start = 0, .bit_range_end = 0, }; var saw_const = false; var saw_volatile = false; var saw_allowzero = false; + var saw_addrspace = false; while (true) { switch (p.token_tags[p.tok_i]) { .keyword_align => { @@ -3063,6 +3112,12 @@ const Parser = struct { p.tok_i += 1; saw_allowzero = true; }, + .keyword_addrspace => { + if (saw_addrspace) { + try p.warn(.extra_addrspace_qualifier); + } + result.addrspace_node = try p.parseAddrSpace(); + }, else => return result, } } diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 3fdbb3ec7b..c64d740b01 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -11,6 +11,7 @@ pub const Token = struct { }; pub const keywords = std.ComptimeStringMap(Tag, .{ + .{ "addrspace", .keyword_addrspace }, .{ "align", .keyword_align }, .{ "allowzero", .keyword_allowzero }, .{ "and", .keyword_and }, @@ -132,6 +133,7 @@ pub const Token = struct { float_literal, doc_comment, container_doc_comment, + keyword_addrspace, keyword_align, keyword_allowzero, keyword_and, @@ -251,6 +253,7 @@ pub const Token = struct { .angle_bracket_angle_bracket_right => ">>", .angle_bracket_angle_bracket_right_equal => ">>=", .tilde => "~", + .keyword_addrspace => "addrspace", .keyword_align => "align", .keyword_allowzero => "allowzero", .keyword_and => "and", diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 93acd464f4..bafaebfea0 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -2614,6 +2614,7 @@ fn renderVar(c: *Context, node: Node) !NodeIndex { .type_node = type_node, .align_node = align_node, .section_node = section_node, + .addrspace_node = 0, }), .rhs = init_node, }, From 7da9fa6fe2e982d10ebc9c3844d1249a4eb1d514 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Tue, 17 Aug 2021 01:38:22 +0200 Subject: [PATCH 038/160] Address spaces: AstGen Adds AST generation for address spaces on pointers, function prototypes, function declarations and variable declarations. In the latter two cases, declaration properties were already stored more efficiently in a declaration structure. To accomodate these for address spaces, the bit indicating presence of a linksection attribute has been extended to include either linksection, address space, or both. --- lib/std/builtin.zig | 6 ++++ src/AstGen.zig | 50 ++++++++++++++++++++++---- src/Sema.zig | 3 ++ src/Zir.zig | 77 +++++++++++++++++++++++++++++++---------- src/translate_c/ast.zig | 2 ++ src/type.zig | 19 ++++++++++ src/value.zig | 5 +++ 7 files changed, 137 insertions(+), 25 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 7f6c40c6f5..724b069a34 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -166,6 +166,12 @@ pub const CallingConvention = enum { SysV, }; +/// This data structure is used by the Zig language code generation and +/// therefore must be kept in sync with the compiler implementation. +pub const AddressSpace = enum { + generic, +}; + /// 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 { diff --git a/src/AstGen.zig b/src/AstGen.zig index be3613dfb9..bfb7a0b10f 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1116,6 +1116,11 @@ fn fnProtoExpr( const align_inst: Zir.Inst.Ref = if (fn_proto.ast.align_expr == 0) .none else inst: { break :inst try expr(gz, scope, align_rl, fn_proto.ast.align_expr); }; + + const addrspace_inst: Zir.Inst.Ref = if (fn_proto.ast.addrspace_expr == 0) .none else inst: { + break :inst try expr(gz, scope, .{ .ty = .address_space_type }, fn_proto.ast.addrspace_expr); + }; + if (fn_proto.ast.section_expr != 0) { return astgen.failNode(fn_proto.ast.section_expr, "linksection not allowed on function prototypes", .{}); } @@ -1148,6 +1153,7 @@ fn fnProtoExpr( .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = align_inst, + .addrspace_inst = addrspace_inst, .lib_name = 0, .is_var_args = is_var_args, .is_inferred_error = false, @@ -2714,6 +2720,7 @@ fn ptrType( const elem_type = try typeExpr(gz, scope, ptr_info.ast.child_type); const simple = ptr_info.ast.align_node == 0 and + ptr_info.ast.addrspace_node == 0 and ptr_info.ast.sentinel == 0 and ptr_info.ast.bit_range_start == 0; @@ -2732,6 +2739,7 @@ fn ptrType( var sentinel_ref: Zir.Inst.Ref = .none; var align_ref: Zir.Inst.Ref = .none; + var addrspace_ref: Zir.Inst.Ref = .none; var bit_start_ref: Zir.Inst.Ref = .none; var bit_end_ref: Zir.Inst.Ref = .none; var trailing_count: u32 = 0; @@ -2744,6 +2752,10 @@ fn ptrType( align_ref = try expr(gz, scope, align_rl, ptr_info.ast.align_node); trailing_count += 1; } + if (ptr_info.ast.addrspace_node != 0) { + addrspace_ref = try expr(gz, scope, .{ .ty = .address_space_type }, ptr_info.ast.addrspace_node); + trailing_count += 1; + } if (ptr_info.ast.bit_range_start != 0) { assert(ptr_info.ast.bit_range_end != 0); bit_start_ref = try expr(gz, scope, .none, ptr_info.ast.bit_range_start); @@ -2764,6 +2776,9 @@ fn ptrType( if (align_ref != .none) { gz.astgen.extra.appendAssumeCapacity(@enumToInt(align_ref)); } + if (addrspace_ref != .none) { + gz.astgen.extra.appendAssumeCapacity(@enumToInt(addrspace_ref)); + } if (bit_start_ref != .none) { gz.astgen.extra.appendAssumeCapacity(@enumToInt(bit_start_ref)); gz.astgen.extra.appendAssumeCapacity(@enumToInt(bit_end_ref)); @@ -2779,6 +2794,7 @@ fn ptrType( .is_volatile = ptr_info.volatile_token != null, .has_sentinel = sentinel_ref != .none, .has_align = align_ref != .none, + .has_addrspace = addrspace_ref != .none, .has_bit_range = bit_start_ref != .none, }, .size = ptr_info.size, @@ -2847,7 +2863,7 @@ const WipDecls = struct { is_pub: bool, is_export: bool, has_align: bool, - has_section: bool, + has_section_or_addrspace: bool, ) Allocator.Error!void { if (wip_decls.decl_index % fields_per_u32 == 0 and wip_decls.decl_index != 0) { try wip_decls.bit_bag.append(gpa, wip_decls.cur_bit_bag); @@ -2857,7 +2873,7 @@ const WipDecls = struct { (@as(u32, @boolToInt(is_pub)) << 28) | (@as(u32, @boolToInt(is_export)) << 29) | (@as(u32, @boolToInt(has_align)) << 30) | - (@as(u32, @boolToInt(has_section)) << 31); + (@as(u32, @boolToInt(has_section_or_addrspace)) << 31); wip_decls.decl_index += 1; } @@ -2922,7 +2938,8 @@ fn fnDecl( const maybe_inline_token = fn_proto.extern_export_inline_token orelse break :blk false; break :blk token_tags[maybe_inline_token] == .keyword_inline; }; - try wip_decls.next(gpa, is_pub, is_export, fn_proto.ast.align_expr != 0, fn_proto.ast.section_expr != 0); + const has_section_or_addrspace = fn_proto.ast.section_expr != 0 or fn_proto.ast.addrspace_expr != 0; + try wip_decls.next(gpa, is_pub, is_export, fn_proto.ast.align_expr != 0, has_section_or_addrspace); var params_scope = &fn_gz.base; const is_var_args = is_var_args: { @@ -3011,6 +3028,9 @@ fn fnDecl( const align_inst: Zir.Inst.Ref = if (fn_proto.ast.align_expr == 0) .none else inst: { break :inst try expr(&decl_gz, params_scope, align_rl, fn_proto.ast.align_expr); }; + const addrspace_inst: Zir.Inst.Ref = if (fn_proto.ast.addrspace_expr == 0) .none else inst: { + break :inst try expr(&decl_gz, params_scope, .{ .ty = .address_space_type }, fn_proto.ast.addrspace_expr); + }; const section_inst: Zir.Inst.Ref = if (fn_proto.ast.section_expr == 0) .none else inst: { break :inst try comptimeExpr(&decl_gz, params_scope, .{ .ty = .const_slice_u8_type }, fn_proto.ast.section_expr); }; @@ -3060,6 +3080,7 @@ fn fnDecl( .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = .none, // passed in the per-decl data + .addrspace_inst = .none, // passed in the per-decl data .lib_name = lib_name, .is_var_args = is_var_args, .is_inferred_error = false, @@ -3099,6 +3120,7 @@ fn fnDecl( .body = fn_gz.instructions.items, .cc = cc, .align_inst = .none, // passed in the per-decl data + .addrspace_inst = .none, // passed in the per-decl data .lib_name = lib_name, .is_var_args = is_var_args, .is_inferred_error = is_inferred_error, @@ -3127,8 +3149,10 @@ fn fnDecl( if (align_inst != .none) { wip_decls.payload.appendAssumeCapacity(@enumToInt(align_inst)); } - if (section_inst != .none) { + + if (has_section_or_addrspace) { wip_decls.payload.appendAssumeCapacity(@enumToInt(section_inst)); + wip_decls.payload.appendAssumeCapacity(@enumToInt(addrspace_inst)); } } @@ -3175,10 +3199,14 @@ fn globalVarDecl( const align_inst: Zir.Inst.Ref = if (var_decl.ast.align_node == 0) .none else inst: { break :inst try expr(&block_scope, &block_scope.base, align_rl, var_decl.ast.align_node); }; + const addrspace_inst: Zir.Inst.Ref = if (var_decl.ast.addrspace_node == 0) .none else inst: { + break :inst try expr(&block_scope, &block_scope.base, .{ .ty = .address_space_type }, var_decl.ast.addrspace_node); + }; const section_inst: Zir.Inst.Ref = if (var_decl.ast.section_node == 0) .none else inst: { break :inst try comptimeExpr(&block_scope, &block_scope.base, .{ .ty = .const_slice_u8_type }, var_decl.ast.section_node); }; - try wip_decls.next(gpa, is_pub, is_export, align_inst != .none, section_inst != .none); + const has_section_or_addrspace = section_inst != .none or addrspace_inst != .none; + try wip_decls.next(gpa, is_pub, is_export, align_inst != .none, has_section_or_addrspace); const is_threadlocal = if (var_decl.threadlocal_token) |tok| blk: { if (!is_mutable) { @@ -3271,8 +3299,9 @@ fn globalVarDecl( if (align_inst != .none) { wip_decls.payload.appendAssumeCapacity(@enumToInt(align_inst)); } - if (section_inst != .none) { + if (has_section_or_addrspace) { wip_decls.payload.appendAssumeCapacity(@enumToInt(section_inst)); + wip_decls.payload.appendAssumeCapacity(@enumToInt(addrspace_inst)); } } @@ -3443,6 +3472,7 @@ fn testDecl( .body = fn_block.instructions.items, .cc = .none, .align_inst = .none, + .addrspace_inst = .none, .lib_name = 0, .is_var_args = false, .is_inferred_error = true, @@ -9178,6 +9208,7 @@ const GenZir = struct { ret_br: Zir.Inst.Index, cc: Zir.Inst.Ref, align_inst: Zir.Inst.Ref, + addrspace_inst: Zir.Inst.Ref, lib_name: u32, is_var_args: bool, is_inferred_error: bool, @@ -9221,7 +9252,7 @@ const GenZir = struct { if (args.cc != .none or args.lib_name != 0 or args.is_var_args or args.is_test or args.align_inst != .none or - args.is_extern) + args.addrspace_inst != .none or args.is_extern) { try astgen.extra.ensureUnusedCapacity( gpa, @@ -9229,6 +9260,7 @@ const GenZir = struct { args.ret_ty.len + args.body.len + src_locs.len + @boolToInt(args.lib_name != 0) + @boolToInt(args.align_inst != .none) + + @boolToInt(args.addrspace_inst != .none) + @boolToInt(args.cc != .none), ); const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{ @@ -9246,6 +9278,9 @@ const GenZir = struct { if (args.align_inst != .none) { astgen.extra.appendAssumeCapacity(@enumToInt(args.align_inst)); } + if (args.addrspace_inst != .none) { + astgen.extra.appendAssumeCapacity(@enumToInt(args.addrspace_inst)); + } astgen.extra.appendSliceAssumeCapacity(args.ret_ty); astgen.extra.appendSliceAssumeCapacity(args.body); astgen.extra.appendSliceAssumeCapacity(src_locs); @@ -9264,6 +9299,7 @@ const GenZir = struct { .has_lib_name = args.lib_name != 0, .has_cc = args.cc != .none, .has_align = args.align_inst != .none, + .has_addrspace = args.addrspace_inst != .none, .is_test = args.is_test, .is_extern = args.is_extern, }), diff --git a/src/Sema.zig b/src/Sema.zig index 5471795317..8200e95aa5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10200,6 +10200,7 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type .atomic_order => return sema.resolveBuiltinTypeFields(block, src, "AtomicOrder"), .atomic_rmw_op => return sema.resolveBuiltinTypeFields(block, src, "AtomicRmwOp"), .calling_convention => return sema.resolveBuiltinTypeFields(block, src, "CallingConvention"), + .address_space => return sema.resolveBuiltinTypeFields(block, src, "AddressSpace"), .float_mode => return sema.resolveBuiltinTypeFields(block, src, "FloatMode"), .reduce_op => return sema.resolveBuiltinTypeFields(block, src, "ReduceOp"), .call_options => return sema.resolveBuiltinTypeFields(block, src, "CallOptions"), @@ -10594,6 +10595,7 @@ fn typeHasOnePossibleValue( .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -10779,6 +10781,7 @@ pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref { .atomic_order => return .atomic_order_type, .atomic_rmw_op => return .atomic_rmw_op_type, .calling_convention => return .calling_convention_type, + .address_space => return .address_space_type, .float_mode => return .float_mode_type, .reduce_op => return .reduce_op_type, .call_options => return .call_options_type, diff --git a/src/Zir.zig b/src/Zir.zig index f8dbef2534..0e50f8c256 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -488,10 +488,10 @@ pub const Inst = struct { /// this instruction; a following 'ret' instruction will do the diversion. /// Uses the `str_tok` union field. ret_err_value_code, - /// Create a pointer type that does not have a sentinel, alignment, or bit range specified. + /// Create a pointer type that does not have a sentinel, alignment, address space, or bit range specified. /// Uses the `ptr_type_simple` union field. ptr_type_simple, - /// Create a pointer type which can have a sentinel, alignment, and/or bit range. + /// Create a pointer type which can have a sentinel, alignment, address space, and/or bit range. /// Uses the `ptr_type` union field. ptr_type, /// Slice operation `lhs[rhs..]`. No sentinel and no end offset. @@ -1717,6 +1717,7 @@ pub const Inst = struct { atomic_order_type, atomic_rmw_op_type, calling_convention_type, + address_space_type, float_mode_type, reduce_op_type, call_options_type, @@ -1973,6 +1974,10 @@ pub const Inst = struct { .ty = Type.initTag(.type), .val = Value.initTag(.calling_convention_type), }, + .address_space_type = .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.address_space_type), + }, .float_mode_type = .{ .ty = Type.initTag(.type), .val = Value.initTag(.float_mode_type), @@ -2174,8 +2179,9 @@ pub const Inst = struct { is_volatile: bool, has_sentinel: bool, has_align: bool, + has_addrspace: bool, has_bit_range: bool, - _: u2 = undefined, + _: u1 = undefined, }, size: std.builtin.TypeInfo.Pointer.Size, /// Index into extra. See `PtrType`. @@ -2303,6 +2309,7 @@ pub const Inst = struct { /// 0. lib_name: u32, // null terminated string index, if has_lib_name is set /// 1. cc: Ref, // if has_cc is set /// 2. align: Ref, // if has_align is set + /// 3. addrspace: Ref, // if has_addrspace is set /// 3. return_type: Index // for each ret_body_len /// 4. body: Index // for each body_len /// 5. src_locs: Func.SrcLocs // if body_len != 0 @@ -2320,9 +2327,10 @@ pub const Inst = struct { has_lib_name: bool, has_cc: bool, has_align: bool, + has_addrspace: bool, is_test: bool, is_extern: bool, - _: u9 = undefined, + _: u8 = undefined, }; }; @@ -2405,12 +2413,13 @@ pub const Inst = struct { else_body_len: u32, }; - /// Stored in extra. Depending on the flags in Data, there will be up to 4 + /// Stored in extra. Depending on the flags in Data, there will be up to 5 /// trailing Ref fields: /// 0. sentinel: Ref // if `has_sentinel` flag is set /// 1. align: Ref // if `has_align` flag is set - /// 2. bit_start: Ref // if `has_bit_range` flag is set - /// 3. bit_end: Ref // if `has_bit_range` flag is set + /// 2. address_space: Ref // if `has_addrspace` flag is set + /// 3. bit_start: Ref // if `has_bit_range` flag is set + /// 4. bit_end: Ref // if `has_bit_range` flag is set pub const PtrType = struct { elem_type: Ref, }; @@ -2528,7 +2537,7 @@ pub const Inst = struct { /// 0b000X: whether corresponding decl is pub /// 0b00X0: whether corresponding decl is exported /// 0b0X00: whether corresponding decl has an align expression - /// 0bX000: whether corresponding decl has a linksection expression + /// 0bX000: whether corresponding decl has a linksection or an address space expression /// 5. decl: { // for every decls_len /// src_hash: [4]u32, // hash of source bytes /// line: u32, // line number of decl, relative to parent @@ -2540,7 +2549,10 @@ pub const Inst = struct { /// this is a test decl, and the name starts at `name+1`. /// value: Index, /// align: Ref, // if corresponding bit is set - /// link_section: Ref, // if corresponding bit is set + /// link_section_or_address_space: { // if corresponding bit is set. + /// link_section: Ref, + /// address_space: Ref, + /// } /// } /// 6. inst: Index // for every body_len /// 7. flags: u32 // for every 8 fields @@ -2592,7 +2604,7 @@ pub const Inst = struct { /// 0b000X: whether corresponding decl is pub /// 0b00X0: whether corresponding decl is exported /// 0b0X00: whether corresponding decl has an align expression - /// 0bX000: whether corresponding decl has a linksection expression + /// 0bX000: whether corresponding decl has a linksection or an address space expression /// 6. decl: { // for every decls_len /// src_hash: [4]u32, // hash of source bytes /// line: u32, // line number of decl, relative to parent @@ -2604,7 +2616,10 @@ pub const Inst = struct { /// this is a test decl, and the name starts at `name+1`. /// value: Index, /// align: Ref, // if corresponding bit is set - /// link_section: Ref, // if corresponding bit is set + /// link_section_or_address_space: { // if corresponding bit is set. + /// link_section: Ref, + /// address_space: Ref, + /// } /// } /// 7. inst: Index // for every body_len /// 8. has_bits: u32 // for every 32 fields @@ -2637,7 +2652,7 @@ pub const Inst = struct { /// 0b000X: whether corresponding decl is pub /// 0b00X0: whether corresponding decl is exported /// 0b0X00: whether corresponding decl has an align expression - /// 0bX000: whether corresponding decl has a linksection expression + /// 0bX000: whether corresponding decl has a linksection or an address space expression /// 6. decl: { // for every decls_len /// src_hash: [4]u32, // hash of source bytes /// line: u32, // line number of decl, relative to parent @@ -2649,7 +2664,10 @@ pub const Inst = struct { /// this is a test decl, and the name starts at `name+1`. /// value: Index, /// align: Ref, // if corresponding bit is set - /// link_section: Ref, // if corresponding bit is set + /// link_section_or_address_space: { // if corresponding bit is set. + /// link_section: Ref, + /// address_space: Ref, + /// } /// } /// 7. inst: Index // for every body_len /// 8. has_bits: u32 // for every 8 fields @@ -2686,7 +2704,7 @@ pub const Inst = struct { /// 0b000X: whether corresponding decl is pub /// 0b00X0: whether corresponding decl is exported /// 0b0X00: whether corresponding decl has an align expression - /// 0bX000: whether corresponding decl has a linksection expression + /// 0bX000: whether corresponding decl has a linksection or an address space expression /// 1. decl: { // for every decls_len /// src_hash: [4]u32, // hash of source bytes /// line: u32, // line number of decl, relative to parent @@ -2698,7 +2716,10 @@ pub const Inst = struct { /// this is a test decl, and the name starts at `name+1`. /// value: Index, /// align: Ref, // if corresponding bit is set - /// link_section: Ref, // if corresponding bit is set + /// link_section_or_address_space: { // if corresponding bit is set. + /// link_section: Ref, + /// address_space: Ref, + /// } /// } pub const OpaqueDecl = struct { decls_len: u32, @@ -3983,7 +4004,7 @@ const Writer = struct { cur_bit_bag >>= 1; const has_align = @truncate(u1, cur_bit_bag) != 0; cur_bit_bag >>= 1; - const has_section = @truncate(u1, cur_bit_bag) != 0; + const has_section_or_addrspace = @truncate(u1, cur_bit_bag) != 0; cur_bit_bag >>= 1; const sub_index = extra_index; @@ -4001,12 +4022,16 @@ const Writer = struct { extra_index += 1; break :inst inst; }; - const section_inst: Inst.Ref = if (!has_section) .none else inst: { + const section_inst: Inst.Ref = if (!has_section_or_addrspace) .none else inst: { const inst = @intToEnum(Inst.Ref, self.code.extra[extra_index]); extra_index += 1; break :inst inst; }; - + const addrspace_inst: Inst.Ref = if (!has_section_or_addrspace) .none else inst: { + const inst = @intToEnum(Inst.Ref, self.code.extra[extra_index]); + extra_index +=1; + break :inst inst; + }; const pub_str = if (is_pub) "pub " else ""; const hash_bytes = @bitCast([16]u8, hash_u32s.*); try stream.writeByteNTimes(' ', self.indent); @@ -4032,6 +4057,11 @@ const Writer = struct { try self.writeInstRef(stream, align_inst); try stream.writeAll(")"); } + if (addrspace_inst != .none) { + try stream.writeAll(" addrspace("); + try self.writeInstRef(stream, addrspace_inst); + try stream.writeAll(")"); + } if (section_inst != .none) { try stream.writeAll(" linksection("); try self.writeInstRef(stream, section_inst); @@ -4453,6 +4483,7 @@ const Writer = struct { false, .none, .none, + .none, body, src, src_locs, @@ -4481,6 +4512,11 @@ const Writer = struct { extra_index += 1; break :blk align_inst; }; + const addrspace_inst: Inst.Ref = if (!small.has_addrspace) .none else blk: { + const addrspace_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :blk addrspace_inst; + }; const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; extra_index += ret_ty_body.len; @@ -4500,6 +4536,7 @@ const Writer = struct { small.is_extern, cc, align_inst, + addrspace_inst, body, src, src_locs, @@ -4582,6 +4619,7 @@ const Writer = struct { is_extern: bool, cc: Inst.Ref, align_inst: Inst.Ref, + addrspace_inst: Inst.Ref, body: []const Inst.Index, src: LazySrcLoc, src_locs: Zir.Inst.Func.SrcLocs, @@ -4599,6 +4637,7 @@ const Writer = struct { try self.writeOptionalInstRef(stream, ", cc=", cc); try self.writeOptionalInstRef(stream, ", align=", align_inst); + try self.writeOptionalInstRef(stream, ", addrspace=", addrspace_inst); try self.writeFlag(stream, ", vargs", var_args); try self.writeFlag(stream, ", extern", is_extern); try self.writeFlag(stream, ", inferror", inferred_error_set); @@ -4876,6 +4915,7 @@ fn findDeclsInner( extra_index += @boolToInt(small.has_lib_name); extra_index += @boolToInt(small.has_cc); extra_index += @boolToInt(small.has_align); + extra_index += @boolToInt(small.has_addrspace); const body = zir.extra[extra_index..][0..extra.data.body_len]; return zir.findDeclsBody(list, body); }, @@ -5079,6 +5119,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { extra_index += @boolToInt(small.has_lib_name); extra_index += @boolToInt(small.has_cc); extra_index += @boolToInt(small.has_align); + extra_index += @boolToInt(small.has_addrspace); const ret_ty_body = zir.extra[extra_index..][0..extra.data.ret_body_len]; extra_index += ret_ty_body.len; const body = zir.extra[extra_index..][0..extra.data.body_len]; diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index bafaebfea0..d0fe6d1b31 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -2706,6 +2706,7 @@ fn renderFunc(c: *Context, node: Node) !NodeIndex { .lhs = try c.addExtra(std.zig.Ast.Node.FnProtoOne{ .param = params.items[0], .align_expr = align_expr, + .addrspace_expr = 0, // TODO .section_expr = section_expr, .callconv_expr = callconv_expr, }), @@ -2721,6 +2722,7 @@ fn renderFunc(c: *Context, node: Node) !NodeIndex { .params_start = span.start, .params_end = span.end, .align_expr = align_expr, + .addrspace_expr = 0, // TODO .section_expr = section_expr, .callconv_expr = callconv_expr, }), diff --git a/src/type.zig b/src/type.zig index ec73ae1196..8eca352eac 100644 --- a/src/type.zig +++ b/src/type.zig @@ -127,6 +127,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, => return .Enum, @@ -746,6 +747,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -958,6 +960,7 @@ pub const Type = extern union { .atomic_order => return writer.writeAll("std.builtin.AtomicOrder"), .atomic_rmw_op => return writer.writeAll("std.builtin.AtomicRmwOp"), .calling_convention => return writer.writeAll("std.builtin.CallingConvention"), + .address_space => return writer.writeAll("std.builtin.AddressSpace"), .float_mode => return writer.writeAll("std.builtin.FloatMode"), .reduce_op => return writer.writeAll("std.builtin.ReduceOp"), .call_options => return writer.writeAll("std.builtin.CallOptions"), @@ -1186,6 +1189,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -1301,6 +1305,7 @@ pub const Type = extern union { .atomic_order => return Value.initTag(.atomic_order_type), .atomic_rmw_op => return Value.initTag(.atomic_rmw_op_type), .calling_convention => return Value.initTag(.calling_convention_type), + .address_space => return Value.initTag(.address_space_type), .float_mode => return Value.initTag(.float_mode_type), .reduce_op => return Value.initTag(.reduce_op_type), .call_options => return Value.initTag(.call_options_type), @@ -1362,6 +1367,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -1508,6 +1514,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -1734,6 +1741,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -2018,6 +2026,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -2775,6 +2784,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -2982,6 +2992,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -3006,6 +3017,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -3029,6 +3041,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -3082,6 +3095,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -3137,6 +3151,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -3174,6 +3189,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -3224,6 +3240,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, @@ -3284,6 +3301,7 @@ pub const Type = extern union { atomic_order, atomic_rmw_op, calling_convention, + address_space, float_mode, reduce_op, call_options, @@ -3407,6 +3425,7 @@ pub const Type = extern union { .atomic_order, .atomic_rmw_op, .calling_convention, + .address_space, .float_mode, .reduce_op, .call_options, diff --git a/src/value.zig b/src/value.zig index 88d0d04086..9fa42d3008 100644 --- a/src/value.zig +++ b/src/value.zig @@ -63,6 +63,7 @@ pub const Value = extern union { atomic_order_type, atomic_rmw_op_type, calling_convention_type, + address_space_type, float_mode_type, reduce_op_type, call_options_type, @@ -226,6 +227,7 @@ pub const Value = extern union { .atomic_order_type, .atomic_rmw_op_type, .calling_convention_type, + .address_space_type, .float_mode_type, .reduce_op_type, .call_options_type, @@ -412,6 +414,7 @@ pub const Value = extern union { .atomic_order_type, .atomic_rmw_op_type, .calling_convention_type, + .address_space_type, .float_mode_type, .reduce_op_type, .call_options_type, @@ -625,6 +628,7 @@ pub const Value = extern union { .atomic_order_type => return out_stream.writeAll("std.builtin.AtomicOrder"), .atomic_rmw_op_type => return out_stream.writeAll("std.builtin.AtomicRmwOp"), .calling_convention_type => return out_stream.writeAll("std.builtin.CallingConvention"), + .address_space_type => return out_stream.writeAll("std.builtin.AddressSpace"), .float_mode_type => return out_stream.writeAll("std.builtin.FloatMode"), .reduce_op_type => return out_stream.writeAll("std.builtin.ReduceOp"), .call_options_type => return out_stream.writeAll("std.builtin.CallOptions"), @@ -792,6 +796,7 @@ pub const Value = extern union { .atomic_order_type => Type.initTag(.atomic_order), .atomic_rmw_op_type => Type.initTag(.atomic_rmw_op), .calling_convention_type => Type.initTag(.calling_convention), + .address_space_type => Type.initTag(.address_space), .float_mode_type => Type.initTag(.float_mode), .reduce_op_type => Type.initTag(.reduce_op), .call_options_type => Type.initTag(.call_options), From 805e1bffbdcab84717356fb1a7b375369407d9c2 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 2 Sep 2021 14:46:31 +0200 Subject: [PATCH 039/160] Address Spaces: Sema basics --- lib/std/builtin.zig | 1 + src/AstGen.zig | 4 ++-- src/Module.zig | 53 +++++++++++++++++++++++++++++++-------------- src/Sema.zig | 28 ++++++++++++++++++++++-- src/type.zig | 42 +++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 20 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 724b069a34..b9176794c6 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -170,6 +170,7 @@ pub const CallingConvention = enum { /// therefore must be kept in sync with the compiler implementation. pub const AddressSpace = enum { generic, + special, }; /// This data structure is used by the Zig language code generation and diff --git a/src/AstGen.zig b/src/AstGen.zig index bfb7a0b10f..443834485f 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -3134,7 +3134,7 @@ fn fnDecl( _ = try decl_gz.addBreak(.break_inline, block_inst, func_inst); try decl_gz.setBlockBody(block_inst); - try wip_decls.payload.ensureUnusedCapacity(gpa, 9); + try wip_decls.payload.ensureUnusedCapacity(gpa, 10); { const contents_hash = std.zig.hashSrc(tree.getNodeSource(decl_node)); const casted = @bitCast([4]u32, contents_hash); @@ -3284,7 +3284,7 @@ fn globalVarDecl( _ = try block_scope.addBreak(.break_inline, block_inst, var_inst); try block_scope.setBlockBody(block_inst); - try wip_decls.payload.ensureUnusedCapacity(gpa, 9); + try wip_decls.payload.ensureUnusedCapacity(gpa, 10); { const contents_hash = std.zig.hashSrc(tree.getNodeSource(node)); const casted = @bitCast([4]u32, contents_hash); diff --git a/src/Module.zig b/src/Module.zig index add0562d93..957987b895 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -288,6 +288,8 @@ pub const Decl = struct { align_val: Value, /// Populated when `has_tv`. linksection_val: Value, + /// Populated when `has_tv`. + @"addrspace": std.builtin.AddressSpace, /// The memory for ty, val, align_val, linksection_val. /// If this is `null` then there is no memory management needed. value_arena: ?*std.heap.ArenaAllocator.State = null, @@ -351,7 +353,7 @@ pub const Decl = struct { /// to require re-analysis. outdated, }, - /// Whether `typed_value`, `align_val`, and `linksection_val` are populated. + /// Whether `typed_value`, `align_val`, `linksection_val` and `has_addrspace` are populated. has_tv: bool, /// If `true` it means the `Decl` is the resource owner of the type/value associated /// with it. That means when `Decl` is destroyed, the cleanup code should additionally @@ -366,8 +368,8 @@ pub const Decl = struct { is_exported: bool, /// Whether the ZIR code provides an align instruction. has_align: bool, - /// Whether the ZIR code provides a linksection instruction. - has_linksection: bool, + /// Whether the ZIR code provides a linksection and address space instruction. + has_linksection_or_addrspace: bool, /// Flag used by garbage collection to mark and sweep. /// Decls which correspond to an AST node always have this field set to `true`. /// Anonymous Decls are initialized with this field set to `false` and then it @@ -489,14 +491,22 @@ pub const Decl = struct { if (!decl.has_align) return .none; assert(decl.zir_decl_index != 0); const zir = decl.namespace.file_scope.zir; - return @intToEnum(Zir.Inst.Ref, zir.extra[decl.zir_decl_index + 6]); + return @intToEnum(Zir.Inst.Ref, zir.extra[decl.zir_decl_index + 7]); } pub fn zirLinksectionRef(decl: Decl) Zir.Inst.Ref { - if (!decl.has_linksection) return .none; + if (!decl.has_linksection_or_addrspace) return .none; assert(decl.zir_decl_index != 0); const zir = decl.namespace.file_scope.zir; - const extra_index = decl.zir_decl_index + 6 + @boolToInt(decl.has_align); + const extra_index = decl.zir_decl_index + 7 + @boolToInt(decl.has_align); + return @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); + } + + pub fn zirAddrspaceRef(decl: Decl) Zir.Inst.Ref { + if (!decl.has_linksection_or_addrspace) return .none; + assert(decl.zir_decl_index != 0); + const zir = decl.namespace.file_scope.zir; + const extra_index = decl.zir_decl_index + 7 + @boolToInt(decl.has_align) + 1; return @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); } @@ -3072,7 +3082,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { new_decl.is_pub = true; new_decl.is_exported = false; new_decl.has_align = false; - new_decl.has_linksection = false; + new_decl.has_linksection_or_addrspace = false; new_decl.ty = struct_ty; new_decl.val = struct_val; new_decl.has_tv = true; @@ -3202,6 +3212,12 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { if (linksection_ref == .none) break :blk Value.initTag(.null_value); break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val; }; + const address_space = blk: { + const addrspace_ref = decl.zirAddrspaceRef(); + if (addrspace_ref == .none) break :blk .generic; + const addrspace_tv = try sema.resolveInstConst(&block_scope, src, addrspace_ref); + break :blk addrspace_tv.val.toEnum(std.builtin.AddressSpace); + }; // Note this resolves the type of the Decl, not the value; if this Decl // is a struct, for example, this resolves `type` (which needs no resolution), // not the struct itself. @@ -3258,6 +3274,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { decl.val = try decl_tv.val.copy(&decl_arena.allocator); decl.align_val = try align_val.copy(&decl_arena.allocator); decl.linksection_val = try linksection_val.copy(&decl_arena.allocator); + decl.@"addrspace" = address_space; decl.has_tv = true; decl.owns_tv = owns_tv; decl_arena_state.* = decl_arena.state; @@ -3319,6 +3336,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { decl.val = try decl_tv.val.copy(&decl_arena.allocator); decl.align_val = try align_val.copy(&decl_arena.allocator); decl.linksection_val = try linksection_val.copy(&decl_arena.allocator); + decl.@"addrspace" = address_space; decl.has_tv = true; decl_arena_state.* = decl_arena.state; decl.value_arena = decl_arena_state; @@ -3526,8 +3544,8 @@ pub fn scanNamespace( const decl_sub_index = extra_index; extra_index += 7; // src_hash(4) + line(1) + name(1) + value(1) - extra_index += @truncate(u1, flags >> 2); - extra_index += @truncate(u1, flags >> 3); + extra_index += @truncate(u1, flags >> 2); // Align + extra_index += @as(u2, @truncate(u1, flags >> 3)) * 2; // Link section or address space, consists of 2 Refs try scanDecl(&scan_decl_iter, decl_sub_index, flags); } @@ -3553,10 +3571,10 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi const zir = namespace.file_scope.zir; // zig fmt: off - const is_pub = (flags & 0b0001) != 0; - const export_bit = (flags & 0b0010) != 0; - const has_align = (flags & 0b0100) != 0; - const has_linksection = (flags & 0b1000) != 0; + const is_pub = (flags & 0b0001) != 0; + const export_bit = (flags & 0b0010) != 0; + const has_align = (flags & 0b0100) != 0; + const has_linksection_or_addrspace = (flags & 0b1000) != 0; // zig fmt: on const line = iter.parent_decl.relativeToLine(zir.extra[decl_sub_index + 4]); @@ -3639,7 +3657,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi new_decl.is_exported = is_exported; new_decl.is_usingnamespace = is_usingnamespace; new_decl.has_align = has_align; - new_decl.has_linksection = has_linksection; + new_decl.has_linksection_or_addrspace = has_linksection_or_addrspace; new_decl.zir_decl_index = @intCast(u32, decl_sub_index); new_decl.alive = true; // This Decl corresponds to an AST node and therefore always alive. return; @@ -3656,7 +3674,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi decl.is_exported = is_exported; decl.is_usingnamespace = is_usingnamespace; decl.has_align = has_align; - decl.has_linksection = has_linksection; + decl.has_linksection_or_addrspace = has_linksection_or_addrspace; decl.zir_decl_index = @intCast(u32, decl_sub_index); if (decl.getFunction()) |_| { switch (mod.comp.bin_file.tag) { @@ -4028,6 +4046,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast. .val = undefined, .align_val = undefined, .linksection_val = undefined, + .@"addrspace" = undefined, .analysis = .unreferenced, .deletion_flag = false, .zir_decl_index = 0, @@ -4052,7 +4071,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast. .generation = 0, .is_pub = false, .is_exported = false, - .has_linksection = false, + .has_linksection_or_addrspace = false, .has_align = false, .alive = false, .is_usingnamespace = false, @@ -4357,6 +4376,7 @@ pub fn ptrType( elem_ty: Type, sentinel: ?Value, @"align": u32, + @"addrspace": std.builtin.AddressSpace, bit_offset: u16, host_size: u16, mutable: bool, @@ -4371,6 +4391,7 @@ pub fn ptrType( .pointee_type = elem_ty, .sentinel = sentinel, .@"align" = @"align", + .@"addrspace" = @"addrspace", .bit_offset = bit_offset, .host_size = host_size, .@"allowzero" = @"allowzero", diff --git a/src/Sema.zig b/src/Sema.zig index 8200e95aa5..b1145bab99 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3004,7 +3004,7 @@ fn analyzeCall( new_decl.is_pub = module_fn.owner_decl.is_pub; new_decl.is_exported = module_fn.owner_decl.is_exported; new_decl.has_align = module_fn.owner_decl.has_align; - new_decl.has_linksection = module_fn.owner_decl.has_linksection; + new_decl.has_linksection_or_addrspace = module_fn.owner_decl.has_linksection_or_addrspace; new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; new_decl.alive = true; // This Decl is called at runtime. new_decl.has_tv = true; @@ -3895,6 +3895,7 @@ fn zirFunc( ret_ty_body, cc, Value.initTag(.null_value), + .generic, false, inferred_error_set, false, @@ -3911,6 +3912,7 @@ fn funcCommon( ret_ty_body: []const Zir.Inst.Index, cc: std.builtin.CallingConvention, align_val: Value, + address_space: std.builtin.AddressSpace, var_args: bool, inferred_error_set: bool, is_extern: bool, @@ -3968,7 +3970,7 @@ fn funcCommon( // Hot path for some common function types. // TODO can we eliminate some of these Type tag values? seems unnecessarily complicated. if (!is_generic and block.params.items.len == 0 and !var_args and - align_val.tag() == .null_value and !inferred_error_set) + align_val.tag() == .null_value and !inferred_error_set and address_space == .generic) { if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) { break :fn_ty Type.initTag(.fn_noreturn_no_args); @@ -4020,6 +4022,7 @@ fn funcCommon( .comptime_params = comptime_params.ptr, .return_type = return_type, .cc = cc, + .@"addrspace" = address_space, .is_var_args = var_args, .is_generic = is_generic, }); @@ -6876,6 +6879,7 @@ fn zirPtrTypeSimple(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp elem_type, null, 0, + .generic, 0, 0, inst_data.is_mutable, @@ -6908,6 +6912,13 @@ fn zirPtrType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr break :blk try sema.resolveAlreadyCoercedInt(block, .unneeded, ref, u32); } else 0; + const address_space = if (inst_data.flags.has_addrspace) blk: { + const ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_i]); + extra_i += 1; + const addrspace_tv = try sema.resolveInstConst(block, .unneeded, ref); + break :blk addrspace_tv.val.toEnum(std.builtin.AddressSpace); + } else .generic; + const bit_start = if (inst_data.flags.has_bit_range) blk: { const ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_i]); extra_i += 1; @@ -6930,6 +6941,7 @@ fn zirPtrType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr elem_type, sentinel, abi_align, + address_space, bit_start, bit_end, inst_data.flags.is_mutable, @@ -8035,6 +8047,7 @@ fn zirFuncExtended( const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = extra.data.src_node }; const align_src: LazySrcLoc = src; // TODO add a LazySrcLoc that points at align + const addrspace_src: LazySrcLoc = src; // TODO(Snektron) add a LazySrcLoc that points at addrspace const small = @bitCast(Zir.Inst.ExtendedFunc.Small, extended.small); var extra_index: usize = extra.end; @@ -8059,6 +8072,13 @@ fn zirFuncExtended( break :blk align_tv.val; } else Value.initTag(.null_value); + const address_space: std.builtin.AddressSpace = if (small.has_addrspace) blk: { + const addrspace_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const addrspace_tv = try sema.resolveInstConst(block, addrspace_src, addrspace_ref); + break :blk addrspace_tv.val.toEnum(std.builtin.AddressSpace); + } else .generic; + const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; extra_index += ret_ty_body.len; @@ -8081,6 +8101,7 @@ fn zirFuncExtended( ret_ty_body, cc, align_val, + address_space, is_var_args, is_inferred_error, is_extern, @@ -9693,6 +9714,9 @@ fn analyzeSlice( return_elem_type, if (end_opt == .none) slice_sentinel else null, 0, // TODO alignment + // TODO(Snektron) address space, should be inferred from the pointer type. + // TODO(Snektron) address space for slicing a local, should compute address space from context and architecture. + .generic, 0, 0, !ptr_child.isConstPtr(), diff --git a/src/type.zig b/src/type.zig index 8eca352eac..647d88c60f 100644 --- a/src/type.zig +++ b/src/type.zig @@ -289,6 +289,7 @@ pub const Type = extern union { .pointee_type = Type.initTag(.comptime_int), .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -300,6 +301,7 @@ pub const Type = extern union { .pointee_type = Type.initTag(.u8), .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -311,6 +313,7 @@ pub const Type = extern union { .pointee_type = self.castPointer().?.data, .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -322,6 +325,7 @@ pub const Type = extern union { .pointee_type = self.castPointer().?.data, .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -333,6 +337,7 @@ pub const Type = extern union { .pointee_type = self.castPointer().?.data, .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -344,6 +349,7 @@ pub const Type = extern union { .pointee_type = Type.initTag(.u8), .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -355,6 +361,7 @@ pub const Type = extern union { .pointee_type = self.castPointer().?.data, .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -366,6 +373,7 @@ pub const Type = extern union { .pointee_type = Type.initTag(.u8), .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -377,6 +385,7 @@ pub const Type = extern union { .pointee_type = self.castPointer().?.data, .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -388,6 +397,7 @@ pub const Type = extern union { .pointee_type = self.castPointer().?.data, .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -399,6 +409,7 @@ pub const Type = extern union { .pointee_type = self.castPointer().?.data, .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -410,6 +421,7 @@ pub const Type = extern union { .pointee_type = self.castPointer().?.data, .sentinel = null, .@"align" = 0, + .@"addrspace" = .generic, .bit_offset = 0, .host_size = 0, .@"allowzero" = false, @@ -462,6 +474,8 @@ pub const Type = extern union { return false; if (info_a.host_size != info_b.host_size) return false; + if (info_a.@"addrspace" != info_b.@"addrspace") + return false; const sentinel_a = info_a.sentinel; const sentinel_b = info_b.sentinel; @@ -516,6 +530,8 @@ pub const Type = extern union { return false; if (a.fnCallingConvention() != b.fnCallingConvention()) return false; + if (a.fnAddressSpace() != b.fnAddressSpace()) + return false; const a_param_len = a.fnParamLen(); const b_param_len = b.fnParamLen(); if (a_param_len != b_param_len) @@ -822,6 +838,7 @@ pub const Type = extern union { .return_type = try payload.return_type.copy(allocator), .param_types = param_types, .cc = payload.cc, + .@"addrspace" = payload.@"addrspace", .is_var_args = payload.is_var_args, .is_generic = payload.is_generic, .comptime_params = comptime_params.ptr, @@ -837,6 +854,7 @@ pub const Type = extern union { .pointee_type = try payload.pointee_type.copy(allocator), .sentinel = sent, .@"align" = payload.@"align", + .@"addrspace" = payload.@"addrspace", .bit_offset = payload.bit_offset, .host_size = payload.host_size, .@"allowzero" = payload.@"allowzero", @@ -983,6 +1001,9 @@ pub const Type = extern union { try writer.writeAll(") callconv(."); try writer.writeAll(@tagName(payload.cc)); try writer.writeAll(") "); + if (payload.@"addrspace" != .generic) { + try writer.print("addrspace(.{s}) ", .{ @tagName(payload.@"addrspace") }); + } ty = payload.return_type; continue; }, @@ -1114,6 +1135,9 @@ pub const Type = extern union { } try writer.writeAll(") "); } + if (payload.@"addrspace" != .generic) { + try writer.print("addrspace(.{s}) ", .{ @tagName(payload.@"addrspace") }); + } if (!payload.mutable) try writer.writeAll("const "); if (payload.@"volatile") try writer.writeAll("volatile "); if (payload.@"allowzero") try writer.writeAll("allowzero "); @@ -2642,6 +2666,18 @@ pub const Type = extern union { }; } + pub fn fnAddressSpace(self: Type) std.builtin.AddressSpace { + return switch (self.tag()) { + .fn_noreturn_no_args => .generic, + .fn_void_no_args => .generic, + .fn_naked_noreturn_no_args => .generic, + .fn_ccc_void_no_args => .generic, + .function => self.castTag(.function).?.data.@"addrspace", + + else => unreachable, + }; + } + pub fn fnInfo(ty: Type) Payload.Function.Data { return switch (ty.tag()) { .fn_noreturn_no_args => .{ @@ -2649,6 +2685,7 @@ pub const Type = extern union { .comptime_params = undefined, .return_type = initTag(.noreturn), .cc = .Unspecified, + .@"addrspace" = .generic, .is_var_args = false, .is_generic = false, }, @@ -2657,6 +2694,7 @@ pub const Type = extern union { .comptime_params = undefined, .return_type = initTag(.void), .cc = .Unspecified, + .@"addrspace" = .generic, .is_var_args = false, .is_generic = false, }, @@ -2665,6 +2703,7 @@ pub const Type = extern union { .comptime_params = undefined, .return_type = initTag(.noreturn), .cc = .Naked, + .@"addrspace" = .generic, .is_var_args = false, .is_generic = false, }, @@ -2673,6 +2712,7 @@ pub const Type = extern union { .comptime_params = undefined, .return_type = initTag(.void), .cc = .C, + .@"addrspace" = .generic, .is_var_args = false, .is_generic = false, }, @@ -3544,6 +3584,7 @@ pub const Type = extern union { comptime_params: [*]bool, return_type: Type, cc: std.builtin.CallingConvention, + @"addrspace": std.builtin.AddressSpace, is_var_args: bool, is_generic: bool, @@ -3581,6 +3622,7 @@ pub const Type = extern union { sentinel: ?Value, /// If zero use pointee_type.AbiAlign() @"align": u32, + @"addrspace": std.builtin.AddressSpace, bit_offset: u16, host_size: u16, @"allowzero": bool, From cfbe9a6f61c98cb1b18e18aa6a4acf7785590953 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 2 Sep 2021 14:49:24 +0200 Subject: [PATCH 040/160] Address spaces: Forbid addrspace and linksection for local variables --- src/AstGen.zig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/AstGen.zig b/src/AstGen.zig index 443834485f..cffc626a1e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2377,6 +2377,7 @@ fn varDecl( const gpa = astgen.gpa; const tree = astgen.tree; const token_tags = tree.tokens.items(.tag); + const main_tokens = tree.nodes.items(.main_token); const name_token = var_decl.ast.mut_token + 1; const ident_name_raw = tree.tokenSlice(name_token); @@ -2391,6 +2392,14 @@ fn varDecl( return astgen.failNode(node, "variables must be initialized", .{}); } + if (var_decl.ast.addrspace_node != 0) { + return astgen.failTok(main_tokens[var_decl.ast.addrspace_node], "cannot set address space of local variable '{s}'", .{ ident_name_raw }); + } + + if (var_decl.ast.section_node != 0) { + return astgen.failTok(main_tokens[var_decl.ast.section_node], "cannot set section of local variable '{s}'", .{ ident_name_raw }); + } + const align_inst: Zir.Inst.Ref = if (var_decl.ast.align_node != 0) try expr(gz, scope, align_rl, var_decl.ast.align_node) else From 60231086508d26689d53b8bc545e8fe98cad966d Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 20 Aug 2021 01:22:44 +0200 Subject: [PATCH 041/160] Address Spaces: x86 segment address spaces in builtin --- lib/std/builtin.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index b9176794c6..09d93d5d21 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -170,7 +170,9 @@ pub const CallingConvention = enum { /// therefore must be kept in sync with the compiler implementation. pub const AddressSpace = enum { generic, - special, + gs, + fs, + ss, }; /// This data structure is used by the Zig language code generation and From cd9f6001af407a6961281cbe9c658cfe94b81ecb Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 20 Aug 2021 02:53:29 +0200 Subject: [PATCH 042/160] Address Spaces: decl_ref, *?T => *T, and *(E!T) -> *T --- src/Module.zig | 30 +++++++++++++++++++++++++++++- src/Sema.zig | 20 ++++++++++++++++---- src/type.zig | 24 ++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 957987b895..83b9a600d0 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -353,7 +353,7 @@ pub const Decl = struct { /// to require re-analysis. outdated, }, - /// Whether `typed_value`, `align_val`, `linksection_val` and `has_addrspace` are populated. + /// Whether `typed_value`, `align_val`, `linksection_val` and `addrspace` are populated. has_tv: bool, /// If `true` it means the `Decl` is the resource owner of the type/value associated /// with it. That means when `Decl` is destroyed, the cleanup code should additionally @@ -4401,6 +4401,34 @@ pub fn ptrType( }); } +/// Create a pointer type with an explicit address space. This function might return results +/// of either simplePtrType or ptrType, depending on the address space. +/// TODO(Snektron) unify ptrType functions. +pub fn simplePtrTypeWithAddressSpace( + arena: *Allocator, + elem_ty: Type, + mutable: bool, + size: std.builtin.TypeInfo.Pointer.Size, + address_space: std.builtin.AddressSpace, +) Allocator.Error!Type { + switch (address_space) { + .generic => return simplePtrType(arena, elem_ty, mutable, size), + else => return ptrType( + arena, + elem_ty, + null, + 0, + address_space, + 0, + 0, + mutable, + false, + false, + size, + ), + } +} + pub fn optionalType(arena: *Allocator, child_type: Type) Allocator.Error!Type { switch (child_type.tag()) { .single_const_pointer => return Type.Tag.optional_single_const_pointer.create( diff --git a/src/Sema.zig b/src/Sema.zig index b1145bab99..14f43ccf9e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3658,7 +3658,13 @@ fn zirOptionalPayloadPtr( } const child_type = try opt_type.optionalChildAlloc(sema.arena); - const child_pointer = try Module.simplePtrType(sema.arena, child_type, !optional_ptr_ty.isConstPtr(), .One); + const child_pointer = try Module.simplePtrTypeWithAddressSpace( + sema.arena, + child_type, + !optional_ptr_ty.isConstPtr(), + .One, + optional_ptr_ty.ptrAddressSpace(), + ); if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| { if (try pointer_val.pointerDeref(sema.arena)) |val| { @@ -3773,7 +3779,13 @@ fn zirErrUnionPayloadPtr( return sema.mod.fail(&block.base, src, "expected error union type, found {}", .{operand_ty.elemType()}); const payload_ty = operand_ty.elemType().errorUnionPayload(); - const operand_pointer_ty = try Module.simplePtrType(sema.arena, payload_ty, !operand_ty.isConstPtr(), .One); + const operand_pointer_ty = try Module.simplePtrTypeWithAddressSpace( + sema.arena, + payload_ty, + !operand_ty.isConstPtr(), + .One, + operand_ty.ptrAddressSpace(), + ); if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { if (try pointer_val.pointerDeref(sema.arena)) |val| { @@ -9525,11 +9537,11 @@ fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { const decl_tv = try decl.typedValue(); if (decl_tv.val.castTag(.variable)) |payload| { const variable = payload.data; - const ty = try Module.simplePtrType(sema.arena, decl_tv.ty, variable.is_mutable, .One); + const ty = try Module.simplePtrTypeWithAddressSpace(sema.arena, decl_tv.ty, variable.is_mutable, .One, decl.@"addrspace"); return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl)); } return sema.addConstant( - try Module.simplePtrType(sema.arena, decl_tv.ty, false, .One), + try Module.simplePtrTypeWithAddressSpace(sema.arena, decl_tv.ty, false, .One, decl.@"addrspace"), try Value.Tag.decl_ref.create(sema.arena, decl), ); } diff --git a/src/type.zig b/src/type.zig index 647d88c60f..b15026b595 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1526,6 +1526,30 @@ pub const Type = extern union { } } + pub fn ptrAddressSpace(self: Type) std.builtin.AddressSpace { + return switch (self.tag()) { + .single_const_pointer_to_comptime_int, + .const_slice_u8, + .single_const_pointer, + .single_mut_pointer, + .many_const_pointer, + .many_mut_pointer, + .c_const_pointer, + .c_mut_pointer, + .const_slice, + .mut_slice, + .inferred_alloc_const, + .inferred_alloc_mut, + .manyptr_u8, + .manyptr_const_u8, + => .generic, + + .pointer => self.castTag(.pointer).?.data.@"addrspace", + + else => unreachable, + }; + } + /// Asserts that hasCodeGenBits() is true. pub fn abiAlignment(self: Type, target: Target) u32 { return switch (self.tag()) { From 0e6dc64a6fa173ae92197f692cf907fdc8bbf811 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 26 Aug 2021 03:29:31 +0200 Subject: [PATCH 043/160] Address Spaces: Return proper address space for &x[y] --- src/Sema.zig | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 14f43ccf9e..a782cb2472 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9035,10 +9035,13 @@ fn elemPtrArray( ) CompileError!Air.Inst.Ref { const array_ptr_ty = sema.typeOf(array_ptr); const pointee_type = array_ptr_ty.elemType().elemType(); - const result_ty = if (array_ptr_ty.ptrIsMutable()) - try Type.Tag.single_mut_pointer.create(sema.arena, pointee_type) - else - try Type.Tag.single_const_pointer.create(sema.arena, pointee_type); + const result_ty = try Module.simplePtrTypeWithAddressSpace( + sema.arena, + pointee_type, + array_ptr_ty.ptrIsMutable(), + .One, + array_ptr_ty.ptrAddressSpace(), + ); if (try sema.resolveDefinedValue(block, src, array_ptr)) |array_ptr_val| { if (try sema.resolveDefinedValue(block, elem_index_src, elem_index)) |index_val| { From e182c17187c12cfb448cf47a04a156caaf9e3fc9 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 26 Aug 2021 03:31:03 +0200 Subject: [PATCH 044/160] Address Spaces: Disallow coercing pointers to different address spaces --- src/Sema.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sema.zig b/src/Sema.zig index a782cb2472..e0a085a010 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9132,6 +9132,7 @@ fn coerce( const dest_is_mut = !dest_type.isConstPtr(); if (inst_ty.isConstPtr() and dest_is_mut) break :src_array_ptr; if (inst_ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr; + if (inst_ty.ptrAddressSpace() != dest_type.ptrAddressSpace()) break :src_array_ptr; const dst_elem_type = dest_type.elemType(); switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut)) { From 64c328a71700608b4cf5a19644f96fe99fbac4dd Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 26 Aug 2021 03:51:22 +0200 Subject: [PATCH 045/160] Address Spaces: Default align, linksection & addrspace for anon decls --- src/Module.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Module.zig b/src/Module.zig index 83b9a600d0..b2cef0792a 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4204,6 +4204,9 @@ pub fn createAnonymousDeclFromDeclNamed( new_decl.src_line = owner_decl.src_line; new_decl.ty = typed_value.ty; new_decl.val = typed_value.val; + new_decl.align_val = Value.initTag(.null_value); + new_decl.linksection_val = Value.initTag(.null_value); + new_decl.@"addrspace" = .generic; // default global addrspace new_decl.has_tv = true; new_decl.analysis = .complete; new_decl.generation = mod.generation; From 497c0d3783c6a11cc64eaff72be050ce09a0ac13 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 26 Aug 2021 04:11:04 +0200 Subject: [PATCH 046/160] Allow x.y when x is a pointer --- src/Sema.zig | 99 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 38 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e0a085a010..9426c0caec 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8424,21 +8424,32 @@ fn fieldVal( } }, .One => { - const elem_ty = object_ty.elemType(); - if (elem_ty.zigTypeTag() == .Array) { - if (mem.eql(u8, field_name, "len")) { - return sema.addConstant( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(arena, elem_ty.arrayLen()), - ); - } else { - return mod.fail( - &block.base, - field_name_src, - "no member named '{s}' in '{}'", - .{ field_name, object_ty }, - ); - } + const ptr_child = object_ty.elemType(); + switch (ptr_child.zigTypeTag()) { + .Array => { + if (mem.eql(u8, field_name, "len")) { + return sema.addConstant( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(arena, ptr_child.arrayLen()), + ); + } else { + return mod.fail( + &block.base, + field_name_src, + "no member named '{s}' in '{}'", + .{ field_name, object_ty }, + ); + } + }, + .Struct => { + const struct_ptr_deref = try sema.analyzeLoad(block, src, object, object_src); + return sema.unionFieldVal(block, src, struct_ptr_deref, field_name, field_name_src, ptr_child); + }, + .Union => { + const union_ptr_deref = try sema.analyzeLoad(block, src, object, object_src); + return sema.unionFieldVal(block, src, union_ptr_deref, field_name, field_name_src, ptr_child); + }, + else => {}, } }, .Many, .C => {}, @@ -8562,9 +8573,8 @@ fn fieldPtr( ); } }, - .Pointer => { - const ptr_child = object_ty.elemType(); - if (ptr_child.isSlice()) { + .Pointer => switch (object_ty.ptrSize()) { + .Slice => { // Here for the ptr and len fields what we need to do is the situation // when a temporary has its address taken, e.g. `&a[c..d].len`. // This value may be known at compile-time or runtime. In the former @@ -8594,26 +8604,39 @@ fn fieldPtr( .{ field_name, object_ty }, ); } - } else switch (ptr_child.zigTypeTag()) { - .Array => { - if (mem.eql(u8, field_name, "len")) { - var anon_decl = try block.startAnonDecl(); - defer anon_decl.deinit(); - return sema.analyzeDeclRef(try anon_decl.finish( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), - )); - } else { - return mod.fail( - &block.base, - field_name_src, - "no member named '{s}' in '{}'", - .{ field_name, object_ty }, - ); - } - }, - else => {}, - } + }, + .One => { + const ptr_child = object_ty.elemType(); + switch (ptr_child.zigTypeTag()) { + .Array => { + if (mem.eql(u8, field_name, "len")) { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + return sema.analyzeDeclRef(try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), + )); + } else { + return mod.fail( + &block.base, + field_name_src, + "no member named '{s}' in '{}'", + .{ field_name, object_ty }, + ); + } + }, + .Struct => { + const struct_ptr_deref = try sema.analyzeLoad(block, src, object_ptr, object_ptr_src); + return sema.structFieldPtr(block, src, struct_ptr_deref, field_name, field_name_src, ptr_child); + }, + .Union => { + const union_ptr_deref = try sema.analyzeLoad(block, src, object_ptr, object_ptr_src); + return sema.unionFieldPtr(block, src, union_ptr_deref, field_name, field_name_src, ptr_child); + }, + else => {}, + } + }, + .Many, .C => {}, }, .Type => { _ = try sema.resolveConstValue(block, object_ptr_src, object_ptr); From 538f1bbcb35c10fb0aa633adbac64932cc89d034 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 26 Aug 2021 04:16:36 +0200 Subject: [PATCH 047/160] Address Spaces: Return proper address space for &x.y --- src/Sema.zig | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 9426c0caec..dfcfde71cc 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8778,13 +8778,20 @@ fn structFieldPtr( const arena = sema.arena; assert(unresolved_struct_ty.zigTypeTag() == .Struct); + const struct_ptr_ty = sema.typeOf(struct_ptr); const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_ty); const struct_obj = struct_ty.castTag(.@"struct").?.data; const field_index = struct_obj.fields.getIndex(field_name) orelse return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); const field = struct_obj.fields.values()[field_index]; - const ptr_field_ty = try Module.simplePtrType(arena, field.ty, true, .One); + const ptr_field_ty = try Module.simplePtrTypeWithAddressSpace( + arena, + field.ty, + struct_ptr_ty.ptrIsMutable(), + .One, + struct_ptr_ty.ptrAddressSpace(), + ); if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| { return sema.addConstant( @@ -8875,6 +8882,7 @@ fn unionFieldPtr( const arena = sema.arena; assert(unresolved_union_ty.zigTypeTag() == .Union); + const union_ptr_ty = sema.typeOf(union_ptr); const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty); const union_obj = union_ty.cast(Type.Payload.Union).?.data; @@ -8882,7 +8890,13 @@ fn unionFieldPtr( return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name); const field = union_obj.fields.values()[field_index]; - const ptr_field_ty = try Module.simplePtrType(arena, field.ty, true, .One); + const ptr_field_ty = try Module.simplePtrTypeWithAddressSpace( + arena, + field.ty, + union_ptr_ty.ptrIsMutable(), + .One, + union_ptr_ty.ptrAddressSpace(), + ); if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| { // TODO detect inactive union field and emit compile error From 8672f2696f20e8989d42f69adbe09edfe5cd9332 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 2 Sep 2021 14:50:40 +0200 Subject: [PATCH 048/160] Address Spaces: zig fmt + tests --- lib/std/zig/parser_test.zig | 34 +++++++++++++++++++-------- lib/std/zig/render.zig | 47 +++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 615648d1ad..2f79cc175c 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -404,6 +404,10 @@ test "zig fmt: trailing comma in fn parameter list" { \\pub fn f( \\ a: i32, \\ b: i32, + \\) addrspace(.generic) i32 {} + \\pub fn f( + \\ a: i32, + \\ b: i32, \\) linksection(".text") i32 {} \\pub fn f( \\ a: i32, @@ -553,8 +557,8 @@ test "zig fmt: sentinel-terminated slice type" { test "zig fmt: pointer-to-one with modifiers" { try testCanonical( \\const x: *u32 = undefined; - \\const y: *allowzero align(8) const volatile u32 = undefined; - \\const z: *allowzero align(8:4:2) const volatile u32 = undefined; + \\const y: *allowzero align(8) addrspace(.generic) const volatile u32 = undefined; + \\const z: *allowzero align(8:4:2) addrspace(.generic) const volatile u32 = undefined; \\ ); } @@ -562,8 +566,8 @@ test "zig fmt: pointer-to-one with modifiers" { test "zig fmt: pointer-to-many with modifiers" { try testCanonical( \\const x: [*]u32 = undefined; - \\const y: [*]allowzero align(8) const volatile u32 = undefined; - \\const z: [*]allowzero align(8:4:2) const volatile u32 = undefined; + \\const y: [*]allowzero align(8) addrspace(.generic) const volatile u32 = undefined; + \\const z: [*]allowzero align(8:4:2) addrspace(.generic) const volatile u32 = undefined; \\ ); } @@ -571,8 +575,8 @@ test "zig fmt: pointer-to-many with modifiers" { test "zig fmt: sentinel pointer with modifiers" { try testCanonical( \\const x: [*:42]u32 = undefined; - \\const y: [*:42]allowzero align(8) const volatile u32 = undefined; - \\const y: [*:42]allowzero align(8:4:2) const volatile u32 = undefined; + \\const y: [*:42]allowzero align(8) addrspace(.generic) const volatile u32 = undefined; + \\const y: [*:42]allowzero align(8:4:2) addrspace(.generic) const volatile u32 = undefined; \\ ); } @@ -580,8 +584,8 @@ test "zig fmt: sentinel pointer with modifiers" { test "zig fmt: c pointer with modifiers" { try testCanonical( \\const x: [*c]u32 = undefined; - \\const y: [*c]allowzero align(8) const volatile u32 = undefined; - \\const z: [*c]allowzero align(8:4:2) const volatile u32 = undefined; + \\const y: [*c]allowzero align(8) addrspace(.generic) const volatile u32 = undefined; + \\const z: [*c]allowzero align(8:4:2) addrspace(.generic) const volatile u32 = undefined; \\ ); } @@ -589,7 +593,7 @@ test "zig fmt: c pointer with modifiers" { test "zig fmt: slice with modifiers" { try testCanonical( \\const x: []u32 = undefined; - \\const y: []allowzero align(8) const volatile u32 = undefined; + \\const y: []allowzero align(8) addrspace(.generic) const volatile u32 = undefined; \\ ); } @@ -597,7 +601,7 @@ test "zig fmt: slice with modifiers" { test "zig fmt: sentinel slice with modifiers" { try testCanonical( \\const x: [:42]u32 = undefined; - \\const y: [:42]allowzero align(8) const volatile u32 = undefined; + \\const y: [:42]allowzero align(8) addrspace(.generic) const volatile u32 = undefined; \\ ); } @@ -1129,6 +1133,16 @@ test "zig fmt: linksection" { ); } +test "zig fmt: addrspace" { + try testCanonical( + \\export var python_length: u64 align(1) addrspace(.generic); + \\export var python_color: Color addrspace(.generic) = .green; + \\export var python_legs: u0 align(8) addrspace(.generic) linksection(".python") = 0; + \\export fn python_hiss() align(8) addrspace(.generic) linksection(".python") void; + \\ + ); +} + test "zig fmt: correctly space struct fields with doc comments" { try testTransform( \\pub const S = struct { diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 265049e1f9..0703b5bbfa 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -797,6 +797,14 @@ fn renderPtrType( } } + if (ptr_type.ast.addrspace_node != 0) { + const addrspace_first = tree.firstToken(ptr_type.ast.addrspace_node); + try renderToken(ais, tree, addrspace_first - 2, .none); // addrspace + try renderToken(ais, tree, addrspace_first - 1, .none); // lparen + try renderExpression(gpa, ais, tree, ptr_type.ast.addrspace_node, .none); + try renderToken(ais, tree, tree.lastToken(ptr_type.ast.addrspace_node) + 1, .space); // rparen + } + if (ptr_type.const_token) |const_token| { try renderToken(ais, tree, const_token, .space); } @@ -921,6 +929,7 @@ fn renderVarDecl(gpa: *Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDe const name_space = if (var_decl.ast.type_node == 0 and (var_decl.ast.align_node != 0 or + var_decl.ast.addrspace_node != 0 or var_decl.ast.section_node != 0 or var_decl.ast.init_node != 0)) Space.space @@ -930,8 +939,8 @@ fn renderVarDecl(gpa: *Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDe if (var_decl.ast.type_node != 0) { try renderToken(ais, tree, var_decl.ast.mut_token + 2, Space.space); // : - if (var_decl.ast.align_node != 0 or var_decl.ast.section_node != 0 or - var_decl.ast.init_node != 0) + if (var_decl.ast.align_node != 0 or var_decl.ast.addrspace_node != 0 or + var_decl.ast.section_node != 0 or var_decl.ast.init_node != 0) { try renderExpression(gpa, ais, tree, var_decl.ast.type_node, .space); } else { @@ -948,6 +957,22 @@ fn renderVarDecl(gpa: *Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDe try renderToken(ais, tree, align_kw, Space.none); // align try renderToken(ais, tree, lparen, Space.none); // ( try renderExpression(gpa, ais, tree, var_decl.ast.align_node, Space.none); + if (var_decl.ast.addrspace_node != 0 or var_decl.ast.section_node != 0 or + var_decl.ast.init_node != 0) { + try renderToken(ais, tree, rparen, .space); // ) + } else { + try renderToken(ais, tree, rparen, .none); // ) + return renderToken(ais, tree, rparen + 1, Space.newline); // ; + } + } + + if (var_decl.ast.addrspace_node != 0) { + const lparen = tree.firstToken(var_decl.ast.addrspace_node) - 1; + const addrspace_kw = lparen - 1; + const rparen = tree.lastToken(var_decl.ast.addrspace_node) + 1; + try renderToken(ais, tree, addrspace_kw, Space.none); // addrspace + try renderToken(ais, tree, lparen, Space.none); // ( + try renderExpression(gpa, ais, tree, var_decl.ast.addrspace_node, Space.none); if (var_decl.ast.section_node != 0 or var_decl.ast.init_node != 0) { try renderToken(ais, tree, rparen, .space); // ) } else { @@ -1267,6 +1292,14 @@ fn renderFnProto(gpa: *Allocator, ais: *Ais, tree: Ast, fn_proto: Ast.full.FnPro smallest_start = start; } } + if (fn_proto.ast.addrspace_expr != 0) { + const tok = tree.firstToken(fn_proto.ast.addrspace_expr) - 3; + const start = token_starts[tok]; + if (start < smallest_start) { + rparen = tok; + smallest_start = start; + } + } if (fn_proto.ast.section_expr != 0) { const tok = tree.firstToken(fn_proto.ast.section_expr) - 3; const start = token_starts[tok]; @@ -1407,6 +1440,16 @@ fn renderFnProto(gpa: *Allocator, ais: *Ais, tree: Ast, fn_proto: Ast.full.FnPro try renderToken(ais, tree, align_rparen, .space); // ) } + if (fn_proto.ast.addrspace_expr != 0) { + const align_lparen = tree.firstToken(fn_proto.ast.addrspace_expr) - 1; + const align_rparen = tree.lastToken(fn_proto.ast.addrspace_expr) + 1; + + try renderToken(ais, tree, align_lparen - 1, .none); // addrspace + try renderToken(ais, tree, align_lparen, .none); // ( + try renderExpression(gpa, ais, tree, fn_proto.ast.addrspace_expr, .none); + try renderToken(ais, tree, align_rparen, .space); // ) + } + if (fn_proto.ast.section_expr != 0) { const section_lparen = tree.firstToken(fn_proto.ast.section_expr) - 1; const section_rparen = tree.lastToken(fn_proto.ast.section_expr) + 1; From e77fcf17301ad5a68e84cb598984a526cc356621 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 27 Aug 2021 02:48:29 +0200 Subject: [PATCH 049/160] Address Spaces: Implement right address space for slicing --- src/Sema.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index dfcfde71cc..4b75cc4c43 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9767,9 +9767,7 @@ fn analyzeSlice( return_elem_type, if (end_opt == .none) slice_sentinel else null, 0, // TODO alignment - // TODO(Snektron) address space, should be inferred from the pointer type. - // TODO(Snektron) address space for slicing a local, should compute address space from context and architecture. - .generic, + if (ptr_child.zigTypeTag() == .Pointer) ptr_child.ptrAddressSpace() else .generic, 0, 0, !ptr_child.isConstPtr(), From 6336f08c2122faf712cb4b5096555e2617ce3936 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 27 Aug 2021 04:26:05 +0200 Subject: [PATCH 050/160] Address Spaces: Address space on local variable test --- test/cases.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/cases.zig b/test/cases.zig index 59f0ef7146..33fa5b19e2 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1807,4 +1807,16 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + + { + var case = ctx.exe("setting an address space on a local variable", linux_x64); + case.addError( + \\export fn entry() i32 { + \\ var foo: i32 addrspace(".general") = 1234; + \\ return foo; + \\} + , &[_][]const u8{ + ":2:28: error: cannot set address space of local variable 'foo'", + }); + } } From 7686165c8265cebfb7a3d7d4fd4f00a46dc4743a Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 27 Aug 2021 05:04:13 +0200 Subject: [PATCH 051/160] Address Spaces: Pointer coercion errors tests --- test/cases.zig | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/test/cases.zig b/test/cases.zig index 33fa5b19e2..1a92f20262 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -861,7 +861,7 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); try case.files.append(.{ - .src = + .src = \\pub fn print() void { \\ asm volatile ("syscall" \\ : @@ -924,7 +924,7 @@ pub fn addCases(ctx: *TestContext) !void { }, ); try case.files.append(.{ - .src = + .src = \\// dummy comment to make print be on line 2 \\fn print() void { \\ asm volatile ("syscall" @@ -1819,4 +1819,43 @@ pub fn addCases(ctx: *TestContext) !void { ":2:28: error: cannot set address space of local variable 'foo'", }); } + + { + var case = ctx.exe("address space pointer coercions", linux_x64); + case.addError( + \\fn entry(a: *addrspace(.gs) i32) *i32 { + \\ return a; + \\} + \\pub fn main() void { _ = entry; } + , &[_][]const u8{ + ":2:12: error: expected *i32, found *addrspace(.gs) i32", + }); + + case.addError( + \\fn entry(a: *addrspace(.gs) i32) *addrspace(.fs) i32 { + \\ return a; + \\} + \\pub fn main() void { _ = entry; } + , &[_][]const u8{ + ":2:12: error: expected *addrspace(.fs) i32, found *addrspace(.gs) i32", + }); + + case.addError( + \\fn entry(a: ?*addrspace(.gs) i32) *i32 { + \\ return a.?; + \\} + \\pub fn main() void { _ = entry; } + , &[_][]const u8{ + ":2:13: error: expected *i32, found *addrspace(.gs) i32", + }); + + case.addError( + \\fn entry(a: *addrspace(.gs) i32) *i32 { + \\ return &a.*; + \\} + \\pub fn main() void { _ = entry; } + , &[_][]const u8{ + ":2:12: error: expected *i32, found *addrspace(.gs) i32", + }); + } } From 8f28c5875911c663541dd4fd1a48fecfe412e8bf Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 29 Aug 2021 02:19:58 +0200 Subject: [PATCH 052/160] Address Spaces: compiles() test cases --- test/cases.zig | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/cases.zig b/test/cases.zig index 1a92f20262..e36bb368c8 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1831,6 +1831,20 @@ pub fn addCases(ctx: *TestContext) !void { ":2:12: error: expected *i32, found *addrspace(.gs) i32", }); + case.compiles( + \\fn entry(a: *addrspace(.gs) i32) *addrspace(.gs) i32 { + \\ return a; + \\} + \\pub fn main() void { _ = entry; } + ); + + case.compiles( + \\fn entry(a: *addrspace(.generic) i32) *i32 { + \\ return a; + \\} + \\pub fn main() void { _ = entry; } + ); + case.addError( \\fn entry(a: *addrspace(.gs) i32) *addrspace(.fs) i32 { \\ return a; @@ -1857,5 +1871,12 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{ ":2:12: error: expected *i32, found *addrspace(.gs) i32", }); + + case.compiles( + \\fn entry(a: *addrspace(.gs) i32) *addrspace(.gs) i32 { + \\ return &a.*; + \\} + \\pub fn main() void { _ = entry; } + ); } } From 2f43749c2b48b1ef3e59663f3e7033500a4b0c0c Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 1 Sep 2021 17:03:41 +0200 Subject: [PATCH 053/160] Address Spaces: Move stage 2 tests to stage2/llvm.zig --- test/cases.zig | 60 -------------------------------------------- test/stage2/llvm.zig | 60 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/test/cases.zig b/test/cases.zig index e36bb368c8..62677b8424 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1819,64 +1819,4 @@ pub fn addCases(ctx: *TestContext) !void { ":2:28: error: cannot set address space of local variable 'foo'", }); } - - { - var case = ctx.exe("address space pointer coercions", linux_x64); - case.addError( - \\fn entry(a: *addrspace(.gs) i32) *i32 { - \\ return a; - \\} - \\pub fn main() void { _ = entry; } - , &[_][]const u8{ - ":2:12: error: expected *i32, found *addrspace(.gs) i32", - }); - - case.compiles( - \\fn entry(a: *addrspace(.gs) i32) *addrspace(.gs) i32 { - \\ return a; - \\} - \\pub fn main() void { _ = entry; } - ); - - case.compiles( - \\fn entry(a: *addrspace(.generic) i32) *i32 { - \\ return a; - \\} - \\pub fn main() void { _ = entry; } - ); - - case.addError( - \\fn entry(a: *addrspace(.gs) i32) *addrspace(.fs) i32 { - \\ return a; - \\} - \\pub fn main() void { _ = entry; } - , &[_][]const u8{ - ":2:12: error: expected *addrspace(.fs) i32, found *addrspace(.gs) i32", - }); - - case.addError( - \\fn entry(a: ?*addrspace(.gs) i32) *i32 { - \\ return a.?; - \\} - \\pub fn main() void { _ = entry; } - , &[_][]const u8{ - ":2:13: error: expected *i32, found *addrspace(.gs) i32", - }); - - case.addError( - \\fn entry(a: *addrspace(.gs) i32) *i32 { - \\ return &a.*; - \\} - \\pub fn main() void { _ = entry; } - , &[_][]const u8{ - ":2:12: error: expected *i32, found *addrspace(.gs) i32", - }); - - case.compiles( - \\fn entry(a: *addrspace(.gs) i32) *addrspace(.gs) i32 { - \\ return &a.*; - \\} - \\pub fn main() void { _ = entry; } - ); - } } diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig index 34f73b01c7..b5ec47c5de 100644 --- a/test/stage2/llvm.zig +++ b/test/stage2/llvm.zig @@ -242,4 +242,64 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + + { + var case = ctx.exeUsingLlvmBackend("address space pointer coercions", linux_x64); + case.addError( + \\fn entry(a: *addrspace(.gs) i32) *i32 { + \\ return a; + \\} + \\pub export fn main() void { _ = entry; } + , &[_][]const u8{ + ":2:12: error: expected *i32, found *addrspace(.gs) i32", + }); + + case.compiles( + \\fn entry(a: *addrspace(.gs) i32) *addrspace(.gs) i32 { + \\ return a; + \\} + \\pub export fn main() void { _ = entry; } + ); + + case.compiles( + \\fn entry(a: *addrspace(.generic) i32) *i32 { + \\ return a; + \\} + \\pub export fn main() void { _ = entry; } + ); + + case.addError( + \\fn entry(a: *addrspace(.gs) i32) *addrspace(.fs) i32 { + \\ return a; + \\} + \\pub export fn main() void { _ = entry; } + , &[_][]const u8{ + ":2:12: error: expected *addrspace(.fs) i32, found *addrspace(.gs) i32", + }); + + case.addError( + \\fn entry(a: ?*addrspace(.gs) i32) *i32 { + \\ return a.?; + \\} + \\pub export fn main() void { _ = entry; } + , &[_][]const u8{ + ":2:13: error: expected *i32, found *addrspace(.gs) i32", + }); + + case.addError( + \\fn entry(a: *addrspace(.gs) i32) *i32 { + \\ return &a.*; + \\} + \\pub export fn main() void { _ = entry; } + , &[_][]const u8{ + ":2:12: error: expected *i32, found *addrspace(.gs) i32", + }); + + case.compiles( + \\fn entry(a: *addrspace(.gs) i32) *addrspace(.gs) i32 { + \\ return &a.*; + \\} + \\pub export fn main() void { _ = entry; } + ); + } } From e09465fc49d86cc4aa9338106862d3e059ae3303 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 29 Aug 2021 03:18:23 +0200 Subject: [PATCH 054/160] Address Spaces: Chaining tests --- src/codegen/llvm/bindings.zig | 59 +++++++++++++++++++++++++++++++++++ test/stage2/llvm.zig | 52 ++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index db1dcd22f2..67b39784b1 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -973,3 +973,62 @@ pub const TypeKind = enum(c_int) { BFloat, X86_AMX, }; + +pub const address_space = struct { + // See llvm/lib/Target/X86/X86.h + pub const x86_64 = x86; + pub const x86 = struct { + pub const gs = 256; + pub const fs = 257; + pub const ss = 258; + + pub const ptr32_sptr = 270; + pub const ptr32_uptr = 271; + pub const ptr64 = 272; + }; + + // See llvm/lib/Target/AVR/AVR.h + pub const avr = struct { + pub const data_memory = 0; + pub const program_memory = 1; + }; + + // See llvm/lib/Target/NVPTX/NVPTX.h + pub const nvptx = struct { + pub const generic = 0; + pub const global = 1; + pub const constant = 2; + pub const shared = 3; + pub const param = 4; + pub const local = 5; + }; + + // See llvm/lib/Target/AMDGPU/AMDGPU.h + pub const amdgpu = struct { + pub const flat = 0; + pub const global = 1; + pub const region = 2; + pub const local = 3; + pub const constant = 4; + pub const constant_32bit = 6; + pub const buffer_fat_pointer = 7; + pub const param_d = 6; + pub const param_i = 7; + pub const constant_buffer_0 = 8; + pub const constant_buffer_1 = 9; + pub const constant_buffer_2 = 10; + pub const constant_buffer_3 = 11; + pub const constant_buffer_4 = 12; + pub const constant_buffer_5 = 13; + pub const constant_buffer_6 = 14; + pub const constant_buffer_7 = 15; + pub const constant_buffer_8 = 16; + pub const constant_buffer_9 = 17; + pub const constant_buffer_10 = 18; + pub const constant_buffer_11 = 19; + pub const constant_buffer_12 = 20; + pub const constant_buffer_13 = 21; + pub const constant_buffer_14 = 22; + pub const constant_buffer_15 = 23; + }; +}; diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig index b5ec47c5de..e61e7181c6 100644 --- a/test/stage2/llvm.zig +++ b/test/stage2/llvm.zig @@ -302,4 +302,56 @@ pub fn addCases(ctx: *TestContext) !void { \\pub export fn main() void { _ = entry; } ); } + + { + var case = ctx.exeUsingLlvmBackend("address spaces pointer access chaining: array pointer", linux_x64); + case.compiles( + \\fn entry(a: *addrspace(.gs) [1]i32) *addrspace(.gs) i32 { + \\ return &a[0]; + \\} + \\pub export fn main() void { _ = entry; } + ); + } + + { + var case = ctx.exeUsingLlvmBackend("address spaces pointer access chaining: pointer to optional array", linux_x64); + case.compiles( + \\fn entry(a: *addrspace(.gs) ?[1]i32) *addrspace(.gs) i32 { + \\ return &a.*.?[0]; + \\} + \\pub export fn main() void { _ = entry; } + ); + } + + { + var case = ctx.exeUsingLlvmBackend("address spaces pointer access chaining: struct pointer", linux_x64); + case.compiles( + \\const A = struct{ a: i32 }; + \\fn entry(a: *addrspace(.gs) A) *addrspace(.gs) i32 { + \\ return &a.a; + \\} + \\pub export fn main() void { _ = entry; } + ); + } + + { + var case = ctx.exeUsingLlvmBackend("address spaces pointer access chaining: complex", linux_x64); + case.compiles( + \\const A = struct{ a: ?[1]i32 }; + \\fn entry(a: *addrspace(.gs) [1]A) *addrspace(.gs) i32 { + \\ return &a[0].a.?[0]; + \\} + \\pub export fn main() void { _ = entry; } + ); + } + + { + var case = ctx.exeUsingLlvmBackend("dereferencing through multiple pointers with address spaces", linux_x64); + case.compiles( + \\fn entry(a: *addrspace(.fs) *addrspace(.gs) *i32) *i32 { + \\ return a.*.*; + \\} + \\pub export fn main() void { _ = entry; } + ); + } } From ea393b2bca7587955df81d149caecc5522944d15 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 29 Aug 2021 06:08:19 +0200 Subject: [PATCH 055/160] Address Spaces: Implement in LLVM codegen --- src/Module.zig | 2 +- src/Sema.zig | 2 +- src/codegen.zig | 2 +- src/codegen/c.zig | 2 +- src/codegen/llvm.zig | 40 +++++++++++++++++++++++------ src/codegen/llvm/bindings.zig | 8 ++++++ src/type.zig | 48 ++++++++++++++++++++++++++++------- src/zig_llvm.cpp | 5 ++++ src/zig_llvm.h | 3 +++ 9 files changed, 91 insertions(+), 21 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index b2cef0792a..1f9b5abcb9 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4761,7 +4761,7 @@ pub fn populateTestFunctions(mod: *Module) !void { const builtin_file = (mod.importPkg(builtin_pkg) catch unreachable).file; const builtin_namespace = builtin_file.root_decl.?.namespace; const decl = builtin_namespace.decls.get("test_functions").?; - var buf: Type.Payload.ElemType = undefined; + var buf: Type.SlicePtrFieldTypeBuffer = undefined; const tmp_test_fn_ty = decl.ty.slicePtrFieldType(&buf).elemType(); const array_decl = d: { diff --git a/src/Sema.zig b/src/Sema.zig index 4b75cc4c43..d6e926e604 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8390,7 +8390,7 @@ fn fieldVal( .Pointer => switch (object_ty.ptrSize()) { .Slice => { if (mem.eql(u8, field_name, "ptr")) { - const buf = try arena.create(Type.Payload.ElemType); + const buf = try arena.create(Type.SlicePtrFieldTypeBuffer); const result_ty = object_ty.slicePtrFieldType(buf); if (try sema.resolveMaybeUndefVal(block, object_src, object)) |val| { if (val.isUndef()) return sema.addConstUndef(result_ty); diff --git a/src/codegen.zig b/src/codegen.zig index e0047de1f7..cbee79f139 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -4873,7 +4873,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (typed_value.ty.zigTypeTag()) { .Pointer => switch (typed_value.ty.ptrSize()) { .Slice => { - var buf: Type.Payload.ElemType = undefined; + var buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_type = typed_value.ty.slicePtrFieldType(&buf); const ptr_mcv = try self.genTypedValue(.{ .ty = ptr_type, .val = typed_value.val }); const slice_len = typed_value.val.sliceLen(); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 5eb4388a9e..7a2dc343f7 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -251,7 +251,7 @@ pub const DeclGen = struct { try writer.writeByte('('); try dg.renderType(writer, t); try writer.writeAll("){"); - var buf: Type.Payload.ElemType = undefined; + var buf: Type.SlicePtrFieldTypeBuffer = undefined; try dg.renderValue(writer, t.slicePtrFieldType(&buf), val); try writer.writeAll(", "); try writer.print("{d}", .{val.sliceLen()}); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 53e57ee219..8b7282160e 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -554,7 +554,8 @@ pub const DeclGen = struct { @intCast(c_uint, fn_param_len), .False, ); - const llvm_fn = self.llvmModule().addFunction(decl.name, fn_type); + const llvm_addrspace = self.llvmAddressSpace(decl.@"addrspace"); + const llvm_fn = self.llvmModule().addFunctionInAddressSpace(decl.name, fn_type, llvm_addrspace); const is_extern = decl.val.tag() == .extern_fn; if (!is_extern) { @@ -576,7 +577,27 @@ pub const DeclGen = struct { if (llvm_module.getNamedGlobal(decl.name)) |val| return val; // TODO: remove this redundant `llvmType`, it is also called in `genTypedValue`. const llvm_type = try self.llvmType(decl.ty); - return llvm_module.addGlobal(llvm_type, decl.name); + const llvm_addrspace = self.llvmAddressSpace(decl.@"addrspace"); + return llvm_module.addGlobalInAddressSpace(llvm_type, decl.name, llvm_addrspace); + } + + fn llvmAddressSpace(self: DeclGen, address_space: std.builtin.AddressSpace) c_uint { + const target = self.module.getTarget(); + return switch (address_space) { + .generic => llvm.address_space.default, + .gs => switch (target.cpu.arch) { + .i386, .x86_64 => llvm.address_space.x86.gs, + else => unreachable, + }, + .fs => switch (target.cpu.arch) { + .i386, .x86_64 => llvm.address_space.x86.fs, + else => unreachable, + }, + .ss => switch (target.cpu.arch) { + .i386, .x86_64 => llvm.address_space.x86.ss, + else => unreachable, + }, + }; } fn llvmType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type { @@ -605,7 +626,7 @@ pub const DeclGen = struct { .Bool => return self.context.intType(1), .Pointer => { if (t.isSlice()) { - var buf: Type.Payload.ElemType = undefined; + var buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_type = t.slicePtrFieldType(&buf); const fields: [2]*const llvm.Type = .{ @@ -615,7 +636,8 @@ pub const DeclGen = struct { return self.context.structType(&fields, fields.len, .False); } else { const elem_type = try self.llvmType(t.elemType()); - return elem_type.pointerType(0); + const llvm_addrspace = self.llvmAddressSpace(t.ptrAddressSpace()); + return elem_type.pointerType(llvm_addrspace); } }, .Array => { @@ -681,7 +703,8 @@ pub const DeclGen = struct { @intCast(c_uint, llvm_params.len), llvm.Bool.fromBool(is_var_args), ); - return llvm_fn_ty.pointerType(0); + const llvm_addrspace = self.llvmAddressSpace(t.fnAddressSpace()); + return llvm_fn_ty.pointerType(llvm_addrspace); }, .ComptimeInt => unreachable, .ComptimeFloat => unreachable, @@ -749,7 +772,7 @@ pub const DeclGen = struct { .Pointer => switch (tv.val.tag()) { .decl_ref => { if (tv.ty.isSlice()) { - var buf: Type.Payload.ElemType = undefined; + var buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_ty = tv.ty.slicePtrFieldType(&buf); var slice_len: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, @@ -779,12 +802,13 @@ pub const DeclGen = struct { decl.alive = true; const val = try self.resolveGlobalDecl(decl); const llvm_var_type = try self.llvmType(tv.ty); - const llvm_type = llvm_var_type.pointerType(0); + const llvm_addrspace = self.llvmAddressSpace(decl.@"addrspace"); + const llvm_type = llvm_var_type.pointerType(llvm_addrspace); return val.constBitCast(llvm_type); }, .slice => { const slice = tv.val.castTag(.slice).?.data; - var buf: Type.Payload.ElemType = undefined; + var buf: Type.SlicePtrFieldTypeBuffer = undefined; const fields: [2]*const llvm.Value = .{ try self.genTypedValue(.{ .ty = tv.ty.slicePtrFieldType(&buf), diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 67b39784b1..e50589dee1 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -197,6 +197,9 @@ pub const Module = opaque { pub const addFunction = LLVMAddFunction; extern fn LLVMAddFunction(*const Module, Name: [*:0]const u8, FunctionTy: *const Type) *const Value; + pub const addFunctionInAddressSpace = ZigLLVMAddFunctionInAddressSpace; + extern fn ZigLLVMAddFunctionInAddressSpace(*const Module, Name: [*:0]const u8, FunctionTy: *const Type, AddressSpace: c_uint) *const Value; + pub const getNamedFunction = LLVMGetNamedFunction; extern fn LLVMGetNamedFunction(*const Module, Name: [*:0]const u8) ?*const Value; @@ -209,6 +212,9 @@ pub const Module = opaque { pub const addGlobal = LLVMAddGlobal; extern fn LLVMAddGlobal(M: *const Module, Ty: *const Type, Name: [*:0]const u8) *const Value; + pub const addGlobalInAddressSpace = LLVMAddGlobalInAddressSpace; + extern fn LLVMAddGlobalInAddressSpace(M: *const Module, Ty: *const Type, Name: [*:0]const u8, AddressSpace: c_uint) *const Value; + pub const getNamedGlobal = LLVMGetNamedGlobal; extern fn LLVMGetNamedGlobal(M: *const Module, Name: [*:0]const u8) ?*const Value; @@ -975,6 +981,8 @@ pub const TypeKind = enum(c_int) { }; pub const address_space = struct { + pub const default = 0; + // See llvm/lib/Target/X86/X86.h pub const x86_64 = x86; pub const x86 = struct { diff --git a/src/type.zig b/src/type.zig index b15026b595..9cda4aacf7 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2161,42 +2161,72 @@ pub const Type = extern union { }; } - pub fn slicePtrFieldType(self: Type, buffer: *Payload.ElemType) Type { + pub const SlicePtrFieldTypeBuffer = union { + elem_type: Payload.ElemType, + pointer: Payload.Pointer, + }; + + pub fn slicePtrFieldType(self: Type, buffer: *SlicePtrFieldTypeBuffer) Type { switch (self.tag()) { .const_slice_u8 => return Type.initTag(.manyptr_const_u8), .const_slice => { const elem_type = self.castTag(.const_slice).?.data; - buffer.* = .{ + buffer.elem_type = .{ .base = .{ .tag = .many_const_pointer }, .data = elem_type, }; - return Type.initPayload(&buffer.base); + return Type.initPayload(&buffer.elem_type.base); }, .mut_slice => { const elem_type = self.castTag(.mut_slice).?.data; - buffer.* = .{ + buffer.elem_type = .{ .base = .{ .tag = .many_mut_pointer }, .data = elem_type, }; - return Type.initPayload(&buffer.base); + return Type.initPayload(&buffer.elem_type.base); }, .pointer => { const payload = self.castTag(.pointer).?.data; assert(payload.size == .Slice); - if (payload.mutable) { - buffer.* = .{ + + if (payload.sentinel != null or + payload.@"align" != 0 or + payload.@"addrspace" != .generic or + payload.bit_offset != 0 or + payload.host_size != 0 or + payload.@"allowzero" or + payload.@"volatile" + ) { + buffer.pointer = .{ + .data = .{ + .pointee_type = payload.pointee_type, + .sentinel = payload.sentinel, + .@"align" = payload.@"align", + .@"addrspace" = payload.@"addrspace", + .bit_offset = payload.bit_offset, + .host_size = payload.host_size, + .@"allowzero" = payload.@"allowzero", + .mutable = payload.mutable, + .@"volatile" = payload.@"volatile", + .size = .Many + }, + }; + return Type.initPayload(&buffer.pointer.base); + } else if (payload.mutable) { + buffer.elem_type = .{ .base = .{ .tag = .many_mut_pointer }, .data = payload.pointee_type, }; + return Type.initPayload(&buffer.elem_type.base); } else { - buffer.* = .{ + buffer.elem_type = .{ .base = .{ .tag = .many_const_pointer }, .data = payload.pointee_type, }; + return Type.initPayload(&buffer.elem_type.base); } - return Type.initPayload(&buffer.base); }, else => unreachable, diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index e1ab74f423..6e136161a6 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -416,6 +416,11 @@ ZIG_EXTERN_C LLVMTypeRef ZigLLVMTokenTypeInContext(LLVMContextRef context_ref) { return wrap(Type::getTokenTy(*unwrap(context_ref))); } +LLVMValueRef ZigLLVMAddFunctionInAddressSpace(LLVMModuleRef M, const char *Name, LLVMTypeRef FunctionTy, unsigned AddressSpace) { + Function* func = Function::Create(unwrap(FunctionTy), GlobalValue::ExternalLinkage, AddressSpace, Name, unwrap(M)); + return wrap(func); +} + LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMValueRef Fn, LLVMValueRef *Args, unsigned NumArgs, ZigLLVM_CallingConv CC, ZigLLVM_CallAttr attr, const char *Name) { diff --git a/src/zig_llvm.h b/src/zig_llvm.h index be279d86e1..49a4c0e8fd 100644 --- a/src/zig_llvm.h +++ b/src/zig_llvm.h @@ -65,6 +65,9 @@ ZIG_EXTERN_C LLVMTargetMachineRef ZigLLVMCreateTargetMachine(LLVMTargetRef T, co ZIG_EXTERN_C LLVMTypeRef ZigLLVMTokenTypeInContext(LLVMContextRef context_ref); +ZIG_EXTERN_C LLVMValueRef ZigLLVMAddFunctionInAddressSpace(LLVMModuleRef M, const char *Name, + LLVMTypeRef FunctionTy, unsigned AddressSpace); + enum ZigLLVM_CallingConv { ZigLLVM_C = 0, ZigLLVM_Fast = 8, From 68fcbb5c0d5ece02876824a8be0e18b306954e26 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Sun, 29 Aug 2021 19:41:57 +0200 Subject: [PATCH 056/160] Address Spaces: fmt a bunch of stuff --- lib/std/zig/render.zig | 3 ++- src/AstGen.zig | 4 ++-- src/Zir.zig | 2 +- src/type.zig | 10 +++++----- test/cases.zig | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 0703b5bbfa..3029d38cb9 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -958,7 +958,8 @@ fn renderVarDecl(gpa: *Allocator, ais: *Ais, tree: Ast, var_decl: Ast.full.VarDe try renderToken(ais, tree, lparen, Space.none); // ( try renderExpression(gpa, ais, tree, var_decl.ast.align_node, Space.none); if (var_decl.ast.addrspace_node != 0 or var_decl.ast.section_node != 0 or - var_decl.ast.init_node != 0) { + var_decl.ast.init_node != 0) + { try renderToken(ais, tree, rparen, .space); // ) } else { try renderToken(ais, tree, rparen, .none); // ) diff --git a/src/AstGen.zig b/src/AstGen.zig index cffc626a1e..2e4671d92e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2393,11 +2393,11 @@ fn varDecl( } if (var_decl.ast.addrspace_node != 0) { - return astgen.failTok(main_tokens[var_decl.ast.addrspace_node], "cannot set address space of local variable '{s}'", .{ ident_name_raw }); + return astgen.failTok(main_tokens[var_decl.ast.addrspace_node], "cannot set address space of local variable '{s}'", .{ident_name_raw}); } if (var_decl.ast.section_node != 0) { - return astgen.failTok(main_tokens[var_decl.ast.section_node], "cannot set section of local variable '{s}'", .{ ident_name_raw }); + return astgen.failTok(main_tokens[var_decl.ast.section_node], "cannot set section of local variable '{s}'", .{ident_name_raw}); } const align_inst: Zir.Inst.Ref = if (var_decl.ast.align_node != 0) diff --git a/src/Zir.zig b/src/Zir.zig index 0e50f8c256..50c53e5485 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -4029,7 +4029,7 @@ const Writer = struct { }; const addrspace_inst: Inst.Ref = if (!has_section_or_addrspace) .none else inst: { const inst = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index +=1; + extra_index += 1; break :inst inst; }; const pub_str = if (is_pub) "pub " else ""; diff --git a/src/type.zig b/src/type.zig index 9cda4aacf7..fa2147f852 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1002,7 +1002,7 @@ pub const Type = extern union { try writer.writeAll(@tagName(payload.cc)); try writer.writeAll(") "); if (payload.@"addrspace" != .generic) { - try writer.print("addrspace(.{s}) ", .{ @tagName(payload.@"addrspace") }); + try writer.print("addrspace(.{s}) ", .{@tagName(payload.@"addrspace")}); } ty = payload.return_type; continue; @@ -1136,7 +1136,7 @@ pub const Type = extern union { try writer.writeAll(") "); } if (payload.@"addrspace" != .generic) { - try writer.print("addrspace(.{s}) ", .{ @tagName(payload.@"addrspace") }); + try writer.print("addrspace(.{s}) ", .{@tagName(payload.@"addrspace")}); } if (!payload.mutable) try writer.writeAll("const "); if (payload.@"volatile") try writer.writeAll("volatile "); @@ -2197,8 +2197,8 @@ pub const Type = extern union { payload.bit_offset != 0 or payload.host_size != 0 or payload.@"allowzero" or - payload.@"volatile" - ) { + payload.@"volatile") + { buffer.pointer = .{ .data = .{ .pointee_type = payload.pointee_type, @@ -2210,7 +2210,7 @@ pub const Type = extern union { .@"allowzero" = payload.@"allowzero", .mutable = payload.mutable, .@"volatile" = payload.@"volatile", - .size = .Many + .size = .Many, }, }; return Type.initPayload(&buffer.pointer.base); diff --git a/test/cases.zig b/test/cases.zig index 62677b8424..33fa5b19e2 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -861,7 +861,7 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); try case.files.append(.{ - .src = + .src = \\pub fn print() void { \\ asm volatile ("syscall" \\ : @@ -924,7 +924,7 @@ pub fn addCases(ctx: *TestContext) !void { }, ); try case.files.append(.{ - .src = + .src = \\// dummy comment to make print be on line 2 \\fn print() void { \\ asm volatile ("syscall" From 0492b71319e5fa2367258b33206bc0d06f4a42be Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 30 Aug 2021 00:22:08 +0200 Subject: [PATCH 057/160] Address Spaces: Smol fixup --- src/type.zig | 58 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/src/type.zig b/src/type.zig index fa2147f852..e8a9d059d0 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2172,17 +2172,21 @@ pub const Type = extern union { .const_slice => { const elem_type = self.castTag(.const_slice).?.data; - buffer.elem_type = .{ - .base = .{ .tag = .many_const_pointer }, - .data = elem_type, + buffer.* = .{ + .elem_type = .{ + .base = .{ .tag = .many_const_pointer }, + .data = elem_type, + }, }; return Type.initPayload(&buffer.elem_type.base); }, .mut_slice => { const elem_type = self.castTag(.mut_slice).?.data; - buffer.elem_type = .{ - .base = .{ .tag = .many_mut_pointer }, - .data = elem_type, + buffer.* = .{ + .elem_type = .{ + .base = .{ .tag = .many_mut_pointer }, + .data = elem_type, + }, }; return Type.initPayload(&buffer.elem_type.base); }, @@ -2199,31 +2203,37 @@ pub const Type = extern union { payload.@"allowzero" or payload.@"volatile") { - buffer.pointer = .{ - .data = .{ - .pointee_type = payload.pointee_type, - .sentinel = payload.sentinel, - .@"align" = payload.@"align", - .@"addrspace" = payload.@"addrspace", - .bit_offset = payload.bit_offset, - .host_size = payload.host_size, - .@"allowzero" = payload.@"allowzero", - .mutable = payload.mutable, - .@"volatile" = payload.@"volatile", - .size = .Many, + buffer.* = .{ + .pointer = .{ + .data = .{ + .pointee_type = payload.pointee_type, + .sentinel = payload.sentinel, + .@"align" = payload.@"align", + .@"addrspace" = payload.@"addrspace", + .bit_offset = payload.bit_offset, + .host_size = payload.host_size, + .@"allowzero" = payload.@"allowzero", + .mutable = payload.mutable, + .@"volatile" = payload.@"volatile", + .size = .Many, + }, }, }; return Type.initPayload(&buffer.pointer.base); } else if (payload.mutable) { - buffer.elem_type = .{ - .base = .{ .tag = .many_mut_pointer }, - .data = payload.pointee_type, + buffer.* = .{ + .elem_type = .{ + .base = .{ .tag = .many_mut_pointer }, + .data = payload.pointee_type, + }, }; return Type.initPayload(&buffer.elem_type.base); } else { - buffer.elem_type = .{ - .base = .{ .tag = .many_const_pointer }, - .data = payload.pointee_type, + buffer.* = .{ + .elem_type = .{ + .base = .{ .tag = .many_const_pointer }, + .data = payload.pointee_type, + }, }; return Type.initPayload(&buffer.elem_type.base); } From 7a5d0cdf45f861f63d89666607dc86b3a2810826 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 30 Aug 2021 01:09:22 +0200 Subject: [PATCH 058/160] Address Spaces: Render addrspace token in docgen --- doc/docgen.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/docgen.zig b/doc/docgen.zig index 79fd1519cf..a062767ebf 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -901,6 +901,7 @@ fn tokenizeAndPrintRaw( switch (token.tag) { .eof => break, + .keyword_addrspace, .keyword_align, .keyword_and, .keyword_asm, From c5945467acfd580cb3413250ef52f13dc412a8cf Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 30 Aug 2021 02:54:03 +0200 Subject: [PATCH 059/160] Address Spaces: Pointer and function info in @Type --- lib/std/builtin.zig | 2 + lib/std/mem.zig | 2 + lib/std/meta.zig | 3 + lib/std/zig/c_translation.zig | 1 + src/Sema.zig | 15 +++-- src/stage1/all_types.hpp | 8 +++ src/stage1/analyze.cpp | 10 +++ src/stage1/analyze.hpp | 2 + src/stage1/ir.cpp | 123 ++++++++++++++++++++++------------ test/behavior/type.zig | 1 + test/compile_errors.zig | 4 ++ 11 files changed, 122 insertions(+), 49 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 09d93d5d21..11273eedeb 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -235,6 +235,7 @@ pub const TypeInfo = union(enum) { is_const: bool, is_volatile: bool, alignment: comptime_int, + address_space: AddressSpace, child: type, is_allowzero: bool, @@ -364,6 +365,7 @@ pub const TypeInfo = union(enum) { pub const Fn = struct { calling_convention: CallingConvention, alignment: comptime_int, + address_space: AddressSpace, is_generic: bool, is_var_args: bool, return_type: ?type, diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 9eaf185119..95d4c919db 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -2472,6 +2472,7 @@ fn CopyPtrAttrs(comptime source: type, comptime size: std.builtin.TypeInfo.Point .is_volatile = info.is_volatile, .is_allowzero = info.is_allowzero, .alignment = info.alignment, + .address_space = info.address_space, .child = child, .sentinel = null, }, @@ -2960,6 +2961,7 @@ fn AlignedSlice(comptime AttributeSource: type, comptime new_alignment: u29) typ .is_volatile = info.is_volatile, .is_allowzero = info.is_allowzero, .alignment = new_alignment, + .address_space = info.address_space, .child = info.child, .sentinel = null, }, diff --git a/lib/std/meta.zig b/lib/std/meta.zig index a1bfacf597..62866bb711 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -235,6 +235,7 @@ pub fn Sentinel(comptime T: type, comptime sentinel_val: Elem(T)) type { .is_const = info.is_const, .is_volatile = info.is_volatile, .alignment = info.alignment, + .address_space = info.address_space, .child = @Type(.{ .Array = .{ .len = array_info.len, @@ -254,6 +255,7 @@ pub fn Sentinel(comptime T: type, comptime sentinel_val: Elem(T)) type { .is_const = info.is_const, .is_volatile = info.is_volatile, .alignment = info.alignment, + .address_space = info.address_space, .child = info.child, .is_allowzero = info.is_allowzero, .sentinel = sentinel_val, @@ -271,6 +273,7 @@ pub fn Sentinel(comptime T: type, comptime sentinel_val: Elem(T)) type { .is_const = ptr_info.is_const, .is_volatile = ptr_info.is_volatile, .alignment = ptr_info.alignment, + .address_space = ptr_info.address_space, .child = ptr_info.child, .is_allowzero = ptr_info.is_allowzero, .sentinel = sentinel_val, diff --git a/lib/std/zig/c_translation.zig b/lib/std/zig/c_translation.zig index bb8a699f69..999572d212 100644 --- a/lib/std/zig/c_translation.zig +++ b/lib/std/zig/c_translation.zig @@ -325,6 +325,7 @@ pub fn FlexibleArrayType(comptime SelfType: type, ElementType: type) type { .is_const = ptr.is_const, .is_volatile = ptr.is_volatile, .alignment = @alignOf(ElementType), + .address_space = .generic, .child = ElementType, .is_allowzero = true, .sentinel = null, diff --git a/src/Sema.zig b/src/Sema.zig index d6e926e604..bf676866a1 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6413,7 +6413,7 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr switch (ty.zigTypeTag()) { .Fn => { - const field_values = try sema.arena.alloc(Value, 6); + const field_values = try sema.arena.alloc(Value, 7); // calling_convention: CallingConvention, field_values[0] = try Value.Tag.enum_field_index.create( sema.arena, @@ -6421,14 +6421,19 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr ); // alignment: comptime_int, field_values[1] = try Value.Tag.int_u64.create(sema.arena, ty.abiAlignment(target)); + // address_space: AddressSpace, + field_values[2] = try Value.Tag.enum_field_index.create( + sema.arena, + @enumToInt(ty.fnAddressSpace()), + ); // is_generic: bool, - field_values[2] = Value.initTag(.bool_false); // TODO - // is_var_args: bool, field_values[3] = Value.initTag(.bool_false); // TODO + // is_var_args: bool, + field_values[4] = Value.initTag(.bool_false); // TODO // return_type: ?type, - field_values[4] = try Value.Tag.ty.create(sema.arena, ty.fnReturnType()); + field_values[5] = try Value.Tag.ty.create(sema.arena, ty.fnReturnType()); // args: []const FnArg, - field_values[5] = Value.initTag(.null_value); // TODO + field_values[6] = Value.initTag(.null_value); // TODO return sema.addConstant( type_info_ty, diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 4004199eb6..13c37fc839 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -86,6 +86,14 @@ enum CallingConvention { CallingConventionSysV }; +// Stage 1 supports only the generic address space +enum AddressSpace { + AddressSpaceGeneric, + AddressSpaceGS, + AddressSpaceFS, + AddressSpaceSS, +}; + // This one corresponds to the builtin.zig enum. enum BuiltinPtrSize { BuiltinPtrSizeOne, diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index 2eb609ef1a..320d8ff9b2 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -1019,6 +1019,16 @@ bool calling_convention_allows_zig_types(CallingConvention cc) { zig_unreachable(); } +const char *address_space_name(AddressSpace as) { + switch (as) { + case AddressSpaceGeneric: return "generic"; + case AddressSpaceGS: return "gs"; + case AddressSpaceFS: return "fs"; + case AddressSpaceSS: return "ss"; + } + zig_unreachable(); +} + ZigType *get_stack_trace_type(CodeGen *g) { if (g->stack_trace_type == nullptr) { g->stack_trace_type = get_builtin_type(g, "StackTrace"); diff --git a/src/stage1/analyze.hpp b/src/stage1/analyze.hpp index 8290ef572c..6d584ff361 100644 --- a/src/stage1/analyze.hpp +++ b/src/stage1/analyze.hpp @@ -242,6 +242,8 @@ Error get_primitive_type(CodeGen *g, Buf *name, ZigType **result); bool calling_convention_allows_zig_types(CallingConvention cc); const char *calling_convention_name(CallingConvention cc); +const char *address_space_name(AddressSpace as); + Error ATTRIBUTE_MUST_USE file_fetch(CodeGen *g, Buf *resolved_path, Buf *contents); void walk_function_params(CodeGen *g, ZigType *fn_type, FnWalk *fn_walk); diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 0604c05c46..a41384cee6 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -16124,7 +16124,7 @@ static Stage1AirInst *ir_analyze_instruction_optional_unwrap_ptr(IrAnalyze *ira, static Stage1AirInst *ir_analyze_instruction_ctz(IrAnalyze *ira, Stage1ZirInstCtz *instruction) { Error err; - + ZigType *int_type = ir_resolve_int_type(ira, instruction->type->child); if (type_is_invalid(int_type)) return ira->codegen->invalid_inst_gen; @@ -16166,7 +16166,7 @@ static Stage1AirInst *ir_analyze_instruction_ctz(IrAnalyze *ira, Stage1ZirInstCt return ira->codegen->invalid_inst_gen; if (val->special == ConstValSpecialUndef) return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_num_lit_int); - + if (is_vector) { ZigType *smallest_vec_type = get_vector_type(ira->codegen, vector_len, smallest_type); Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, smallest_vec_type); @@ -16200,7 +16200,7 @@ static Stage1AirInst *ir_analyze_instruction_ctz(IrAnalyze *ira, Stage1ZirInstCt static Stage1AirInst *ir_analyze_instruction_clz(IrAnalyze *ira, Stage1ZirInstClz *instruction) { Error err; - + ZigType *int_type = ir_resolve_int_type(ira, instruction->type->child); if (type_is_invalid(int_type)) return ira->codegen->invalid_inst_gen; @@ -16242,7 +16242,7 @@ static Stage1AirInst *ir_analyze_instruction_clz(IrAnalyze *ira, Stage1ZirInstCl return ira->codegen->invalid_inst_gen; if (val->special == ConstValSpecialUndef) return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_num_lit_int); - + if (is_vector) { ZigType *smallest_vec_type = get_vector_type(ira->codegen, vector_len, smallest_type); Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, smallest_vec_type); @@ -16276,7 +16276,7 @@ static Stage1AirInst *ir_analyze_instruction_clz(IrAnalyze *ira, Stage1ZirInstCl static Stage1AirInst *ir_analyze_instruction_pop_count(IrAnalyze *ira, Stage1ZirInstPopCount *instruction) { Error err; - + ZigType *int_type = ir_resolve_int_type(ira, instruction->type->child); if (type_is_invalid(int_type)) return ira->codegen->invalid_inst_gen; @@ -16318,7 +16318,7 @@ static Stage1AirInst *ir_analyze_instruction_pop_count(IrAnalyze *ira, Stage1Zir return ira->codegen->invalid_inst_gen; if (val->special == ConstValSpecialUndef) return ir_const_undef(ira, instruction->base.scope, instruction->base.source_node, ira->codegen->builtin_types.entry_num_lit_int); - + if (is_vector) { ZigType *smallest_vec_type = get_vector_type(ira->codegen, vector_len, smallest_type); Stage1AirInst *result = ir_const(ira, instruction->base.scope, instruction->base.source_node, smallest_vec_type); @@ -17904,7 +17904,7 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, Scope *scope, AstNode result->special = ConstValSpecialStatic; result->type = type_info_pointer_type; - ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 7); + ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 8); result->data.x_struct.fields = fields; // size: Size @@ -17939,24 +17939,29 @@ static ZigValue *create_ptr_like_type_info(IrAnalyze *ira, Scope *scope, AstNode lazy_align_of->base.id = LazyValueIdAlignOf; lazy_align_of->target_type = ir_const_type(ira, scope, source_node, attrs_type->data.pointer.child_type); } - // child: type - ensure_field_index(result->type, "child", 4); + // address_space: AddressSpace, + ensure_field_index(result->type, "address_space", 4); fields[4]->special = ConstValSpecialStatic; - fields[4]->type = ira->codegen->builtin_types.entry_type; - fields[4]->data.x_type = attrs_type->data.pointer.child_type; - // is_allowzero: bool - ensure_field_index(result->type, "is_allowzero", 5); + fields[4]->type = get_builtin_type(ira->codegen, "AddressSpace"); + bigint_init_unsigned(&fields[4]->data.x_enum_tag, AddressSpaceGeneric); + // child: type + ensure_field_index(result->type, "child", 5); 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: anytype - ensure_field_index(result->type, "sentinel", 6); + fields[5]->type = ira->codegen->builtin_types.entry_type; + fields[5]->data.x_type = attrs_type->data.pointer.child_type; + // is_allowzero: bool + ensure_field_index(result->type, "is_allowzero", 6); fields[6]->special = ConstValSpecialStatic; + fields[6]->type = ira->codegen->builtin_types.entry_bool; + fields[6]->data.x_bool = attrs_type->data.pointer.allow_zero; + // sentinel: anytype + ensure_field_index(result->type, "sentinel", 7); + fields[7]->special = ConstValSpecialStatic; if (attrs_type->data.pointer.sentinel != nullptr) { - fields[6]->type = get_optional_type(ira->codegen, attrs_type->data.pointer.child_type); - set_optional_payload(fields[6], attrs_type->data.pointer.sentinel); + fields[7]->type = get_optional_type(ira->codegen, attrs_type->data.pointer.child_type); + set_optional_payload(fields[7], attrs_type->data.pointer.sentinel); } else { - fields[6]->type = ira->codegen->builtin_types.entry_null; + fields[7]->type = ira->codegen->builtin_types.entry_null; } return result; @@ -18465,7 +18470,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, Scope *scope, AstNode *sour result->special = ConstValSpecialStatic; result->type = ir_type_info_get_type(ira, "Fn", nullptr); - ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 6); + ZigValue **fields = alloc_const_vals_ptrs(ira->codegen, 7); result->data.x_struct.fields = fields; // calling_convention: TypeInfo.CallingConvention @@ -18478,30 +18483,35 @@ static Error ir_make_type_info_value(IrAnalyze *ira, Scope *scope, AstNode *sour fields[1]->special = ConstValSpecialStatic; fields[1]->type = ira->codegen->builtin_types.entry_num_lit_int; bigint_init_unsigned(&fields[1]->data.x_bigint, get_ptr_align(ira->codegen, type_entry)); - // is_generic: bool - ensure_field_index(result->type, "is_generic", 2); - bool is_generic = type_entry->data.fn.is_generic; + // address_space: AddressSpace + ensure_field_index(result->type, "address_space", 2); fields[2]->special = ConstValSpecialStatic; - fields[2]->type = ira->codegen->builtin_types.entry_bool; - fields[2]->data.x_bool = is_generic; - // is_varargs: bool - ensure_field_index(result->type, "is_var_args", 3); - bool is_varargs = type_entry->data.fn.fn_type_id.is_var_args; + fields[2]->type = get_builtin_type(ira->codegen, "AddressSpace"); + bigint_init_unsigned(&fields[2]->data.x_enum_tag, AddressSpaceGeneric); + // is_generic: bool + ensure_field_index(result->type, "is_generic", 3); + bool is_generic = type_entry->data.fn.is_generic; fields[3]->special = ConstValSpecialStatic; fields[3]->type = ira->codegen->builtin_types.entry_bool; - fields[3]->data.x_bool = is_varargs; - // return_type: ?type - ensure_field_index(result->type, "return_type", 4); + fields[3]->data.x_bool = is_generic; + // is_varargs: bool + ensure_field_index(result->type, "is_var_args", 4); + bool is_varargs = type_entry->data.fn.fn_type_id.is_var_args; fields[4]->special = ConstValSpecialStatic; - fields[4]->type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type); + fields[4]->type = ira->codegen->builtin_types.entry_bool; + fields[4]->data.x_bool = is_varargs; + // return_type: ?type + ensure_field_index(result->type, "return_type", 5); + fields[5]->special = ConstValSpecialStatic; + fields[5]->type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type); if (type_entry->data.fn.fn_type_id.return_type == nullptr) - fields[4]->data.x_optional = nullptr; + fields[5]->data.x_optional = nullptr; else { ZigValue *return_type = ira->codegen->pass1_arena->create(); return_type->special = ConstValSpecialStatic; return_type->type = ira->codegen->builtin_types.entry_type; return_type->data.x_type = type_entry->data.fn.fn_type_id.return_type; - fields[4]->data.x_optional = return_type; + fields[5]->data.x_optional = return_type; } // args: []TypeInfo.FnArg ZigType *type_info_fn_arg_type = ir_type_info_get_type(ira, "FnArg", nullptr); @@ -18516,7 +18526,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, Scope *scope, AstNode *sour fn_arg_array->data.x_array.special = ConstArraySpecialNone; fn_arg_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate(fn_arg_count); - init_const_slice(ira->codegen, fields[5], fn_arg_array, 0, fn_arg_count, false, nullptr); + init_const_slice(ira->codegen, fields[6], fn_arg_array, 0, fn_arg_count, false, nullptr); for (size_t fn_arg_index = 0; fn_arg_index < fn_arg_count; fn_arg_index++) { FnTypeParamInfo *fn_param_info = &type_entry->data.fn.fn_type_id.param_info[fn_arg_index]; @@ -18826,11 +18836,11 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ assert(size_value->type == ir_type_info_get_type(ira, "Size", type_info_pointer_type)); BuiltinPtrSize size_enum_index = (BuiltinPtrSize)bigint_as_u32(&size_value->data.x_enum_tag); PtrLen ptr_len = size_enum_index_to_ptr_len(size_enum_index); - ZigType *elem_type = get_const_field_meta_type(ira, source_node, payload, "child", 4); + ZigType *elem_type = get_const_field_meta_type(ira, source_node, payload, "child", 5); if (type_is_invalid(elem_type)) return ira->codegen->invalid_inst_gen->value->type; ZigValue *sentinel; - if ((err = get_const_field_sentinel(ira, scope, source_node, payload, "sentinel", 6, + if ((err = get_const_field_sentinel(ira, scope, source_node, payload, "sentinel", 7, elem_type, &sentinel))) { return ira->codegen->invalid_inst_gen->value->type; @@ -18845,6 +18855,19 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ if (alignment == nullptr) return ira->codegen->invalid_inst_gen->value->type; + ZigValue *as_value = get_const_field(ira, source_node, payload, "address_space", 4); + if (as_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + assert(as_value->special == ConstValSpecialStatic); + assert(as_value->type == get_builtin_type(ira->codegen, "AddressSpace")); + AddressSpace as = (AddressSpace)bigint_as_u32(&as_value->data.x_enum_tag); + if (as != AddressSpaceGeneric) { + ir_add_error_node(ira, source_node, buf_sprintf( + "address space '%s' not available in stage 1 compiler, must be .generic", + address_space_name(as))); + return ira->codegen->invalid_inst_gen->value->type; + } + bool is_const; if ((err = get_const_field_bool(ira, source_node, payload, "is_const", 1, &is_const))) return ira->codegen->invalid_inst_gen->value->type; @@ -18857,13 +18880,12 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ } bool is_allowzero; - if ((err = get_const_field_bool(ira, source_node, payload, "is_allowzero", 5, + if ((err = get_const_field_bool(ira, source_node, payload, "is_allowzero", 6, &is_allowzero))) { return ira->codegen->invalid_inst_gen->value->type; } - ZigType *ptr_type = get_pointer_to_type_extra2(ira->codegen, elem_type, is_const, @@ -19308,9 +19330,22 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ if (alignment == nullptr) return ira->codegen->invalid_inst_gen->value->type; + ZigValue *as_value = get_const_field(ira, source_node, payload, "address_space", 2); + if (as_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + assert(as_value->special == ConstValSpecialStatic); + assert(as_value->type == get_builtin_type(ira->codegen, "AddressSpace")); + AddressSpace as = (AddressSpace)bigint_as_u32(&as_value->data.x_enum_tag); + if (as != AddressSpaceGeneric) { + ir_add_error_node(ira, source_node, buf_sprintf( + "address space '%s' not available in stage 1 compiler, must be .generic", + address_space_name(as))); + return ira->codegen->invalid_inst_gen->value->type; + } + Error err; bool is_generic; - if ((err = get_const_field_bool(ira, source_node, payload, "is_generic", 2, &is_generic))) + if ((err = get_const_field_bool(ira, source_node, payload, "is_generic", 3, &is_generic))) return ira->codegen->invalid_inst_gen->value->type; if (is_generic) { ir_add_error_node(ira, source_node, buf_sprintf("TypeInfo.Fn.is_generic must be false for @Type")); @@ -19318,20 +19353,20 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ } bool is_var_args; - if ((err = get_const_field_bool(ira, source_node, payload, "is_var_args", 3, &is_var_args))) + if ((err = get_const_field_bool(ira, source_node, payload, "is_var_args", 4, &is_var_args))) return ira->codegen->invalid_inst_gen->value->type; if (is_var_args && cc != CallingConventionC) { ir_add_error_node(ira, source_node, buf_sprintf("varargs functions must have C calling convention")); return ira->codegen->invalid_inst_gen->value->type; } - ZigType *return_type = get_const_field_meta_type_optional(ira, source_node, payload, "return_type", 4); + ZigType *return_type = get_const_field_meta_type_optional(ira, source_node, payload, "return_type", 5); if (return_type == nullptr) { ir_add_error_node(ira, source_node, buf_sprintf("TypeInfo.Fn.return_type must be non-null for @Type")); return ira->codegen->invalid_inst_gen->value->type; } - ZigValue *args_value = get_const_field(ira, source_node, payload, "args", 5); + ZigValue *args_value = get_const_field(ira, source_node, payload, "args", 6); if (args_value == nullptr) return ira->codegen->invalid_inst_gen->value->type; assert(args_value->special == ConstValSpecialStatic); diff --git a/test/behavior/type.zig b/test/behavior/type.zig index 3a56f2171f..cd5d2c3e06 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -137,6 +137,7 @@ test "@Type create slice with null sentinel" { .is_volatile = false, .is_allowzero = false, .alignment = 8, + .address_space = .generic, .child = *i32, .sentinel = null, }, diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 9fd125a775..3e9508be37 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -410,6 +410,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ .Fn = .{ \\ .calling_convention = .Unspecified, \\ .alignment = 0, + \\ .address_space = 0, \\ .is_generic = true, \\ .is_var_args = false, \\ .return_type = u0, @@ -426,6 +427,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ .Fn = .{ \\ .calling_convention = .Unspecified, \\ .alignment = 0, + \\ .address_space = 0, \\ .is_generic = false, \\ .is_var_args = true, \\ .return_type = u0, @@ -442,6 +444,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ .Fn = .{ \\ .calling_convention = .Unspecified, \\ .alignment = 0, + \\ .address_space = 0, \\ .is_generic = false, \\ .is_var_args = false, \\ .return_type = null, @@ -711,6 +714,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ .is_const = false, \\ .is_volatile = false, \\ .alignment = 1, + \\ .address_space = .generic, \\ .child = u8, \\ .is_allowzero = false, \\ .sentinel = 0, From 7956eee1ab126cb6657f7b87e3fcf86794c141b0 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 30 Aug 2021 03:11:22 +0200 Subject: [PATCH 060/160] Address Spaces: Adapt compile error test cases to @Type with address spaces --- test/compile_errors.zig | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 3e9508be37..f2fa20d722 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -410,7 +410,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ .Fn = .{ \\ .calling_convention = .Unspecified, \\ .alignment = 0, - \\ .address_space = 0, + \\ .address_space = .generic, \\ .is_generic = true, \\ .is_var_args = false, \\ .return_type = u0, @@ -427,7 +427,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ .Fn = .{ \\ .calling_convention = .Unspecified, \\ .alignment = 0, - \\ .address_space = 0, + \\ .address_space = .generic, \\ .is_generic = false, \\ .is_var_args = true, \\ .return_type = u0, @@ -444,7 +444,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ .Fn = .{ \\ .calling_convention = .Unspecified, \\ .alignment = 0, - \\ .address_space = 0, + \\ .address_space = .generic, \\ .is_generic = false, \\ .is_var_args = false, \\ .return_type = null, @@ -456,6 +456,23 @@ pub fn addCases(ctx: *TestContext) !void { "tmp.zig:1:20: error: TypeInfo.Fn.return_type must be non-null for @Type", }); + ctx.objErrStage1("@Type(.Fn) with invalid address space ", + \\const Foo = @Type(.{ + \\ .Fn = .{ + \\ .calling_convention = .Unspecified, + \\ .alignment = 0, + \\ .address_space = .fs, + \\ .is_generic = false, + \\ .is_var_args = false, + \\ .return_type = u0, + \\ .args = &[_]@import("std").builtin.TypeInfo.FnArg{}, + \\ }, + \\}); + \\comptime { _ = Foo; } + , &[_][]const u8{ + "tmp.zig:1:20: error: address space 'fs' not available in stage 1 compiler, must be .generic", + }); + ctx.objErrStage1("@Type for union with opaque field", \\const TypeInfo = @import("std").builtin.TypeInfo; \\const Untagged = @Type(.{ @@ -724,6 +741,23 @@ pub fn addCases(ctx: *TestContext) !void { "tmp.zig:2:16: error: sentinels are only allowed on slices and unknown-length pointers", }); + ctx.objErrStage1("@Type(.Pointer) with invalid address space ", + \\export fn entry() void { + \\ _ = @Type(.{ .Pointer = .{ + \\ .size = .One, + \\ .is_const = false, + \\ .is_volatile = false, + \\ .alignment = 1, + \\ .address_space = .gs, + \\ .child = u8, + \\ .is_allowzero = false, + \\ .sentinel = null, + \\ }}); + \\} + , &[_][]const u8{ + "tmp.zig:2:16: error: address space 'gs' not available in stage 1 compiler, must be .generic", + }); + ctx.testErrStage1("helpful return type error message", \\export fn foo() u32 { \\ return error.Ohno; From 90a945b38c44c673e230feb8a6b124f5c8f977a1 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 1 Sep 2021 17:04:42 +0200 Subject: [PATCH 061/160] Address Spaces: Split out stage2 address llvm tests to individual cases This previously caused a test case to crash due to lingering llvm state. --- test/stage2/llvm.zig | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig index e61e7181c6..e1d32aa086 100644 --- a/test/stage2/llvm.zig +++ b/test/stage2/llvm.zig @@ -244,7 +244,7 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.exeUsingLlvmBackend("address space pointer coercions", linux_x64); + var case = ctx.exeUsingLlvmBackend("invalid address space coercion", linux_x64); case.addError( \\fn entry(a: *addrspace(.gs) i32) *i32 { \\ return a; @@ -253,21 +253,30 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{ ":2:12: error: expected *i32, found *addrspace(.gs) i32", }); + } + { + var case = ctx.exeUsingLlvmBackend("pointer keeps address space", linux_x64); case.compiles( \\fn entry(a: *addrspace(.gs) i32) *addrspace(.gs) i32 { \\ return a; \\} \\pub export fn main() void { _ = entry; } ); + } + { + var case = ctx.exeUsingLlvmBackend("pointer to explicit generic address space coerces to implicit pointer", linux_x64); case.compiles( \\fn entry(a: *addrspace(.generic) i32) *i32 { \\ return a; \\} \\pub export fn main() void { _ = entry; } ); + } + { + var case = ctx.exeUsingLlvmBackend("pointers with different address spaces", linux_x64); case.addError( \\fn entry(a: *addrspace(.gs) i32) *addrspace(.fs) i32 { \\ return a; @@ -276,7 +285,10 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{ ":2:12: error: expected *addrspace(.fs) i32, found *addrspace(.gs) i32", }); + } + { + var case = ctx.exeUsingLlvmBackend("pointers with different address spaces", linux_x64); case.addError( \\fn entry(a: ?*addrspace(.gs) i32) *i32 { \\ return a.?; @@ -285,7 +297,10 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{ ":2:13: error: expected *i32, found *addrspace(.gs) i32", }); + } + { + var case = ctx.exeUsingLlvmBackend("invalid pointer keeps address space when taking address of dereference", linux_x64); case.addError( \\fn entry(a: *addrspace(.gs) i32) *i32 { \\ return &a.*; @@ -294,7 +309,10 @@ pub fn addCases(ctx: *TestContext) !void { , &[_][]const u8{ ":2:12: error: expected *i32, found *addrspace(.gs) i32", }); + } + { + var case = ctx.exeUsingLlvmBackend("pointer keeps address space when taking address of dereference", linux_x64); case.compiles( \\fn entry(a: *addrspace(.gs) i32) *addrspace(.gs) i32 { \\ return &a.*; From 13b917148e97560760eb13cd0e4a0b7365739f64 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 1 Sep 2021 17:33:45 +0200 Subject: [PATCH 062/160] Address Spaces: basic system to check for validity. Validity checks are also based on context; whether the entity being validated is a mutable/constant value, a pointer (that is ascripted with an addrspace attribute) or a function with an addrspace attribute. Error messages are relatively simple for now. --- src/Module.zig | 15 +++++++++---- src/Sema.zig | 61 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 1f9b5abcb9..2313480aeb 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3213,11 +3213,18 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val; }; const address_space = blk: { - const addrspace_ref = decl.zirAddrspaceRef(); - if (addrspace_ref == .none) break :blk .generic; - const addrspace_tv = try sema.resolveInstConst(&block_scope, src, addrspace_ref); - break :blk addrspace_tv.val.toEnum(std.builtin.AddressSpace); + const addrspace_ctx: Sema.AddressSpaceContext = switch (decl_tv.val.tag()) { + .function, .extern_fn => .function, + .variable => .variable, + else => .constant, + }; + + break :blk switch (decl.zirAddrspaceRef()) { + .none => .generic, + else => |addrspace_ref| try sema.analyzeAddrspace(&block_scope, src, addrspace_ref, addrspace_ctx), + }; }; + // Note this resolves the type of the Decl, not the value; if this Decl // is a struct, for example, this resolves `type` (which needs no resolution), // not the struct itself. diff --git a/src/Sema.zig b/src/Sema.zig index bf676866a1..7f2b0e855e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6932,8 +6932,7 @@ fn zirPtrType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const address_space = if (inst_data.flags.has_addrspace) blk: { const ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_i]); extra_i += 1; - const addrspace_tv = try sema.resolveInstConst(block, .unneeded, ref); - break :blk addrspace_tv.val.toEnum(std.builtin.AddressSpace); + break :blk try sema.analyzeAddrspace(block, .unneeded, ref, .pointer); } else .generic; const bit_start = if (inst_data.flags.has_bit_range) blk: { @@ -8092,8 +8091,7 @@ fn zirFuncExtended( const address_space: std.builtin.AddressSpace = if (small.has_addrspace) blk: { const addrspace_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const addrspace_tv = try sema.resolveInstConst(block, addrspace_src, addrspace_ref); - break :blk addrspace_tv.val.toEnum(std.builtin.AddressSpace); + break :blk try sema.analyzeAddrspace(block, addrspace_src, addrspace_ref, .function); } else .generic; const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; @@ -10973,3 +10971,58 @@ fn analyzeComptimeAlloc( .decl = decl, })); } + +/// The places where a user can specify an address space attribute +pub const AddressSpaceContext = enum { + /// A function is specificed to be placed in a certain address space. + function, + + /// A (global) variable is specified to be placed in a certain address space. + /// In contrast to .constant, these values (and thus the address space they will be + /// placed in) are required to be mutable. + variable, + + /// A (global) constant value is specified to be placed in a certain address space. + /// In contrast to .variable, values placed in this address space are not required to be mutable. + constant, + + /// A pointer is ascripted to point into a certian address space. + pointer, +}; + +pub fn analyzeAddrspace( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + zir_ref: Zir.Inst.Ref, + ctx: AddressSpaceContext, +) !std.builtin.AddressSpace { + const addrspace_tv = try sema.resolveInstConst(block, src, zir_ref); + const address_space = addrspace_tv.val.toEnum(std.builtin.AddressSpace); + const target = sema.mod.getTarget(); + const arch = target.cpu.arch; + + const supported = switch (address_space) { + .generic => true, + .gs, .fs, .ss => (arch == .i386 or arch == .x86_64) and ctx == .pointer, + }; + + if (!supported) { + // TODO error messages could be made more elaborate here + const entity = switch (ctx) { + .function => "functions", + .variable => "mutable values", + .constant => "constant values", + .pointer => "pointers", + }; + + return sema.mod.fail( + &block.base, + src, + "{s} with address space '{s}' are not supported on {s}", + .{ entity, @tagName(address_space), arch.genericName() }, + ); + } + + return address_space; +} From e4ac063297fc9210ae3a97fa370ea8c6c216ec43 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 3 Sep 2021 04:36:56 +0200 Subject: [PATCH 063/160] Address Spaces: Restructure llvmAddressSpace a bit --- src/codegen/llvm.zig | 19 ++++---- src/codegen/llvm/bindings.zig | 81 ++++++++++++++++++----------------- 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 8b7282160e..ce79d43fac 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -583,18 +583,15 @@ pub const DeclGen = struct { fn llvmAddressSpace(self: DeclGen, address_space: std.builtin.AddressSpace) c_uint { const target = self.module.getTarget(); - return switch (address_space) { - .generic => llvm.address_space.default, - .gs => switch (target.cpu.arch) { - .i386, .x86_64 => llvm.address_space.x86.gs, - else => unreachable, + return switch (target.cpu.arch) { + .i386, .x86_64 => switch (address_space) { + .generic => llvm.address_space.default, + .gs => llvm.address_space.x86.gs, + .fs => llvm.address_space.x86.fs, + .ss => llvm.address_space.x86.ss, }, - .fs => switch (target.cpu.arch) { - .i386, .x86_64 => llvm.address_space.x86.fs, - else => unreachable, - }, - .ss => switch (target.cpu.arch) { - .i386, .x86_64 => llvm.address_space.x86.ss, + else => switch (address_space) { + .generic => llvm.address_space.default, else => unreachable, }, }; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index e50589dee1..ebb84cb05b 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -981,62 +981,63 @@ pub const TypeKind = enum(c_int) { }; pub const address_space = struct { - pub const default = 0; + pub const default: c_uint = 0; // See llvm/lib/Target/X86/X86.h pub const x86_64 = x86; pub const x86 = struct { - pub const gs = 256; - pub const fs = 257; - pub const ss = 258; + pub const gs: c_uint = 256; + pub const fs: c_uint = 257; + pub const ss: c_uint = 258; - pub const ptr32_sptr = 270; - pub const ptr32_uptr = 271; - pub const ptr64 = 272; + pub const ptr32_sptr: c_uint = 270; + pub const ptr32_uptr: c_uint = 271; + pub const ptr64: c_uint = 272; }; // See llvm/lib/Target/AVR/AVR.h pub const avr = struct { - pub const data_memory = 0; - pub const program_memory = 1; + pub const data_memory: c_uint = 0; + pub const program_memory: c_uint = 1; }; // See llvm/lib/Target/NVPTX/NVPTX.h pub const nvptx = struct { - pub const generic = 0; - pub const global = 1; - pub const constant = 2; - pub const shared = 3; - pub const param = 4; - pub const local = 5; + pub const generic: c_uint = 0; + pub const global: c_uint = 1; + pub const constant: c_uint = 2; + pub const shared: c_uint = 3; + pub const param: c_uint = 4; + pub const local: c_uint = 5; }; // See llvm/lib/Target/AMDGPU/AMDGPU.h pub const amdgpu = struct { - pub const flat = 0; - pub const global = 1; - pub const region = 2; - pub const local = 3; - pub const constant = 4; - pub const constant_32bit = 6; - pub const buffer_fat_pointer = 7; - pub const param_d = 6; - pub const param_i = 7; - pub const constant_buffer_0 = 8; - pub const constant_buffer_1 = 9; - pub const constant_buffer_2 = 10; - pub const constant_buffer_3 = 11; - pub const constant_buffer_4 = 12; - pub const constant_buffer_5 = 13; - pub const constant_buffer_6 = 14; - pub const constant_buffer_7 = 15; - pub const constant_buffer_8 = 16; - pub const constant_buffer_9 = 17; - pub const constant_buffer_10 = 18; - pub const constant_buffer_11 = 19; - pub const constant_buffer_12 = 20; - pub const constant_buffer_13 = 21; - pub const constant_buffer_14 = 22; - pub const constant_buffer_15 = 23; + pub const flat: c_uint = 0; + pub const global: c_uint = 1; + pub const region: c_uint = 2; + pub const local: c_uint = 3; + pub const constant: c_uint = 4; + pub const private: c_uint = 5; + pub const constant_32bit: c_uint = 6; + pub const buffer_fat_pointer: c_uint = 7; + pub const param_d: c_uint = 6; + pub const param_i: c_uint = 7; + pub const constant_buffer_0: c_uint = 8; + pub const constant_buffer_1: c_uint = 9; + pub const constant_buffer_2: c_uint = 10; + pub const constant_buffer_3: c_uint = 11; + pub const constant_buffer_4: c_uint = 12; + pub const constant_buffer_5: c_uint = 13; + pub const constant_buffer_6: c_uint = 14; + pub const constant_buffer_7: c_uint = 15; + pub const constant_buffer_8: c_uint = 16; + pub const constant_buffer_9: c_uint = 17; + pub const constant_buffer_10: c_uint = 18; + pub const constant_buffer_11: c_uint = 19; + pub const constant_buffer_12: c_uint = 20; + pub const constant_buffer_13: c_uint = 21; + pub const constant_buffer_14: c_uint = 22; + pub const constant_buffer_15: c_uint = 23; }; }; From 5a142dfa5651ec21792de211a6e9341c31ab4dbb Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Fri, 3 Sep 2021 04:37:07 +0200 Subject: [PATCH 064/160] Address Spaces: LLVM F segment address space test --- test/stage2/llvm.zig | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/stage2/llvm.zig b/test/stage2/llvm.zig index e1d32aa086..820768efe3 100644 --- a/test/stage2/llvm.zig +++ b/test/stage2/llvm.zig @@ -372,4 +372,54 @@ pub fn addCases(ctx: *TestContext) !void { \\pub export fn main() void { _ = entry; } ); } + + { + var case = ctx.exeUsingLlvmBackend("f segment address space reading and writing", linux_x64); + case.addCompareOutput( + \\fn assert(ok: bool) void { + \\ if (!ok) unreachable; + \\} + \\ + \\fn setFs(value: c_ulong) void { + \\ asm volatile ( + \\ \\syscall + \\ : + \\ : [number] "{rax}" (158), + \\ [code] "{rdi}" (0x1002), + \\ [val] "{rsi}" (value), + \\ : "rcx", "r11", "memory" + \\ ); + \\} + \\ + \\fn getFs() c_ulong { + \\ var result: c_ulong = undefined; + \\ asm volatile ( + \\ \\syscall + \\ : + \\ : [number] "{rax}" (158), + \\ [code] "{rdi}" (0x1003), + \\ [ptr] "{rsi}" (@ptrToInt(&result)), + \\ : "rcx", "r11", "memory" + \\ ); + \\ return result; + \\} + \\ + \\var test_value: u64 = 12345; + \\ + \\pub export fn main() c_int { + \\ const orig_fs = getFs(); + \\ + \\ setFs(@ptrToInt(&test_value)); + \\ assert(getFs() == @ptrToInt(&test_value)); + \\ + \\ var test_ptr = @intToPtr(*allowzero addrspace(.fs) u64, 0); + \\ assert(test_ptr.* == 12345); + \\ test_ptr.* = 98765; + \\ assert(test_value == 98765); + \\ + \\ setFs(orig_fs); + \\ return 0; + \\} + , ""); + } } From 95e83afa98c5af89c6e5f40d559eb54c6c31a54e Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 6 Sep 2021 00:29:04 +0200 Subject: [PATCH 065/160] Address Spaces: Yeet address space on function prototypes This is a property which solely belongs to pointers to functions, not to the functions themselves. This cannot be properly represented by stage 2 at the moment, as type with zigTypeTag() == .Fn is overloaded for for function pointers and function prototypes. --- lib/std/builtin.zig | 1 - src/AstGen.zig | 18 ++---- src/Module.zig | 116 +++++++++++++++++++------------------- src/Sema.zig | 121 +++++++++++++++++++++++++++------------- src/Zir.zig | 15 +---- src/codegen/llvm.zig | 3 +- src/stage1/ir.cpp | 54 ++++++------------ src/target.zig | 18 ++++++ src/type.zig | 23 -------- test/compile_errors.zig | 20 ------- 10 files changed, 180 insertions(+), 209 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 11273eedeb..f01e7605ab 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -365,7 +365,6 @@ pub const TypeInfo = union(enum) { pub const Fn = struct { calling_convention: CallingConvention, alignment: comptime_int, - address_space: AddressSpace, is_generic: bool, is_var_args: bool, return_type: ?type, diff --git a/src/AstGen.zig b/src/AstGen.zig index 2e4671d92e..f1eabe4c0c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1117,9 +1117,9 @@ fn fnProtoExpr( break :inst try expr(gz, scope, align_rl, fn_proto.ast.align_expr); }; - const addrspace_inst: Zir.Inst.Ref = if (fn_proto.ast.addrspace_expr == 0) .none else inst: { - break :inst try expr(gz, scope, .{ .ty = .address_space_type }, fn_proto.ast.addrspace_expr); - }; + if (fn_proto.ast.addrspace_expr != 0) { + return astgen.failNode(fn_proto.ast.addrspace_expr, "addrspace not allowed on function prototypes", .{}); + } if (fn_proto.ast.section_expr != 0) { return astgen.failNode(fn_proto.ast.section_expr, "linksection not allowed on function prototypes", .{}); @@ -1153,7 +1153,6 @@ fn fnProtoExpr( .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = align_inst, - .addrspace_inst = addrspace_inst, .lib_name = 0, .is_var_args = is_var_args, .is_inferred_error = false, @@ -3089,7 +3088,6 @@ fn fnDecl( .body = &[0]Zir.Inst.Index{}, .cc = cc, .align_inst = .none, // passed in the per-decl data - .addrspace_inst = .none, // passed in the per-decl data .lib_name = lib_name, .is_var_args = is_var_args, .is_inferred_error = false, @@ -3129,7 +3127,6 @@ fn fnDecl( .body = fn_gz.instructions.items, .cc = cc, .align_inst = .none, // passed in the per-decl data - .addrspace_inst = .none, // passed in the per-decl data .lib_name = lib_name, .is_var_args = is_var_args, .is_inferred_error = is_inferred_error, @@ -3481,7 +3478,6 @@ fn testDecl( .body = fn_block.instructions.items, .cc = .none, .align_inst = .none, - .addrspace_inst = .none, .lib_name = 0, .is_var_args = false, .is_inferred_error = true, @@ -9217,7 +9213,6 @@ const GenZir = struct { ret_br: Zir.Inst.Index, cc: Zir.Inst.Ref, align_inst: Zir.Inst.Ref, - addrspace_inst: Zir.Inst.Ref, lib_name: u32, is_var_args: bool, is_inferred_error: bool, @@ -9261,7 +9256,7 @@ const GenZir = struct { if (args.cc != .none or args.lib_name != 0 or args.is_var_args or args.is_test or args.align_inst != .none or - args.addrspace_inst != .none or args.is_extern) + args.is_extern) { try astgen.extra.ensureUnusedCapacity( gpa, @@ -9269,7 +9264,6 @@ const GenZir = struct { args.ret_ty.len + args.body.len + src_locs.len + @boolToInt(args.lib_name != 0) + @boolToInt(args.align_inst != .none) + - @boolToInt(args.addrspace_inst != .none) + @boolToInt(args.cc != .none), ); const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedFunc{ @@ -9287,9 +9281,6 @@ const GenZir = struct { if (args.align_inst != .none) { astgen.extra.appendAssumeCapacity(@enumToInt(args.align_inst)); } - if (args.addrspace_inst != .none) { - astgen.extra.appendAssumeCapacity(@enumToInt(args.addrspace_inst)); - } astgen.extra.appendSliceAssumeCapacity(args.ret_ty); astgen.extra.appendSliceAssumeCapacity(args.body); astgen.extra.appendSliceAssumeCapacity(src_locs); @@ -9308,7 +9299,6 @@ const GenZir = struct { .has_lib_name = args.lib_name != 0, .has_cc = args.cc != .none, .has_align = args.align_inst != .none, - .has_addrspace = args.addrspace_inst != .none, .is_test = args.is_test, .is_extern = args.is_extern, }), diff --git a/src/Module.zig b/src/Module.zig index 2313480aeb..500c34bcb0 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3220,7 +3220,12 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { }; break :blk switch (decl.zirAddrspaceRef()) { - .none => .generic, + .none => switch (addrspace_ctx) { + .function => target_util.defaultAddressSpace(sema.mod.getTarget(), .function), + .variable => target_util.defaultAddressSpace(sema.mod.getTarget(), .global_mutable), + .constant => target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), + else => unreachable, + }, else => |addrspace_ref| try sema.analyzeAddrspace(&block_scope, src, addrspace_ref, addrspace_ctx), }; }; @@ -4359,26 +4364,21 @@ pub fn simplePtrType( elem_ty: Type, mutable: bool, size: std.builtin.TypeInfo.Pointer.Size, + @"addrspace": std.builtin.AddressSpace, ) Allocator.Error!Type { - if (!mutable and size == .Slice and elem_ty.eql(Type.initTag(.u8))) { - return Type.initTag(.const_slice_u8); - } - // TODO stage1 type inference bug - const T = Type.Tag; - - const type_payload = try arena.create(Type.Payload.ElemType); - type_payload.* = .{ - .base = .{ - .tag = switch (size) { - .One => if (mutable) T.single_mut_pointer else T.single_const_pointer, - .Many => if (mutable) T.many_mut_pointer else T.many_const_pointer, - .C => if (mutable) T.c_mut_pointer else T.c_const_pointer, - .Slice => if (mutable) T.mut_slice else T.const_slice, - }, - }, - .data = elem_ty, - }; - return Type.initPayload(&type_payload.base); + return ptrType( + arena, + elem_ty, + null, + 0, + @"addrspace", + 0, + 0, + mutable, + false, + false, + size, + ); } pub fn ptrType( @@ -4396,47 +4396,43 @@ pub fn ptrType( ) Allocator.Error!Type { assert(host_size == 0 or bit_offset < host_size * 8); - // TODO check if type can be represented by simplePtrType - return Type.Tag.pointer.create(arena, .{ - .pointee_type = elem_ty, - .sentinel = sentinel, - .@"align" = @"align", - .@"addrspace" = @"addrspace", - .bit_offset = bit_offset, - .host_size = host_size, - .@"allowzero" = @"allowzero", - .mutable = mutable, - .@"volatile" = @"volatile", - .size = size, - }); -} - -/// Create a pointer type with an explicit address space. This function might return results -/// of either simplePtrType or ptrType, depending on the address space. -/// TODO(Snektron) unify ptrType functions. -pub fn simplePtrTypeWithAddressSpace( - arena: *Allocator, - elem_ty: Type, - mutable: bool, - size: std.builtin.TypeInfo.Pointer.Size, - address_space: std.builtin.AddressSpace, -) Allocator.Error!Type { - switch (address_space) { - .generic => return simplePtrType(arena, elem_ty, mutable, size), - else => return ptrType( - arena, - elem_ty, - null, - 0, - address_space, - 0, - 0, - mutable, - false, - false, - size, - ), + if (sentinel != null or @"align" != 0 or @"addrspace" != .generic or + bit_offset != 0 or host_size != 0 or @"allowzero" or @"volatile") + { + return Type.Tag.pointer.create(arena, .{ + .pointee_type = elem_ty, + .sentinel = sentinel, + .@"align" = @"align", + .@"addrspace" = @"addrspace", + .bit_offset = bit_offset, + .host_size = host_size, + .@"allowzero" = @"allowzero", + .mutable = mutable, + .@"volatile" = @"volatile", + .size = size, + }); } + + if (!mutable and size == .Slice and elem_ty.eql(Type.initTag(.u8))) { + return Type.initTag(.const_slice_u8); + } + + // TODO stage1 type inference bug + const T = Type.Tag; + + const type_payload = try arena.create(Type.Payload.ElemType); + type_payload.* = .{ + .base = .{ + .tag = switch (size) { + .One => if (mutable) T.single_mut_pointer else T.single_const_pointer, + .Many => if (mutable) T.many_mut_pointer else T.many_const_pointer, + .C => if (mutable) T.c_mut_pointer else T.c_const_pointer, + .Slice => if (mutable) T.mut_slice else T.const_slice, + }, + }, + .data = elem_ty, + }; + return Type.initPayload(&type_payload.base); } pub fn optionalType(arena: *Allocator, child_type: Type) Allocator.Error!Type { diff --git a/src/Sema.zig b/src/Sema.zig index 7f2b0e855e..f781138df5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1373,7 +1373,13 @@ fn zirRetPtr( return sema.analyzeComptimeAlloc(block, sema.fn_ret_ty); } - const ptr_type = try Module.simplePtrType(sema.arena, sema.fn_ret_ty, true, .One); + const ptr_type = try Module.simplePtrType( + sema.arena, + sema.fn_ret_ty, + true, + .One, + target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + ); return block.addTy(.alloc, ptr_type); } @@ -1521,7 +1527,13 @@ fn zirAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node }; const var_decl_src = inst_data.src(); const var_type = try sema.resolveType(block, ty_src, inst_data.operand); - const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One); + const ptr_type = try Module.simplePtrType( + sema.arena, + var_type, + true, + .One, + target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + ); try sema.requireRuntimeBlock(block, var_decl_src); return block.addTy(.alloc, ptr_type); } @@ -1538,7 +1550,13 @@ fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.analyzeComptimeAlloc(block, var_type); } try sema.validateVarType(block, ty_src, var_type); - const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One); + const ptr_type = try Module.simplePtrType( + sema.arena, + var_type, + true, + .One, + target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + ); try sema.requireRuntimeBlock(block, var_decl_src); return block.addTy(.alloc, ptr_type); } @@ -1598,7 +1616,13 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde try sema.mod.declareDeclDependency(sema.owner_decl, decl); const final_elem_ty = try decl.ty.copy(sema.arena); - const final_ptr_ty = try Module.simplePtrType(sema.arena, final_elem_ty, true, .One); + const final_ptr_ty = try Module.simplePtrType( + sema.arena, + final_elem_ty, + true, + .One, + target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + ); const final_ptr_ty_inst = try sema.addType(final_ptr_ty); sema.air_instructions.items(.data)[ptr_inst].ty_pl.ty = final_ptr_ty_inst; @@ -1620,7 +1644,13 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde try sema.validateVarType(block, ty_src, final_elem_ty); } // Change it to a normal alloc. - const final_ptr_ty = try Module.simplePtrType(sema.arena, final_elem_ty, true, .One); + const final_ptr_ty = try Module.simplePtrType( + sema.arena, + final_elem_ty, + true, + .One, + target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + ); sema.air_instructions.set(ptr_inst, .{ .tag = .alloc, .data = .{ .ty = final_ptr_ty }, @@ -1774,7 +1804,14 @@ fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co } const ptr = sema.resolveInst(bin_inst.lhs); const value = sema.resolveInst(bin_inst.rhs); - const ptr_ty = try Module.simplePtrType(sema.arena, sema.typeOf(value), true, .One); + const ptr_ty = try Module.simplePtrType( + sema.arena, + sema.typeOf(value), + true, + .One, + // TODO figure out which address space is appropriate here + target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + ); // TODO detect when this store should be done at compile-time. For example, // if expressions should force it when the condition is compile-time known. const src: LazySrcLoc = .unneeded; @@ -1821,7 +1858,14 @@ fn zirStoreToInferredPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) // for the inferred allocation. try inferred_alloc.data.stored_inst_list.append(sema.arena, operand); // Create a runtime bitcast instruction with exactly the type the pointer wants. - const ptr_ty = try Module.simplePtrType(sema.arena, operand_ty, true, .One); + const ptr_ty = try Module.simplePtrType( + sema.arena, + operand_ty, + true, + .One, + // TODO figure out which address space is appropriate here + target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + ); const bitcasted_ptr = try block.addTyOp(.bitcast, ptr_ty, ptr); return sema.storePtr(block, src, bitcasted_ptr, operand); } @@ -3658,7 +3702,7 @@ fn zirOptionalPayloadPtr( } const child_type = try opt_type.optionalChildAlloc(sema.arena); - const child_pointer = try Module.simplePtrTypeWithAddressSpace( + const child_pointer = try Module.simplePtrType( sema.arena, child_type, !optional_ptr_ty.isConstPtr(), @@ -3779,7 +3823,7 @@ fn zirErrUnionPayloadPtr( return sema.mod.fail(&block.base, src, "expected error union type, found {}", .{operand_ty.elemType()}); const payload_ty = operand_ty.elemType().errorUnionPayload(); - const operand_pointer_ty = try Module.simplePtrTypeWithAddressSpace( + const operand_pointer_ty = try Module.simplePtrType( sema.arena, payload_ty, !operand_ty.isConstPtr(), @@ -3907,7 +3951,6 @@ fn zirFunc( ret_ty_body, cc, Value.initTag(.null_value), - .generic, false, inferred_error_set, false, @@ -3924,7 +3967,6 @@ fn funcCommon( ret_ty_body: []const Zir.Inst.Index, cc: std.builtin.CallingConvention, align_val: Value, - address_space: std.builtin.AddressSpace, var_args: bool, inferred_error_set: bool, is_extern: bool, @@ -3982,7 +4024,7 @@ fn funcCommon( // Hot path for some common function types. // TODO can we eliminate some of these Type tag values? seems unnecessarily complicated. if (!is_generic and block.params.items.len == 0 and !var_args and - align_val.tag() == .null_value and !inferred_error_set and address_space == .generic) + align_val.tag() == .null_value and !inferred_error_set) { if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) { break :fn_ty Type.initTag(.fn_noreturn_no_args); @@ -4034,7 +4076,6 @@ fn funcCommon( .comptime_params = comptime_params.ptr, .return_type = return_type, .cc = cc, - .@"addrspace" = address_space, .is_var_args = var_args, .is_generic = is_generic, }); @@ -6413,7 +6454,7 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr switch (ty.zigTypeTag()) { .Fn => { - const field_values = try sema.arena.alloc(Value, 7); + const field_values = try sema.arena.alloc(Value, 6); // calling_convention: CallingConvention, field_values[0] = try Value.Tag.enum_field_index.create( sema.arena, @@ -6421,19 +6462,14 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr ); // alignment: comptime_int, field_values[1] = try Value.Tag.int_u64.create(sema.arena, ty.abiAlignment(target)); - // address_space: AddressSpace, - field_values[2] = try Value.Tag.enum_field_index.create( - sema.arena, - @enumToInt(ty.fnAddressSpace()), - ); // is_generic: bool, - field_values[3] = Value.initTag(.bool_false); // TODO + field_values[2] = Value.initTag(.bool_false); // TODO // is_var_args: bool, - field_values[4] = Value.initTag(.bool_false); // TODO + field_values[3] = Value.initTag(.bool_false); // TODO // return_type: ?type, - field_values[5] = try Value.Tag.ty.create(sema.arena, ty.fnReturnType()); + field_values[4] = try Value.Tag.ty.create(sema.arena, ty.fnReturnType()); // args: []const FnArg, - field_values[6] = Value.initTag(.null_value); // TODO + field_values[5] = Value.initTag(.null_value); // TODO return sema.addConstant( type_info_ty, @@ -8063,7 +8099,6 @@ fn zirFuncExtended( const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = extra.data.src_node }; const align_src: LazySrcLoc = src; // TODO add a LazySrcLoc that points at align - const addrspace_src: LazySrcLoc = src; // TODO(Snektron) add a LazySrcLoc that points at addrspace const small = @bitCast(Zir.Inst.ExtendedFunc.Small, extended.small); var extra_index: usize = extra.end; @@ -8088,12 +8123,6 @@ fn zirFuncExtended( break :blk align_tv.val; } else Value.initTag(.null_value); - const address_space: std.builtin.AddressSpace = if (small.has_addrspace) blk: { - const addrspace_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); - extra_index += 1; - break :blk try sema.analyzeAddrspace(block, addrspace_src, addrspace_ref, .function); - } else .generic; - const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; extra_index += ret_ty_body.len; @@ -8116,7 +8145,6 @@ fn zirFuncExtended( ret_ty_body, cc, align_val, - address_space, is_var_args, is_inferred_error, is_extern, @@ -8309,7 +8337,13 @@ fn panicWithMsg( const panic_fn = try sema.getBuiltin(block, src, "panic"); const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace"); const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty); - const ptr_stack_trace_ty = try Module.simplePtrType(arena, stack_trace_ty, true, .One); + const ptr_stack_trace_ty = try Module.simplePtrType( + arena, + stack_trace_ty, + true, + .One, + target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), // TODO might need a place that is more dynamic + ); const null_stack_trace = try sema.addConstant( try Module.optionalType(arena, ptr_stack_trace_ty), Value.initTag(.null_value), @@ -8788,7 +8822,7 @@ fn structFieldPtr( const field_index = struct_obj.fields.getIndex(field_name) orelse return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); const field = struct_obj.fields.values()[field_index]; - const ptr_field_ty = try Module.simplePtrTypeWithAddressSpace( + const ptr_field_ty = try Module.simplePtrType( arena, field.ty, struct_ptr_ty.ptrIsMutable(), @@ -8893,7 +8927,7 @@ fn unionFieldPtr( return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name); const field = union_obj.fields.values()[field_index]; - const ptr_field_ty = try Module.simplePtrTypeWithAddressSpace( + const ptr_field_ty = try Module.simplePtrType( arena, field.ty, union_ptr_ty.ptrIsMutable(), @@ -9075,7 +9109,7 @@ fn elemPtrArray( ) CompileError!Air.Inst.Ref { const array_ptr_ty = sema.typeOf(array_ptr); const pointee_type = array_ptr_ty.elemType().elemType(); - const result_ty = try Module.simplePtrTypeWithAddressSpace( + const result_ty = try Module.simplePtrType( sema.arena, pointee_type, array_ptr_ty.ptrIsMutable(), @@ -9581,11 +9615,11 @@ fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { const decl_tv = try decl.typedValue(); if (decl_tv.val.castTag(.variable)) |payload| { const variable = payload.data; - const ty = try Module.simplePtrTypeWithAddressSpace(sema.arena, decl_tv.ty, variable.is_mutable, .One, decl.@"addrspace"); + const ty = try Module.simplePtrType(sema.arena, decl_tv.ty, variable.is_mutable, .One, decl.@"addrspace"); return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl)); } return sema.addConstant( - try Module.simplePtrTypeWithAddressSpace(sema.arena, decl_tv.ty, false, .One, decl.@"addrspace"), + try Module.simplePtrType(sema.arena, decl_tv.ty, false, .One, decl.@"addrspace"), try Value.Tag.decl_ref.create(sema.arena, decl), ); } @@ -9608,8 +9642,9 @@ fn analyzeRef( } try sema.requireRuntimeBlock(block, src); - const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); - const mut_ptr_type = try Module.simplePtrType(sema.arena, operand_ty, true, .One); + const address_space = target_util.defaultAddressSpace(sema.mod.getTarget(), .local); + const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One, address_space); + const mut_ptr_type = try Module.simplePtrType(sema.arena, operand_ty, true, .One, address_space); const alloc = try block.addTy(.alloc, mut_ptr_type); try sema.storePtr(block, src, alloc, operand); @@ -10955,7 +10990,13 @@ fn analyzeComptimeAlloc( block: *Scope.Block, var_type: Type, ) CompileError!Air.Inst.Ref { - const ptr_type = try Module.simplePtrType(sema.arena, var_type, true, .One); + const ptr_type = try Module.simplePtrType( + sema.arena, + var_type, + true, + .One, + target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), + ); var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); diff --git a/src/Zir.zig b/src/Zir.zig index 50c53e5485..28fa92d4a0 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2309,7 +2309,6 @@ pub const Inst = struct { /// 0. lib_name: u32, // null terminated string index, if has_lib_name is set /// 1. cc: Ref, // if has_cc is set /// 2. align: Ref, // if has_align is set - /// 3. addrspace: Ref, // if has_addrspace is set /// 3. return_type: Index // for each ret_body_len /// 4. body: Index // for each body_len /// 5. src_locs: Func.SrcLocs // if body_len != 0 @@ -2327,10 +2326,9 @@ pub const Inst = struct { has_lib_name: bool, has_cc: bool, has_align: bool, - has_addrspace: bool, is_test: bool, is_extern: bool, - _: u8 = undefined, + _: u9 = undefined, }; }; @@ -4483,7 +4481,6 @@ const Writer = struct { false, .none, .none, - .none, body, src, src_locs, @@ -4512,11 +4509,6 @@ const Writer = struct { extra_index += 1; break :blk align_inst; }; - const addrspace_inst: Inst.Ref = if (!small.has_addrspace) .none else blk: { - const addrspace_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - break :blk addrspace_inst; - }; const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; extra_index += ret_ty_body.len; @@ -4536,7 +4528,6 @@ const Writer = struct { small.is_extern, cc, align_inst, - addrspace_inst, body, src, src_locs, @@ -4619,7 +4610,6 @@ const Writer = struct { is_extern: bool, cc: Inst.Ref, align_inst: Inst.Ref, - addrspace_inst: Inst.Ref, body: []const Inst.Index, src: LazySrcLoc, src_locs: Zir.Inst.Func.SrcLocs, @@ -4637,7 +4627,6 @@ const Writer = struct { try self.writeOptionalInstRef(stream, ", cc=", cc); try self.writeOptionalInstRef(stream, ", align=", align_inst); - try self.writeOptionalInstRef(stream, ", addrspace=", addrspace_inst); try self.writeFlag(stream, ", vargs", var_args); try self.writeFlag(stream, ", extern", is_extern); try self.writeFlag(stream, ", inferror", inferred_error_set); @@ -4915,7 +4904,6 @@ fn findDeclsInner( extra_index += @boolToInt(small.has_lib_name); extra_index += @boolToInt(small.has_cc); extra_index += @boolToInt(small.has_align); - extra_index += @boolToInt(small.has_addrspace); const body = zir.extra[extra_index..][0..extra.data.body_len]; return zir.findDeclsBody(list, body); }, @@ -5119,7 +5107,6 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { extra_index += @boolToInt(small.has_lib_name); extra_index += @boolToInt(small.has_cc); extra_index += @boolToInt(small.has_align); - extra_index += @boolToInt(small.has_addrspace); const ret_ty_body = zir.extra[extra_index..][0..extra.data.ret_body_len]; extra_index += ret_ty_body.len; const body = zir.extra[extra_index..][0..extra.data.body_len]; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index ce79d43fac..e2d6e964cf 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -700,7 +700,8 @@ pub const DeclGen = struct { @intCast(c_uint, llvm_params.len), llvm.Bool.fromBool(is_var_args), ); - const llvm_addrspace = self.llvmAddressSpace(t.fnAddressSpace()); + // TODO make .Fn not both a pointer type and a prototype + const llvm_addrspace = self.llvmAddressSpace(.generic); return llvm_fn_ty.pointerType(llvm_addrspace); }, .ComptimeInt => unreachable, diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index a41384cee6..87dfee1bf2 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -18483,35 +18483,30 @@ static Error ir_make_type_info_value(IrAnalyze *ira, Scope *scope, AstNode *sour fields[1]->special = ConstValSpecialStatic; fields[1]->type = ira->codegen->builtin_types.entry_num_lit_int; bigint_init_unsigned(&fields[1]->data.x_bigint, get_ptr_align(ira->codegen, type_entry)); - // address_space: AddressSpace - ensure_field_index(result->type, "address_space", 2); - fields[2]->special = ConstValSpecialStatic; - fields[2]->type = get_builtin_type(ira->codegen, "AddressSpace"); - bigint_init_unsigned(&fields[2]->data.x_enum_tag, AddressSpaceGeneric); // is_generic: bool - ensure_field_index(result->type, "is_generic", 3); + ensure_field_index(result->type, "is_generic", 2); bool is_generic = type_entry->data.fn.is_generic; + fields[2]->special = ConstValSpecialStatic; + fields[2]->type = ira->codegen->builtin_types.entry_bool; + fields[2]->data.x_bool = is_generic; + // is_varargs: bool + ensure_field_index(result->type, "is_var_args", 3); + bool is_varargs = type_entry->data.fn.fn_type_id.is_var_args; fields[3]->special = ConstValSpecialStatic; fields[3]->type = ira->codegen->builtin_types.entry_bool; - fields[3]->data.x_bool = is_generic; - // is_varargs: bool - ensure_field_index(result->type, "is_var_args", 4); - bool is_varargs = type_entry->data.fn.fn_type_id.is_var_args; - fields[4]->special = ConstValSpecialStatic; - fields[4]->type = ira->codegen->builtin_types.entry_bool; - fields[4]->data.x_bool = is_varargs; + fields[3]->data.x_bool = is_varargs; // return_type: ?type - ensure_field_index(result->type, "return_type", 5); - fields[5]->special = ConstValSpecialStatic; - fields[5]->type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type); + ensure_field_index(result->type, "return_type", 4); + fields[4]->special = ConstValSpecialStatic; + fields[4]->type = get_optional_type(ira->codegen, ira->codegen->builtin_types.entry_type); if (type_entry->data.fn.fn_type_id.return_type == nullptr) - fields[5]->data.x_optional = nullptr; + fields[4]->data.x_optional = nullptr; else { ZigValue *return_type = ira->codegen->pass1_arena->create(); return_type->special = ConstValSpecialStatic; return_type->type = ira->codegen->builtin_types.entry_type; return_type->data.x_type = type_entry->data.fn.fn_type_id.return_type; - fields[5]->data.x_optional = return_type; + fields[4]->data.x_optional = return_type; } // args: []TypeInfo.FnArg ZigType *type_info_fn_arg_type = ir_type_info_get_type(ira, "FnArg", nullptr); @@ -18526,7 +18521,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, Scope *scope, AstNode *sour fn_arg_array->data.x_array.special = ConstArraySpecialNone; fn_arg_array->data.x_array.data.s_none.elements = ira->codegen->pass1_arena->allocate(fn_arg_count); - init_const_slice(ira->codegen, fields[6], fn_arg_array, 0, fn_arg_count, false, nullptr); + init_const_slice(ira->codegen, fields[5], fn_arg_array, 0, fn_arg_count, false, nullptr); for (size_t fn_arg_index = 0; fn_arg_index < fn_arg_count; fn_arg_index++) { FnTypeParamInfo *fn_param_info = &type_entry->data.fn.fn_type_id.param_info[fn_arg_index]; @@ -19330,22 +19325,9 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ if (alignment == nullptr) return ira->codegen->invalid_inst_gen->value->type; - ZigValue *as_value = get_const_field(ira, source_node, payload, "address_space", 2); - if (as_value == nullptr) - return ira->codegen->invalid_inst_gen->value->type; - assert(as_value->special == ConstValSpecialStatic); - assert(as_value->type == get_builtin_type(ira->codegen, "AddressSpace")); - AddressSpace as = (AddressSpace)bigint_as_u32(&as_value->data.x_enum_tag); - if (as != AddressSpaceGeneric) { - ir_add_error_node(ira, source_node, buf_sprintf( - "address space '%s' not available in stage 1 compiler, must be .generic", - address_space_name(as))); - return ira->codegen->invalid_inst_gen->value->type; - } - Error err; bool is_generic; - if ((err = get_const_field_bool(ira, source_node, payload, "is_generic", 3, &is_generic))) + if ((err = get_const_field_bool(ira, source_node, payload, "is_generic", 2, &is_generic))) return ira->codegen->invalid_inst_gen->value->type; if (is_generic) { ir_add_error_node(ira, source_node, buf_sprintf("TypeInfo.Fn.is_generic must be false for @Type")); @@ -19353,20 +19335,20 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ } bool is_var_args; - if ((err = get_const_field_bool(ira, source_node, payload, "is_var_args", 4, &is_var_args))) + if ((err = get_const_field_bool(ira, source_node, payload, "is_var_args", 3, &is_var_args))) return ira->codegen->invalid_inst_gen->value->type; if (is_var_args && cc != CallingConventionC) { ir_add_error_node(ira, source_node, buf_sprintf("varargs functions must have C calling convention")); return ira->codegen->invalid_inst_gen->value->type; } - ZigType *return_type = get_const_field_meta_type_optional(ira, source_node, payload, "return_type", 5); + ZigType *return_type = get_const_field_meta_type_optional(ira, source_node, payload, "return_type", 4); if (return_type == nullptr) { ir_add_error_node(ira, source_node, buf_sprintf("TypeInfo.Fn.return_type must be non-null for @Type")); return ira->codegen->invalid_inst_gen->value->type; } - ZigValue *args_value = get_const_field(ira, source_node, payload, "args", 6); + ZigValue *args_value = get_const_field(ira, source_node, payload, "args", 5); if (args_value == nullptr) return ira->codegen->invalid_inst_gen->value->type; assert(args_value->special == ConstValSpecialStatic); diff --git a/src/target.zig b/src/target.zig index c9d7e1742b..09e65ff909 100644 --- a/src/target.zig +++ b/src/target.zig @@ -544,3 +544,21 @@ pub fn largestAtomicBits(target: std.Target) u32 { .x86_64 => 128, }; } + +pub fn defaultAddressSpace( + target: std.Target, + context: enum { + /// Query the default address space for global constant values. + global_constant, + /// Query the default address space for global mutable values. + global_mutable, + /// Query the default address space for function-local values. + local, + /// Query the default address space for functions themselves. + function, + }, +) std.builtin.AddressSpace { + _ = target; + _ = context; + return .generic; +} diff --git a/src/type.zig b/src/type.zig index e8a9d059d0..f85ee12d4b 100644 --- a/src/type.zig +++ b/src/type.zig @@ -530,8 +530,6 @@ pub const Type = extern union { return false; if (a.fnCallingConvention() != b.fnCallingConvention()) return false; - if (a.fnAddressSpace() != b.fnAddressSpace()) - return false; const a_param_len = a.fnParamLen(); const b_param_len = b.fnParamLen(); if (a_param_len != b_param_len) @@ -838,7 +836,6 @@ pub const Type = extern union { .return_type = try payload.return_type.copy(allocator), .param_types = param_types, .cc = payload.cc, - .@"addrspace" = payload.@"addrspace", .is_var_args = payload.is_var_args, .is_generic = payload.is_generic, .comptime_params = comptime_params.ptr, @@ -1001,9 +998,6 @@ pub const Type = extern union { try writer.writeAll(") callconv(."); try writer.writeAll(@tagName(payload.cc)); try writer.writeAll(") "); - if (payload.@"addrspace" != .generic) { - try writer.print("addrspace(.{s}) ", .{@tagName(payload.@"addrspace")}); - } ty = payload.return_type; continue; }, @@ -2730,18 +2724,6 @@ pub const Type = extern union { }; } - pub fn fnAddressSpace(self: Type) std.builtin.AddressSpace { - return switch (self.tag()) { - .fn_noreturn_no_args => .generic, - .fn_void_no_args => .generic, - .fn_naked_noreturn_no_args => .generic, - .fn_ccc_void_no_args => .generic, - .function => self.castTag(.function).?.data.@"addrspace", - - else => unreachable, - }; - } - pub fn fnInfo(ty: Type) Payload.Function.Data { return switch (ty.tag()) { .fn_noreturn_no_args => .{ @@ -2749,7 +2731,6 @@ pub const Type = extern union { .comptime_params = undefined, .return_type = initTag(.noreturn), .cc = .Unspecified, - .@"addrspace" = .generic, .is_var_args = false, .is_generic = false, }, @@ -2758,7 +2739,6 @@ pub const Type = extern union { .comptime_params = undefined, .return_type = initTag(.void), .cc = .Unspecified, - .@"addrspace" = .generic, .is_var_args = false, .is_generic = false, }, @@ -2767,7 +2747,6 @@ pub const Type = extern union { .comptime_params = undefined, .return_type = initTag(.noreturn), .cc = .Naked, - .@"addrspace" = .generic, .is_var_args = false, .is_generic = false, }, @@ -2776,7 +2755,6 @@ pub const Type = extern union { .comptime_params = undefined, .return_type = initTag(.void), .cc = .C, - .@"addrspace" = .generic, .is_var_args = false, .is_generic = false, }, @@ -3648,7 +3626,6 @@ pub const Type = extern union { comptime_params: [*]bool, return_type: Type, cc: std.builtin.CallingConvention, - @"addrspace": std.builtin.AddressSpace, is_var_args: bool, is_generic: bool, diff --git a/test/compile_errors.zig b/test/compile_errors.zig index f2fa20d722..058fd1f2db 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -410,7 +410,6 @@ pub fn addCases(ctx: *TestContext) !void { \\ .Fn = .{ \\ .calling_convention = .Unspecified, \\ .alignment = 0, - \\ .address_space = .generic, \\ .is_generic = true, \\ .is_var_args = false, \\ .return_type = u0, @@ -427,7 +426,6 @@ pub fn addCases(ctx: *TestContext) !void { \\ .Fn = .{ \\ .calling_convention = .Unspecified, \\ .alignment = 0, - \\ .address_space = .generic, \\ .is_generic = false, \\ .is_var_args = true, \\ .return_type = u0, @@ -444,7 +442,6 @@ pub fn addCases(ctx: *TestContext) !void { \\ .Fn = .{ \\ .calling_convention = .Unspecified, \\ .alignment = 0, - \\ .address_space = .generic, \\ .is_generic = false, \\ .is_var_args = false, \\ .return_type = null, @@ -456,23 +453,6 @@ pub fn addCases(ctx: *TestContext) !void { "tmp.zig:1:20: error: TypeInfo.Fn.return_type must be non-null for @Type", }); - ctx.objErrStage1("@Type(.Fn) with invalid address space ", - \\const Foo = @Type(.{ - \\ .Fn = .{ - \\ .calling_convention = .Unspecified, - \\ .alignment = 0, - \\ .address_space = .fs, - \\ .is_generic = false, - \\ .is_var_args = false, - \\ .return_type = u0, - \\ .args = &[_]@import("std").builtin.TypeInfo.FnArg{}, - \\ }, - \\}); - \\comptime { _ = Foo; } - , &[_][]const u8{ - "tmp.zig:1:20: error: address space 'fs' not available in stage 1 compiler, must be .generic", - }); - ctx.objErrStage1("@Type for union with opaque field", \\const TypeInfo = @import("std").builtin.TypeInfo; \\const Untagged = @Type(.{ From 619260d94d68bdecdac649fa7b156dc8962a9889 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Mon, 20 Sep 2021 02:34:53 +0200 Subject: [PATCH 066/160] Address Spaces: Fix comments in Ast.zig --- lib/std/zig/Ast.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index a62e9c105d..5838dcd37a 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -2961,9 +2961,9 @@ pub const Node = struct { type_node: Index, /// Populated if align(A) is present. align_node: Index, - /// Populated if linksection(A) is present. - addrspace_node: Index, /// Populated if addrspace(A) is present. + addrspace_node: Index, + /// Populated if linksection(A) is present. section_node: Index, }; @@ -2995,9 +2995,9 @@ pub const Node = struct { param: Index, /// Populated if align(A) is present. align_expr: Index, - /// Populated if linksection(A) is present. - addrspace_expr: Index, /// Populated if addrspace(A) is present. + addrspace_expr: Index, + /// Populated if linksection(A) is present. section_expr: Index, /// Populated if callconv(A) is present. callconv_expr: Index, From 1f61076ffb3027e043ff78995344267b39b6226f Mon Sep 17 00:00:00 2001 From: Malcolm Still Date: Sun, 19 Sep 2021 20:17:01 +0100 Subject: [PATCH 067/160] I'm working on a WebAssembly interpreter in zig. WebAssembly uses LEB128 encoding throughout its specification. The WebAssembly spec requires signed LEB128 to be encoded up to a maximum number of bytes (max 5 bytes for i32, max 10 bytes for i64) and that "unused" bits are all 0 if the number is positive and all 1 if the number is negative. The Zig LEB128 implementation already enforces the max number of bytes and does check the unused bytes https://github.com/ziglang/zig/blob/master/lib/std/leb128.zig#L70-L79. However, the WebAssembly test suite has a number of tests that were failing validation (expecting the wasm module to fail validation, but when running the tests, those examples were actually passing validation): https://github.com/malcolmstill/foxwren/blob/master/test/testsuite/binary-leb128.wast#L893-L902 https://github.com/malcolmstill/foxwren/blob/master/test/testsuite/binary-leb128.wast#L934-L943 Notably the failures are both cases of negative numbers and the top 4 bits of the last byte are zero. And I believe this is the issue: we're only currently checking the "unused" / remaining_bits if we overflow, but in the case of 0x0_ no overflow happens and so the bits go unchecked. In other words: \xff\xff\xff\xff\7f rightly successfully decodes (because it overflows and the remaining bits are 0b1111) \xff\xff\xff\xff\6f rightly errors with overflow (because it overflows and the remaining bits are 0b1110) \xff\xff\xff\xff\0f incorrectly decodes when it should error (because the top 4 bits are all 0, and so no overflow occurs and no check that the unused bits are 1 happens) This PR adds a the remaining_bits check in an else branch of the @shlWithOverflow when we're looking at the last byte and the number being decoded is negative. Note: this means a couple of the test cases in leb128.zig that are down as decoding shouldn't actually decode so I added the appropriate 1 bits. --- lib/std/leb128.zig | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/std/leb128.zig b/lib/std/leb128.zig index c908b56f69..470bdc85ed 100644 --- a/lib/std/leb128.zig +++ b/lib/std/leb128.zig @@ -76,6 +76,14 @@ pub fn readILEB128(comptime T: type, reader: anytype) !T { const remaining_shift = @intCast(u3, @typeInfo(U).Int.bits - @as(u16, shift)); const remaining_bits = @bitCast(i8, byte | 0x80) >> remaining_shift; if (remaining_bits != -1) return error.Overflow; + } else { + // If we don't overflow and this is the last byte and the number being decoded + // is negative, check that the remaining bits are 1 + if ((byte & 0x80 == 0) and (@bitCast(S, temp) < 0)) { + const remaining_shift = @intCast(u3, @typeInfo(U).Int.bits - @as(u16, shift)); + const remaining_bits = @bitCast(i8, byte | 0x80) >> remaining_shift; + if (remaining_bits != -1) return error.Overflow; + } } value |= temp; @@ -215,6 +223,8 @@ test "deserialize signed LEB128" { try testing.expectError(error.Overflow, test_read_ileb128(i32, "\x80\x80\x80\x80\x40")); try testing.expectError(error.Overflow, test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x40")); try testing.expectError(error.Overflow, test_read_ileb128(i8, "\xff\x7e")); + try testing.expectError(error.Overflow, test_read_ileb128(i32, "\x80\x80\x80\x80\x08")); + try testing.expectError(error.Overflow, test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01")); // Decode SLEB128 try testing.expect((try test_read_ileb128(i64, "\x00")) == 0); @@ -233,8 +243,8 @@ test "deserialize signed LEB128" { try testing.expect((try test_read_ileb128(i8, "\xff\x7f")) == -1); try testing.expect((try test_read_ileb128(i16, "\xff\xff\x7f")) == -1); try testing.expect((try test_read_ileb128(i32, "\xff\xff\xff\xff\x7f")) == -1); - try testing.expect((try test_read_ileb128(i32, "\x80\x80\x80\x80\x08")) == -0x80000000); - try testing.expect((try test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01")) == @bitCast(i64, @intCast(u64, 0x8000000000000000))); + try testing.expect((try test_read_ileb128(i32, "\x80\x80\x80\x80\x78")) == -0x80000000); + try testing.expect((try test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7f")) == @bitCast(i64, @intCast(u64, 0x8000000000000000))); try testing.expect((try test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x40")) == -0x4000000000000000); try testing.expect((try test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7f")) == -0x8000000000000000); From cfd5b81850d0234a5011c98f840d93bed47210c1 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Thu, 16 Sep 2021 16:51:29 -0500 Subject: [PATCH 068/160] Fix compiler builds with tracy on the mingw target --- build.zig | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 0fec584a27..fef9ee786e 100644 --- a/build.zig +++ b/build.zig @@ -112,6 +112,11 @@ pub fn build(b: *Builder) !void { b.default_step.dependOn(&exe.step); exe.single_threaded = single_threaded; + if (target.isWindows() and target.getAbi() == .gnu) { + // LTO is currently broken on mingw, this can be removed when it's fixed. + exe.want_lto = false; + } + const exe_options = b.addOptions(); exe.addOptions("build_options", exe_options); @@ -239,12 +244,24 @@ pub fn build(b: *Builder) !void { b.allocator, &[_][]const u8{ tracy_path, "TracyClient.cpp" }, ) catch unreachable; + + // On mingw, we need to opt into windows 7+ to get some features required by tracy. + const tracy_c_flags: []const []const u8 = if (target.isWindows() and target.getAbi() == .gnu) + &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined", "-D_WIN32_WINNT=0x601" } + else + &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" }; + exe.addIncludeDir(tracy_path); - exe.addCSourceFile(client_cpp, &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" }); + exe.addCSourceFile(client_cpp, tracy_c_flags); if (!enable_llvm) { exe.linkSystemLibraryName("c++"); } exe.linkLibC(); + + if (target.isWindows()) { + exe.linkSystemLibrary("dbghelp"); + exe.linkSystemLibrary("ws2_32"); + } } const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter"); From db181b173f6debc2dbb1e68342ff46a4299c19a9 Mon Sep 17 00:00:00 2001 From: Ali Chraghi <63465728+AliChraghi@users.noreply.github.com> Date: Mon, 20 Sep 2021 10:33:18 +0430 Subject: [PATCH 069/160] Update `hash` & `crypto` benchmarks run comment (#9790) * sync function arguments name with other same functions --- lib/std/crypto/benchmark.zig | 2 +- lib/std/crypto/blake3.zig | 8 ++++---- lib/std/hash/benchmark.zig | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index d9ea4b172a..65e7ecb680 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -1,4 +1,4 @@ -// zig run benchmark.zig --release-fast --zig-lib-dir .. +// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig const std = @import("../std.zig"); const builtin = std.builtin; diff --git a/lib/std/crypto/blake3.zig b/lib/std/crypto/blake3.zig index 6a2950645a..2e3f3f3c7b 100644 --- a/lib/std/crypto/blake3.zig +++ b/lib/std/crypto/blake3.zig @@ -398,10 +398,10 @@ pub const Blake3 = struct { return Blake3.init_internal(context_key_words, DERIVE_KEY_MATERIAL); } - pub fn hash(in: []const u8, out: []u8, options: Options) void { - var hasher = Blake3.init(options); - hasher.update(in); - hasher.final(out); + pub fn hash(b: []const u8, out: []u8, options: Options) void { + var d = Blake3.init(options); + d.update(b); + d.final(out); } fn pushCv(self: *Blake3, cv: [8]u32) void { diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index b4952a9260..d9865bcdd8 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -1,4 +1,4 @@ -// zig run benchmark.zig --release-fast --zig-lib-dir .. +// zig run -O ReleaseFast --zig-lib-dir ../.. benchmark.zig const builtin = std.builtin; const std = @import("std"); From b3ae69d80b5bc04a334882d955d638eac64eef33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rory=20O=E2=80=99Kane?= Date: Sun, 12 Sep 2021 13:08:56 -0400 Subject: [PATCH 070/160] langref: define the inferred error set syntax more explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This edit allows the reader to understand the syntax this section is talking about more quickly – they don’t have to read the whole code block and understand which part of it demonstrates the feature being described. Affects https://ziglang.org/documentation/master/#Inferred-Error-Sets --- 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 3f33123372..384ecfffe3 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -4955,7 +4955,7 @@ test "merge error sets" { {#header_open|Inferred Error Sets#}

    Because many functions in Zig return a possible error, Zig supports inferring the error set. - To infer the error set for a function, use this syntax: + To infer the error set for a function, prepend the {#syntax#}!{#endsyntax#} operator to the function’s return type, like {#syntax#}!T{#endsyntax#}:

    {#code_begin|test|inferred_error_sets#} // With an inferred error set From 806aeab2a729e0638ccf6682171181df56d31122 Mon Sep 17 00:00:00 2001 From: HugoFlorentino Date: Fri, 10 Sep 2021 22:38:38 -0400 Subject: [PATCH 071/160] adding support for UTF-8 output --- lib/std/os/windows/kernel32.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index 89a00ed892..cde4a4906b 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -152,6 +152,8 @@ pub extern "kernel32" fn GetCommandLineW() callconv(WINAPI) LPWSTR; pub extern "kernel32" fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) callconv(WINAPI) BOOL; +pub extern "kernel32" fn GetConsoleOutputCP() callconv(WINAPI) UINT; + pub extern "kernel32" fn GetConsoleScreenBufferInfo(hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) callconv(WINAPI) BOOL; pub extern "kernel32" fn FillConsoleOutputCharacterA(hConsoleOutput: HANDLE, cCharacter: CHAR, nLength: DWORD, dwWriteCoord: COORD, lpNumberOfCharsWritten: *DWORD) callconv(WINAPI) BOOL; pub extern "kernel32" fn FillConsoleOutputCharacterW(hConsoleOutput: HANDLE, cCharacter: WCHAR, nLength: DWORD, dwWriteCoord: COORD, lpNumberOfCharsWritten: *DWORD) callconv(WINAPI) BOOL; @@ -286,6 +288,8 @@ pub extern "kernel32" fn SetConsoleCtrlHandler( Add: BOOL, ) callconv(WINAPI) BOOL; +pub extern "kernel32" fn SetConsoleOutputCP(wCodePageID: UINT) callconv(WINAPI) BOOL; + pub extern "kernel32" fn SetFileCompletionNotificationModes( FileHandle: HANDLE, Flags: UCHAR, From 5dc251747b4ea65b1c8d3f2b5af62ca83c6d1196 Mon Sep 17 00:00:00 2001 From: Travis Martin Date: Fri, 10 Sep 2021 12:20:59 -0700 Subject: [PATCH 072/160] Fix compile error in WindowsCondition.wait() --- lib/std/Thread/Condition.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Thread/Condition.zig b/lib/std/Thread/Condition.zig index 647a50b913..d08a7c3c96 100644 --- a/lib/std/Thread/Condition.zig +++ b/lib/std/Thread/Condition.zig @@ -54,7 +54,7 @@ pub const WindowsCondition = struct { pub fn wait(cond: *WindowsCondition, mutex: *Mutex) void { const rc = windows.kernel32.SleepConditionVariableSRW( &cond.cond, - &mutex.srwlock, + &mutex.impl.srwlock, windows.INFINITE, @as(windows.ULONG, 0), ); From f697e0a326b06b7dcf641fbd61110be756407dcf Mon Sep 17 00:00:00 2001 From: Jacob G-W Date: Sun, 29 Aug 2021 17:25:18 -0400 Subject: [PATCH 073/160] plan9 linker: link lineinfo and filenames --- src/codegen.zig | 46 ++++-- src/link/Plan9.zig | 328 +++++++++++++++++++++++++++++++++------- src/link/Plan9/aout.zig | 8 + 3 files changed, 312 insertions(+), 70 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index 20243ba861..3596ac47ab 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -52,16 +52,23 @@ pub const DebugInfoOutput = union(enum) { /// assume all numbers/variables are bytes /// 0 w x y z -> interpret w x y z as a big-endian i32, and add it to the line offset /// x when x < 65 -> add x to line offset - /// x when x < 129 -> subtract 64 from x and add it to the line offset + /// x when x < 129 -> subtract 64 from x and subtract it from the line offset /// x -> subtract 129 from x, multiply it by the quanta of the instruction size /// (1 on x86_64), and add it to the pc /// after every opcode, add the quanta of the instruction size to the pc plan9: struct { /// the actual opcodes dbg_line: *std.ArrayList(u8), + /// what line the debuginfo starts on + /// this helps because the linker might have to insert some opcodes to make sure that the line count starts at the right amount for the next decl + start_line: *?u32, /// what the line count ends on after codegen /// this helps because the linker might have to insert some opcodes to make sure that the line count starts at the right amount for the next decl end_line: *u32, + /// the last pc change op + /// This is very useful for adding quanta + /// to it if its not actually the last one. + pcop_change_index: *?u32, }, none, }; @@ -946,7 +953,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { fn dbgAdvancePCAndLine(self: *Self, line: u32, column: u32) InnerError!void { const delta_line = @intCast(i32, line) - @intCast(i32, self.prev_di_line); - const delta_pc = self.code.items.len - self.prev_di_pc; + const delta_pc: usize = self.code.items.len - self.prev_di_pc; switch (self.debug_output) { .dwarf => |dbg_out| { // TODO Look into using the DWARF special opcodes to compress this data. @@ -960,30 +967,39 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { leb128.writeILEB128(dbg_out.dbg_line.writer(), delta_line) catch unreachable; } dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy); + self.prev_di_pc = self.code.items.len; + self.prev_di_line = line; + self.prev_di_column = column; + self.prev_di_pc = self.code.items.len; }, .plan9 => |dbg_out| { + if (delta_pc <= 0) return; // only do this when the pc changes // we have already checked the target in the linker to make sure it is compatable const quant = @import("link/Plan9/aout.zig").getPCQuant(self.target.cpu.arch) catch unreachable; // increasing the line number - if (delta_line > 0 and delta_line < 65) { - try dbg_out.dbg_line.append(@intCast(u8, delta_line)); - } else if (delta_line < 0 and delta_line > -65) { - try dbg_out.dbg_line.append(@intCast(u8, -delta_line + 64)); - } else if (delta_line != 0) { - try dbg_out.dbg_line.writer().writeIntBig(i32, delta_line); - } + try @import("link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line); // increasing the pc - if (delta_pc - quant != 0) { - try dbg_out.dbg_line.append(@intCast(u8, delta_pc - quant + 129)); - } + const d_pc_p9 = @intCast(i64, delta_pc) - quant; + if (d_pc_p9 > 0) { + // minus one becaue if its the last one, we want to leave space to change the line which is one quanta + try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant); + if (dbg_out.pcop_change_index.*) |pci| + dbg_out.dbg_line.items[pci] += 1; + dbg_out.pcop_change_index.* = @intCast(u32, dbg_out.dbg_line.items.len - 1); + } else if (d_pc_p9 == 0) { + // we don't need to do anything, because adding the quant does it for us + } else unreachable; + if (dbg_out.start_line.* == null) + dbg_out.start_line.* = self.prev_di_line; dbg_out.end_line.* = line; + // only do this if the pc changed + self.prev_di_line = line; + self.prev_di_column = column; + self.prev_di_pc = self.code.items.len; }, .none => {}, } - self.prev_di_line = line; - self.prev_di_column = column; - self.prev_di_pc = self.code.items.len; } /// Asserts there is already capacity to insert into top branch inst_table. diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index a51ab08e95..384345ff67 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -20,6 +20,14 @@ const Allocator = std.mem.Allocator; const log = std.log.scoped(.link); const assert = std.debug.assert; +const FnDeclOutput = struct { + code: []const u8, + /// this might have to be modified in the linker, so thats why its mutable + lineinfo: []u8, + start_line: u32, + end_line: u32, +}; + base: link.File, sixtyfour_bit: bool, error_flags: File.ErrorFlags = File.ErrorFlags{}, @@ -27,9 +35,31 @@ bases: Bases, /// A symbol's value is just casted down when compiling /// for a 32 bit target. +/// Does not represent the order or amount of symbols in the file +/// it is just useful for storing symbols. Some other symbols are in +/// file_segments. syms: std.ArrayListUnmanaged(aout.Sym) = .{}, -fn_decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, []const u8) = .{}, +/// The plan9 a.out format requires segments of +/// filenames to be deduplicated, so we use this map to +/// de duplicate it. The value is the value of the path +/// component +file_segments: std.StringArrayHashMapUnmanaged(u16) = .{}, +/// The value of a 'f' symbol increments by 1 every time, so that no 2 'f' +/// symbols have the same value. +file_segments_i: u16 = 1, + +path_arena: std.heap.ArenaAllocator, + +/// maps a file scope to a hash map of decl to codegen output +/// this is useful for line debuginfo, since it makes sense to sort by file +/// The debugger looks for the first file (aout.Sym.Type.z) preceeding the text symbol +/// of the function to know what file it came from. +/// If we group the decls by file, it makes it really easy to do this (put the symbol in the correct place) +fn_decl_table: std.AutoArrayHashMapUnmanaged( + *Module.Scope.File, + struct { sym_index: u32, functions: std.AutoArrayHashMapUnmanaged(*Module.Decl, FnDeclOutput) = .{} }, +) = .{}, data_decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, []const u8) = .{}, hdr: aout.ExecHdr = undefined, @@ -110,8 +140,12 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Plan9 { 33...64 => true, else => return error.UnsupportedP9Architecture, }; + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + const self = try gpa.create(Plan9); self.* = .{ + .path_arena = arena_allocator, .base = .{ .tag = .plan9, .options = options, @@ -122,9 +156,66 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Plan9 { .bases = undefined, .magic = try aout.magicFromArch(self.base.options.target.cpu.arch), }; + // a / will always be in a file path + try self.file_segments.put(self.base.allocator, "/", 1); return self; } +fn putFn(self: *Plan9, decl: *Module.Decl, out: FnDeclOutput) !void { + const gpa = self.base.allocator; + const fn_map_res = try self.fn_decl_table.getOrPut(gpa, decl.namespace.file_scope); + if (fn_map_res.found_existing) { + try fn_map_res.value_ptr.functions.put(gpa, decl, out); + } else { + const file = decl.namespace.file_scope; + const arena = &self.path_arena.allocator; + // each file gets a symbol + fn_map_res.value_ptr.* = .{ + .sym_index = blk: { + try self.syms.append(gpa, undefined); + break :blk @intCast(u32, self.syms.items.len - 1); + }, + }; + try fn_map_res.value_ptr.functions.put(gpa, decl, out); + + var a = std.ArrayList(u8).init(arena); + errdefer a.deinit(); + // every 'z' starts with 0 + try a.append(0); + // path component value of '/' + try a.writer().writeIntBig(u16, 1); + + // getting the full file path + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + const dir = file.pkg.root_src_directory.path orelse try std.os.getcwd(&buf); + const sub_path = try std.fs.path.join(arena, &.{ dir, file.sub_file_path }); + try self.addPathComponents(sub_path, &a); + + // null terminate + try a.append(0); + const final = a.toOwnedSlice(); + self.syms.items[fn_map_res.value_ptr.sym_index] = .{ + .type = .z, + .value = 1, + .name = final, + }; + } +} + +fn addPathComponents(self: *Plan9, path: []const u8, a: *std.ArrayList(u8)) !void { + const sep = std.fs.path.sep; + var it = std.mem.tokenize(u8, path, &.{sep}); + while (it.next()) |component| { + if (self.file_segments.get(component)) |num| { + try a.writer().writeIntBig(u16, num); + } else { + self.file_segments_i += 1; + try self.file_segments.put(self.base.allocator, component, self.file_segments_i); + try a.writer().writeIntBig(u16, self.file_segments_i); + } + } +} + pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void { if (build_options.skip_non_native and builtin.object_format != .plan9) { @panic("Attempted to compile for object format that was disabled by build configuration"); @@ -139,7 +230,9 @@ pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liv defer code_buffer.deinit(); var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator); defer dbg_line_buffer.deinit(); - var end_line: u32 = 0; + var start_line: ?u32 = null; + var end_line: u32 = undefined; + var pcop_change_index: ?u32 = null; const res = try codegen.generateFunction( &self.base, @@ -152,6 +245,8 @@ pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liv .plan9 = .{ .dbg_line = &dbg_line_buffer, .end_line = &end_line, + .start_line = &start_line, + .pcop_change_index = &pcop_change_index, }, }, ); @@ -163,7 +258,13 @@ pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liv return; }, }; - try self.fn_decl_table.put(self.base.allocator, decl, code); + const out: FnDeclOutput = .{ + .code = code, + .lineinfo = dbg_line_buffer.toOwnedSlice(), + .start_line = start_line.?, + .end_line = end_line, + }; + try self.putFn(decl, out); return self.updateFinish(decl); } @@ -242,6 +343,30 @@ pub fn flush(self: *Plan9, comp: *Compilation) !void { return self.flushModule(comp); } +pub fn changeLine(l: *std.ArrayList(u8), delta_line: i32) !void { + if (delta_line > 0 and delta_line < 65) { + const toappend = @intCast(u8, delta_line); + try l.append(toappend); + } else if (delta_line < 0 and delta_line > -65) { + const toadd: u8 = @intCast(u8, -delta_line + 64); + try l.append(toadd); + } else if (delta_line != 0) { + try l.append(0); + try l.writer().writeIntBig(i32, delta_line); + } +} + +fn declCount(self: *Plan9) u64 { + var fn_decl_count: u64 = 0; + var itf_files = self.fn_decl_table.iterator(); + while (itf_files.next()) |ent| { + // get the submap + var submap = ent.value_ptr.functions; + fn_decl_count += submap.count(); + } + return self.data_decl_table.count() + fn_decl_count; +} + pub fn flushModule(self: *Plan9, comp: *Compilation) !void { if (build_options.skip_non_native and builtin.object_format != .plan9) { @panic("Attempted to compile for object format that was disabled by build configuration"); @@ -257,13 +382,13 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void { const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; - assert(self.got_len == self.fn_decl_table.count() + self.data_decl_table.count() + self.got_index_free_list.items.len); + assert(self.got_len == self.declCount() + self.got_index_free_list.items.len); const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8; var got_table = try self.base.allocator.alloc(u8, got_size); defer self.base.allocator.free(got_table); - // + 3 for header, got, symbols - var iovecs = try self.base.allocator.alloc(std.os.iovec_const, self.fn_decl_table.count() + self.data_decl_table.count() + 3); + // + 4 for header, got, symbols, linecountinfo + var iovecs = try self.base.allocator.alloc(std.os.iovec_const, self.declCount() + 4); defer self.base.allocator.free(iovecs); const file = self.base.file.?; @@ -276,30 +401,52 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void { iovecs[0] = .{ .iov_base = hdr_slice.ptr, .iov_len = hdr_slice.len }; var iovecs_i: usize = 1; var text_i: u64 = 0; + + var linecountinfo = std.ArrayList(u8).init(self.base.allocator); + defer linecountinfo.deinit(); // text { - var it = self.fn_decl_table.iterator(); - while (it.next()) |entry| { - const decl = entry.key_ptr.*; - const code = entry.value_ptr.*; - log.debug("write text decl {*} ({s})", .{ decl, decl.name }); - foff += code.len; - iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len }; - iovecs_i += 1; - const off = self.getAddr(text_i, .t); - text_i += code.len; - decl.link.plan9.offset = off; - if (!self.sixtyfour_bit) { - mem.writeIntNative(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off)); - mem.writeInt(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off), self.base.options.target.cpu.arch.endian()); - } else { - mem.writeInt(u64, got_table[decl.link.plan9.got_index.? * 8 ..][0..8], off, self.base.options.target.cpu.arch.endian()); - } - self.syms.items[decl.link.plan9.sym_index.?].value = off; - if (mod.decl_exports.get(decl)) |exports| { - try self.addDeclExports(mod, decl, exports); + var linecount: u32 = 0; + var it_file = self.fn_decl_table.iterator(); + while (it_file.next()) |fentry| { + var it = fentry.value_ptr.functions.iterator(); + while (it.next()) |entry| { + const decl = entry.key_ptr.*; + const out = entry.value_ptr.*; + log.debug("write text decl {*} ({s}), lines {d} to {d}", .{ decl, decl.name, out.start_line, out.end_line }); + { + // connect the previous decl to the next + const delta_line = @intCast(i32, out.start_line) - @intCast(i32, linecount); + + try changeLine(&linecountinfo, delta_line); + // TODO change the pc too (maybe?) + + // write out the actual info that was generated in codegen now + try linecountinfo.appendSlice(out.lineinfo); + linecount = out.end_line; + } + foff += out.code.len; + iovecs[iovecs_i] = .{ .iov_base = out.code.ptr, .iov_len = out.code.len }; + iovecs_i += 1; + const off = self.getAddr(text_i, .t); + text_i += out.code.len; + decl.link.plan9.offset = off; + if (!self.sixtyfour_bit) { + mem.writeIntNative(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off)); + mem.writeInt(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off), self.base.options.target.cpu.arch.endian()); + } else { + mem.writeInt(u64, got_table[decl.link.plan9.got_index.? * 8 ..][0..8], off, self.base.options.target.cpu.arch.endian()); + } + self.syms.items[decl.link.plan9.sym_index.?].value = off; + if (mod.decl_exports.get(decl)) |exports| { + try self.addDeclExports(mod, decl, exports); + } } } + if (linecountinfo.items.len & 1 == 1) { + // just a nop to make it even, the plan9 linker does this + try linecountinfo.append(129); + } // etext symbol self.syms.items[2].value = self.getAddr(text_i, .t); } @@ -337,20 +484,23 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void { // edata self.syms.items[1].value = self.getAddr(0x0, .b); var sym_buf = std.ArrayList(u8).init(self.base.allocator); - defer sym_buf.deinit(); try self.writeSyms(&sym_buf); - assert(2 + self.fn_decl_table.count() + self.data_decl_table.count() == iovecs_i); // we didn't write all the decls - iovecs[iovecs_i] = .{ .iov_base = sym_buf.items.ptr, .iov_len = sym_buf.items.len }; + const syms = sym_buf.toOwnedSlice(); + defer self.base.allocator.free(syms); + assert(2 + self.declCount() == iovecs_i); // we didn't write all the decls + iovecs[iovecs_i] = .{ .iov_base = syms.ptr, .iov_len = syms.len }; + iovecs_i += 1; + iovecs[iovecs_i] = .{ .iov_base = linecountinfo.items.ptr, .iov_len = linecountinfo.items.len }; iovecs_i += 1; // generate the header self.hdr = .{ .magic = self.magic, .text = @intCast(u32, text_i), .data = @intCast(u32, data_i), - .syms = @intCast(u32, sym_buf.items.len), + .syms = @intCast(u32, syms.len), .bss = 0, - .pcsz = 0, .spsz = 0, + .pcsz = @intCast(u32, linecountinfo.items.len), .entry = @intCast(u32, self.entry_val.?), }; std.mem.copy(u8, hdr_slice, self.hdr.toU8s()[0..hdr_size]); @@ -397,7 +547,15 @@ pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { // in the deleteUnusedDecl function. const is_fn = (decl.val.tag() == .function); if (is_fn) { - _ = self.fn_decl_table.swapRemove(decl); + var symidx_and_submap = + self.fn_decl_table.get(decl.namespace.file_scope).?; + var submap = symidx_and_submap.functions; + _ = submap.swapRemove(decl); + if (submap.count() == 0) { + self.syms.items[symidx_and_submap.sym_index] = aout.Sym.undefined_symbol; + self.syms_index_free_list.append(self.base.allocator, symidx_and_submap.sym_index) catch {}; + submap.deinit(self.base.allocator); + } } else { _ = self.data_decl_table.swapRemove(decl); } @@ -407,7 +565,7 @@ pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { } if (decl.link.plan9.sym_index) |i| { self.syms_index_free_list.append(self.base.allocator, i) catch {}; - self.syms.items[i] = undefined; + self.syms.items[i] = aout.Sym.undefined_symbol; } } @@ -436,19 +594,29 @@ pub fn updateDeclExports( _ = exports; } pub fn deinit(self: *Plan9) void { - var itf = self.fn_decl_table.iterator(); - while (itf.next()) |entry| { - self.base.allocator.free(entry.value_ptr.*); + const gpa = self.base.allocator; + var itf_files = self.fn_decl_table.iterator(); + while (itf_files.next()) |ent| { + // get the submap + var submap = ent.value_ptr.functions; + defer submap.deinit(gpa); + var itf = submap.iterator(); + while (itf.next()) |entry| { + gpa.free(entry.value_ptr.code); + gpa.free(entry.value_ptr.lineinfo); + } } - self.fn_decl_table.deinit(self.base.allocator); + self.fn_decl_table.deinit(gpa); var itd = self.data_decl_table.iterator(); while (itd.next()) |entry| { - self.base.allocator.free(entry.value_ptr.*); + gpa.free(entry.value_ptr.*); } - self.data_decl_table.deinit(self.base.allocator); - self.syms.deinit(self.base.allocator); - self.got_index_free_list.deinit(self.base.allocator); - self.syms_index_free_list.deinit(self.base.allocator); + self.data_decl_table.deinit(gpa); + self.syms.deinit(gpa); + self.got_index_free_list.deinit(gpa); + self.syms_index_free_list.deinit(gpa); + self.file_segments.deinit(gpa); + self.path_arena.deinit(); } pub const Export = ?usize; @@ -458,7 +626,6 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio return error.LLVMBackendDoesNotSupportPlan9; assert(options.object_format == .plan9); const file = try options.emit.?.directory.handle.createFile(sub_path, .{ - .truncate = false, .read = true, .mode = link.determineMode(options), }); @@ -492,21 +659,72 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio return self; } +pub fn writeSym(self: *Plan9, w: anytype, sym: aout.Sym) !void { + log.debug("write sym.name: {s}", .{sym.name}); + log.debug("write sym.value: {x}", .{sym.value}); + if (sym.type == .bad) return; // we don't want to write free'd symbols + if (!self.sixtyfour_bit) { + try w.writeIntBig(u32, @intCast(u32, sym.value)); + } else { + try w.writeIntBig(u64, sym.value); + } + try w.writeByte(@enumToInt(sym.type)); + try w.writeAll(sym.name); + try w.writeByte(0); +} pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void { const writer = buf.writer(); - for (self.syms.items) |sym| { - log.debug("sym.name: {s}", .{sym.name}); - log.debug("sym.value: {x}", .{sym.value}); - if (mem.eql(u8, sym.name, "_start")) - self.entry_val = sym.value; - if (!self.sixtyfour_bit) { - try writer.writeIntBig(u32, @intCast(u32, sym.value)); - } else { - try writer.writeIntBig(u64, sym.value); + // write the f symbols + { + var it = self.file_segments.iterator(); + while (it.next()) |entry| { + try self.writeSym(writer, .{ + .type = .f, + .value = entry.value_ptr.*, + .name = entry.key_ptr.*, + }); + } + } + // write the data symbols + { + var it = self.data_decl_table.iterator(); + while (it.next()) |entry| { + const decl = entry.key_ptr.*; + const sym = self.syms.items[decl.link.plan9.sym_index.?]; + try self.writeSym(writer, sym); + if (self.base.options.module.?.decl_exports.get(decl)) |exports| { + for (exports) |e| { + try self.writeSym(writer, self.syms.items[e.link.plan9.?]); + } + } + } + } + // text symbols are the hardest: + // the file of a text symbol is the .z symbol before it + // so we have to write everything in the right order + { + var it_file = self.fn_decl_table.iterator(); + while (it_file.next()) |fentry| { + var symidx_and_submap = fentry.value_ptr; + // write the z symbol + try self.writeSym(writer, self.syms.items[symidx_and_submap.sym_index]); + + // write all the decls come from the file of the z symbol + var submap_it = symidx_and_submap.functions.iterator(); + while (submap_it.next()) |entry| { + const decl = entry.key_ptr.*; + const sym = self.syms.items[decl.link.plan9.sym_index.?]; + try self.writeSym(writer, sym); + if (self.base.options.module.?.decl_exports.get(decl)) |exports| { + for (exports) |e| { + const s = self.syms.items[e.link.plan9.?]; + if (mem.eql(u8, s.name, "_start")) + self.entry_val = s.value; + try self.writeSym(writer, s); + } + } + } } - try writer.writeByte(@enumToInt(sym.type)); - try writer.writeAll(sym.name); - try writer.writeByte(0); } } diff --git a/src/link/Plan9/aout.zig b/src/link/Plan9/aout.zig index b4fafbc31d..f3570f880c 100644 --- a/src/link/Plan9/aout.zig +++ b/src/link/Plan9/aout.zig @@ -34,6 +34,12 @@ pub const Sym = struct { type: Type, name: []const u8, + pub const undefined_symbol: Sym = .{ + .value = undefined, + .type = .bad, + .name = "undefined_symbol", + }; + /// The type field is one of the following characters with the /// high bit set: /// T text segment symbol @@ -65,6 +71,8 @@ pub const Sym = struct { z = 0x80 | 'z', Z = 0x80 | 'Z', m = 0x80 | 'm', + /// represents an undefined symbol, to be removed in flush + bad = 0, pub fn toGlobal(self: Type) Type { return switch (self) { From b9d3527e0ed53c4796ab64b4df7daf0909739807 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 20 Sep 2021 14:13:33 -0700 Subject: [PATCH 074/160] stage2: implement comptime `@atomicRmw` * introduce float_to_int and int_to_float AIR instructionts and implement for the LLVM backend and C backend. * Sema: implement `zirIntToFloat`. * Sema: implement `@atomicRmw` comptime evaluation - introduce `storePtrVal` for when one needs to store a Value to a pointer which is a Value, and assert it happens at comptime. * Value: introduce new functionality: - intToFloat - numberAddWrap - numberSubWrap - numberMax - numberMin - bitwiseAnd - bitwiseNand (not implemented yet) - bitwiseOr - bitwiseXor * Sema: hook up `zirBitwise` to the new Value bitwise implementations * Type: rename `isFloat` to `isRuntimeFloat` because it returns `false` for `comptime_float`. --- src/Air.zig | 8 ++ src/Liveness.zig | 2 + src/Sema.zig | 240 +++++++++++++++++++++---------- src/codegen.zig | 22 +++ src/codegen/c.zig | 20 +++ src/codegen/llvm.zig | 48 ++++++- src/codegen/llvm/bindings.zig | 32 +++++ src/print_air.zig | 2 + src/type.zig | 45 ++++-- src/value.zig | 224 +++++++++++++++++++++++++++++ test/behavior/atomics.zig | 29 ++++ test/behavior/atomics_stage1.zig | 29 ---- 12 files changed, 573 insertions(+), 128 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index 2834699d69..ad95200001 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -311,6 +311,12 @@ pub const Inst = struct { /// Given a pointer to an array, return a slice. /// Uses the `ty_op` field. array_to_slice, + /// Given a float operand, return the integer with the closest mathematical meaning. + /// Uses the `ty_op` field. + float_to_int, + /// Given an integer operand, return the float with the closest mathematical meaning. + /// Uses the `ty_op` field. + int_to_float, /// Uses the `ty_pl` field with payload `Cmpxchg`. cmpxchg_weak, @@ -598,6 +604,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .struct_field_ptr_index_2, .struct_field_ptr_index_3, .array_to_slice, + .float_to_int, + .int_to_float, => return air.getRefType(datas[inst].ty_op.ty), .loop, diff --git a/src/Liveness.zig b/src/Liveness.zig index 6e6a3ccf1f..1d34da091d 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -293,6 +293,8 @@ fn analyzeInst( .struct_field_ptr_index_2, .struct_field_ptr_index_3, .array_to_slice, + .float_to_int, + .int_to_float, => { const o = inst_datas[inst].ty_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index 5471795317..72dc25a58a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4830,8 +4830,8 @@ fn analyzeSwitch( var arena = std.heap.ArenaAllocator.init(gpa); defer arena.deinit(); - const min_int = try operand_ty.minInt(&arena, mod.getTarget()); - const max_int = try operand_ty.maxInt(&arena, mod.getTarget()); + const min_int = try operand_ty.minInt(&arena.allocator, mod.getTarget()); + const max_int = try operand_ty.maxInt(&arena.allocator, mod.getTarget()); if (try range_set.spans(min_int, max_int, operand_ty)) { if (special_prong == .@"else") { return mod.fail( @@ -5671,10 +5671,13 @@ fn zirBitwise( if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(resolved_type); - } - return sema.mod.fail(&block.base, src, "TODO implement comptime bitwise operations", .{}); + const result_val = switch (air_tag) { + .bit_and => try lhs_val.bitwiseAnd(rhs_val, sema.arena), + .bit_or => try lhs_val.bitwiseOr(rhs_val, sema.arena), + .xor => try lhs_val.bitwiseXor(rhs_val, sema.arena), + else => unreachable, + }; + return sema.addConstant(scalar_type, result_val); } } @@ -6028,8 +6031,8 @@ fn analyzeArithmetic( } if (zir_tag == .mod_rem) { - const dirty_lhs = lhs_ty.isSignedInt() or lhs_ty.isFloat(); - const dirty_rhs = rhs_ty.isSignedInt() or rhs_ty.isFloat(); + const dirty_lhs = lhs_ty.isSignedInt() or lhs_ty.isRuntimeFloat(); + const dirty_rhs = rhs_ty.isSignedInt() or rhs_ty.isRuntimeFloat(); if (dirty_lhs or dirty_rhs) { return sema.mod.fail(&block.base, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty }); } @@ -7298,13 +7301,30 @@ fn zirFrameSize(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE fn zirFloatToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); + // TODO don't forget the safety check! return sema.mod.fail(&block.base, src, "TODO: Sema.zirFloatToInt", .{}); } fn zirIntToFloat(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirIntToFloat", .{}); + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const dest_ty = try sema.resolveType(block, ty_src, extra.lhs); + const operand = sema.resolveInst(extra.rhs); + const operand_ty = sema.typeOf(operand); + + try sema.checkIntType(block, ty_src, dest_ty); + try sema.checkFloatType(block, operand_src, operand_ty); + + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + const target = sema.mod.getTarget(); + const result_val = try val.intToFloat(sema.arena, dest_ty, target); + return sema.addConstant(dest_ty, result_val); + } + + try sema.requireRuntimeBlock(block, operand_src); + return block.addTyOp(.int_to_float, dest_ty, operand); } fn zirIntToPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -7542,6 +7562,34 @@ fn zirOffsetOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.mod.fail(&block.base, src, "TODO: Sema.zirOffsetOf", .{}); } +fn checkIntType( + sema: *Sema, + block: *Scope.Block, + ty_src: LazySrcLoc, + ty: Type, +) CompileError!void { + switch (ty.zigTypeTag()) { + .ComptimeInt, .Int => {}, + else => return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{ + ty, + }), + } +} + +fn checkFloatType( + sema: *Sema, + block: *Scope.Block, + ty_src: LazySrcLoc, + ty: Type, +) CompileError!void { + switch (ty.zigTypeTag()) { + .ComptimeFloat, .Float => {}, + else => return sema.mod.fail(&block.base, ty_src, "expected float type, found '{}'", .{ + ty, + }), + } +} + fn checkAtomicOperandType( sema: *Sema, block: *Scope.Block, @@ -7815,9 +7863,23 @@ fn zirAtomicRmw(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: { if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |operand_val| { - _ = ptr_val; - _ = operand_val; - return mod.fail(&block.base, src, "TODO implement Sema for @atomicRmw at comptime", .{}); + const target = sema.mod.getTarget(); + const stored_val = (try ptr_val.pointerDeref(sema.arena)) orelse break :rs ptr_src; + const new_val = switch (op) { + // zig fmt: off + .Xchg => operand_val, + .Add => try stored_val.numberAddWrap(operand_val, operand_ty, sema.arena, target), + .Sub => try stored_val.numberSubWrap(operand_val, operand_ty, sema.arena, target), + .And => try stored_val.bitwiseAnd (operand_val, sema.arena), + .Nand => try stored_val.bitwiseNand (operand_val, operand_ty, sema.arena), + .Or => try stored_val.bitwiseOr (operand_val, sema.arena), + .Xor => try stored_val.bitwiseXor (operand_val, sema.arena), + .Max => try stored_val.numberMax (operand_val, sema.arena), + .Min => try stored_val.numberMin (operand_val, sema.arena), + // zig fmt: on + }; + try sema.storePtrVal(block, src, ptr_val, new_val, operand_ty); + return sema.addConstant(operand_ty, stored_val); } else break :rs operand_src; } else ptr_src; @@ -9298,33 +9360,38 @@ fn coerceNum( const target = sema.mod.getTarget(); - if (dst_zig_tag == .ComptimeInt or dst_zig_tag == .Int) { - if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { - if (val.floatHasFraction()) { - return sema.mod.fail(&block.base, inst_src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst_ty }); + switch (dst_zig_tag) { + .ComptimeInt, .Int => { + if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { + if (val.floatHasFraction()) { + return sema.mod.fail(&block.base, inst_src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst_ty }); + } + return sema.mod.fail(&block.base, inst_src, "TODO float to int", .{}); + } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { + if (!val.intFitsInType(dest_type, target)) { + return sema.mod.fail(&block.base, inst_src, "type {} cannot represent integer value {}", .{ dest_type, val }); + } + return try sema.addConstant(dest_type, val); } - return sema.mod.fail(&block.base, inst_src, "TODO float to int", .{}); - } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { - if (!val.intFitsInType(dest_type, target)) { - return sema.mod.fail(&block.base, inst_src, "type {} cannot represent integer value {}", .{ dest_type, val }); + }, + .ComptimeFloat, .Float => { + if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { + const res = val.floatCast(sema.arena, dest_type) catch |err| switch (err) { + error.Overflow => return sema.mod.fail( + &block.base, + inst_src, + "cast of value {} to type '{}' loses information", + .{ val, dest_type }, + ), + error.OutOfMemory => return error.OutOfMemory, + }; + return try sema.addConstant(dest_type, res); + } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { + const result_val = try val.intToFloat(sema.arena, dest_type, target); + return try sema.addConstant(dest_type, result_val); } - return try sema.addConstant(dest_type, val); - } - } else if (dst_zig_tag == .ComptimeFloat or dst_zig_tag == .Float) { - if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { - const res = val.floatCast(sema.arena, dest_type) catch |err| switch (err) { - error.Overflow => return sema.mod.fail( - &block.base, - inst_src, - "cast of value {} to type '{}' loses information", - .{ val, dest_type }, - ), - error.OutOfMemory => return error.OutOfMemory, - }; - return try sema.addConstant(dest_type, res); - } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { - return sema.mod.fail(&block.base, inst_src, "TODO int to float", .{}); - } + }, + else => {}, } return null; } @@ -9375,42 +9442,10 @@ fn storePtr2( return; const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: { - if (ptr_val.castTag(.decl_ref_mut)) |decl_ref_mut| { - const const_val = (try sema.resolveMaybeUndefVal(block, operand_src, operand)) orelse - return sema.mod.fail(&block.base, src, "cannot store runtime value in compile time variable", .{}); - - if (decl_ref_mut.data.runtime_index < block.runtime_index) { - if (block.runtime_cond) |cond_src| { - const msg = msg: { - const msg = try sema.mod.errMsg(&block.base, src, "store to comptime variable depends on runtime condition", .{}); - errdefer msg.destroy(sema.gpa); - try sema.mod.errNote(&block.base, cond_src, msg, "runtime condition here", .{}); - break :msg msg; - }; - return sema.mod.failWithOwnedErrorMsg(&block.base, msg); - } - if (block.runtime_loop) |loop_src| { - const msg = msg: { - const msg = try sema.mod.errMsg(&block.base, src, "cannot store to comptime variable in non-inline loop", .{}); - errdefer msg.destroy(sema.gpa); - try sema.mod.errNote(&block.base, loop_src, msg, "non-inline loop here", .{}); - break :msg msg; - }; - return sema.mod.failWithOwnedErrorMsg(&block.base, msg); - } - unreachable; - } - var new_arena = std.heap.ArenaAllocator.init(sema.gpa); - errdefer new_arena.deinit(); - const new_ty = try elem_ty.copy(&new_arena.allocator); - const new_val = try const_val.copy(&new_arena.allocator); - const decl = decl_ref_mut.data.decl; - var old_arena = decl.value_arena.?.promote(sema.gpa); - decl.value_arena = null; - try decl.finalizeNewArena(&new_arena); - decl.ty = new_ty; - decl.val = new_val; - old_arena.deinit(); + const operand_val = (try sema.resolveMaybeUndefVal(block, operand_src, operand)) orelse + return sema.mod.fail(&block.base, src, "cannot store runtime value in compile time variable", .{}); + if (ptr_val.tag() == .decl_ref_mut) { + try sema.storePtrVal(block, src, ptr_val, operand_val, elem_ty); return; } break :rs operand_src; @@ -9422,6 +9457,53 @@ fn storePtr2( _ = try block.addBinOp(air_tag, ptr, operand); } +/// Call when you have Value objects rather than Air instructions, and you want to +/// assert the store must be done at comptime. +fn storePtrVal( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + ptr_val: Value, + operand_val: Value, + operand_ty: Type, +) !void { + if (ptr_val.castTag(.decl_ref_mut)) |decl_ref_mut| { + if (decl_ref_mut.data.runtime_index < block.runtime_index) { + if (block.runtime_cond) |cond_src| { + const msg = msg: { + const msg = try sema.mod.errMsg(&block.base, src, "store to comptime variable depends on runtime condition", .{}); + errdefer msg.destroy(sema.gpa); + try sema.mod.errNote(&block.base, cond_src, msg, "runtime condition here", .{}); + break :msg msg; + }; + return sema.mod.failWithOwnedErrorMsg(&block.base, msg); + } + if (block.runtime_loop) |loop_src| { + const msg = msg: { + const msg = try sema.mod.errMsg(&block.base, src, "cannot store to comptime variable in non-inline loop", .{}); + errdefer msg.destroy(sema.gpa); + try sema.mod.errNote(&block.base, loop_src, msg, "non-inline loop here", .{}); + break :msg msg; + }; + return sema.mod.failWithOwnedErrorMsg(&block.base, msg); + } + unreachable; + } + var new_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_arena.deinit(); + const new_ty = try operand_ty.copy(&new_arena.allocator); + const new_val = try operand_val.copy(&new_arena.allocator); + const decl = decl_ref_mut.data.decl; + var old_arena = decl.value_arena.?.promote(sema.gpa); + decl.value_arena = null; + try decl.finalizeNewArena(&new_arena); + decl.ty = new_ty; + decl.val = new_val; + old_arena.deinit(); + return; + } +} + fn bitcast( sema: *Sema, block: *Scope.Block, @@ -9801,11 +9883,11 @@ fn cmpNumeric( const lhs_is_signed = if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| lhs_val.compareWithZero(.lt) else - (lhs_ty.isFloat() or lhs_ty.isSignedInt()); + (lhs_ty.isRuntimeFloat() or lhs_ty.isSignedInt()); const rhs_is_signed = if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| rhs_val.compareWithZero(.lt) else - (rhs_ty.isFloat() or rhs_ty.isSignedInt()); + (rhs_ty.isRuntimeFloat() or rhs_ty.isSignedInt()); const dest_int_is_signed = lhs_is_signed or rhs_is_signed; var dest_float_type: ?Type = null; @@ -10031,7 +10113,7 @@ fn resolvePeerTypes( } continue; } - if (chosen_ty.isFloat() and candidate_ty.isFloat()) { + if (chosen_ty.isRuntimeFloat() and candidate_ty.isRuntimeFloat()) { if (chosen_ty.floatBits(target) < candidate_ty.floatBits(target)) { chosen = candidate; chosen_i = candidate_i + 1; @@ -10049,13 +10131,13 @@ fn resolvePeerTypes( continue; } - if (chosen_ty.zigTypeTag() == .ComptimeFloat and candidate_ty.isFloat()) { + if (chosen_ty.zigTypeTag() == .ComptimeFloat and candidate_ty.isRuntimeFloat()) { chosen = candidate; chosen_i = candidate_i + 1; continue; } - if (chosen_ty.isFloat() and candidate_ty.zigTypeTag() == .ComptimeFloat) { + if (chosen_ty.isRuntimeFloat() and candidate_ty.zigTypeTag() == .ComptimeFloat) { continue; } diff --git a/src/codegen.zig b/src/codegen.zig index e0047de1f7..2a33795d0f 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -858,6 +858,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .struct_field_ptr=> try self.airStructFieldPtr(inst), .struct_field_val=> try self.airStructFieldVal(inst), .array_to_slice => try self.airArrayToSlice(inst), + .int_to_float => try self.airIntToFloat(inst), + .float_to_int => try self.airFloatToInt(inst), .cmpxchg_strong => try self.airCmpxchg(inst), .cmpxchg_weak => try self.airCmpxchg(inst), .atomic_rmw => try self.airAtomicRmw(inst), @@ -4769,6 +4771,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } + fn airIntToFloat(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement airIntToFloat for {}", .{ + self.target.cpu.arch, + }), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + + fn airFloatToInt(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement airFloatToInt for {}", .{ + self.target.cpu.arch, + }), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const extra = self.air.extraData(Air.Block, ty_pl.payload); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 5eb4388a9e..6da791cb46 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -917,6 +917,8 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .atomic_rmw => try airAtomicRmw(o, inst), .atomic_load => try airAtomicLoad(o, inst), + .int_to_float, .float_to_int => try airSimpleCast(o, inst), + .atomic_store_unordered => try airAtomicStore(o, inst, toMemoryOrder(.Unordered)), .atomic_store_monotonic => try airAtomicStore(o, inst, toMemoryOrder(.Monotonic)), .atomic_store_release => try airAtomicStore(o, inst, toMemoryOrder(.Release)), @@ -1899,6 +1901,24 @@ fn airArrayToSlice(o: *Object, inst: Air.Inst.Index) !CValue { return local; } +/// Emits a local variable with the result type and initializes it +/// with the operand. +fn airSimpleCast(o: *Object, inst: Air.Inst.Index) !CValue { + if (o.liveness.isUnused(inst)) + return CValue.none; + + const inst_ty = o.air.typeOfIndex(inst); + const local = try o.allocLocal(inst_ty, .Const); + const ty_op = o.air.instructions.items(.data)[inst].ty_op; + const writer = o.writer(); + const operand = try o.resolveInst(ty_op.operand); + + try writer.writeAll(" = "); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); + return local; +} + fn airCmpxchg(o: *Object, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue { const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; const extra = o.air.extraData(Air.Cmpxchg, ty_pl.payload).data; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 53e57ee219..2e835260af 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1122,6 +1122,8 @@ pub const FuncGen = struct { .slice_ptr => try self.airSliceField(inst, 0), .slice_len => try self.airSliceField(inst, 1), .array_to_slice => try self.airArrayToSlice(inst), + .float_to_int => try self.airFloatToInt(inst), + .int_to_float => try self.airIntToFloat(inst), .cmpxchg_weak => try self.airCmpxchg(inst, true), .cmpxchg_strong => try self.airCmpxchg(inst, false), .fence => try self.airFence(inst), @@ -1372,6 +1374,40 @@ pub const FuncGen = struct { return self.builder.buildInsertValue(partial, len, 1, ""); } + fn airIntToFloat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const dest_ty = self.air.typeOfIndex(inst); + const dest_llvm_ty = try self.dg.llvmType(dest_ty); + + if (dest_ty.isSignedInt()) { + return self.builder.buildSIToFP(operand, dest_llvm_ty, ""); + } else { + return self.builder.buildUIToFP(operand, dest_llvm_ty, ""); + } + } + + fn airFloatToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const dest_ty = self.air.typeOfIndex(inst); + const dest_llvm_ty = try self.dg.llvmType(dest_ty); + + // TODO set fast math flag + + if (dest_ty.isSignedInt()) { + return self.builder.buildFPToSI(operand, dest_llvm_ty, ""); + } else { + return self.builder.buildFPToUI(operand, dest_llvm_ty, ""); + } + } + fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1818,7 +1854,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isFloat()) return self.builder.buildFAdd(lhs, rhs, ""); + if (inst_ty.isRuntimeFloat()) return self.builder.buildFAdd(lhs, rhs, ""); if (wrap) return self.builder.buildAdd(lhs, rhs, ""); if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, ""); return self.builder.buildNUWAdd(lhs, rhs, ""); @@ -1833,7 +1869,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isFloat()) return self.builder.buildFSub(lhs, rhs, ""); + if (inst_ty.isRuntimeFloat()) return self.builder.buildFSub(lhs, rhs, ""); if (wrap) return self.builder.buildSub(lhs, rhs, ""); if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, ""); return self.builder.buildNUWSub(lhs, rhs, ""); @@ -1848,7 +1884,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isFloat()) return self.builder.buildFMul(lhs, rhs, ""); + if (inst_ty.isRuntimeFloat()) return self.builder.buildFMul(lhs, rhs, ""); if (wrap) return self.builder.buildMul(lhs, rhs, ""); if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, ""); return self.builder.buildNUWMul(lhs, rhs, ""); @@ -1863,7 +1899,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isFloat()) return self.builder.buildFDiv(lhs, rhs, ""); + if (inst_ty.isRuntimeFloat()) return self.builder.buildFDiv(lhs, rhs, ""); if (inst_ty.isSignedInt()) return self.builder.buildSDiv(lhs, rhs, ""); return self.builder.buildUDiv(lhs, rhs, ""); } @@ -1876,7 +1912,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isFloat()) return self.builder.buildFRem(lhs, rhs, ""); + if (inst_ty.isRuntimeFloat()) return self.builder.buildFRem(lhs, rhs, ""); if (inst_ty.isSignedInt()) return self.builder.buildSRem(lhs, rhs, ""); return self.builder.buildURem(lhs, rhs, ""); } @@ -2165,7 +2201,7 @@ pub const FuncGen = struct { const operand_ty = ptr_ty.elemType(); const operand = try self.resolveInst(extra.operand); const is_signed_int = operand_ty.isSignedInt(); - const is_float = operand_ty.isFloat(); + const is_float = operand_ty.isRuntimeFloat(); const op = toLlvmAtomicRmwBinOp(extra.op(), is_signed_int, is_float); const ordering = toLlvmAtomicOrdering(extra.ordering()); const single_threaded = llvm.Bool.fromBool(self.single_threaded); diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index db1dcd22f2..16445fa2d1 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -563,6 +563,38 @@ pub const Builder = opaque { ordering: AtomicOrdering, singleThread: Bool, ) *const Value; + + pub const buildFPToUI = LLVMBuildFPToUI; + extern fn LLVMBuildFPToUI( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; + + pub const buildFPToSI = LLVMBuildFPToSI; + extern fn LLVMBuildFPToSI( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; + + pub const buildUIToFP = LLVMBuildUIToFP; + extern fn LLVMBuildUIToFP( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; + + pub const buildSIToFP = LLVMBuildSIToFP; + extern fn LLVMBuildSIToFP( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; }; pub const IntPredicate = enum(c_uint) { diff --git a/src/print_air.zig b/src/print_air.zig index 39ae4251fa..3d13fa688f 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -175,6 +175,8 @@ const Writer = struct { .struct_field_ptr_index_2, .struct_field_ptr_index_3, .array_to_slice, + .int_to_float, + .float_to_int, => try w.writeTyOp(s, inst), .block, diff --git a/src/type.zig b/src/type.zig index ec73ae1196..122faefbc7 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2523,7 +2523,8 @@ pub const Type = extern union { }; } - pub fn isFloat(self: Type) bool { + /// Returns `false` for `comptime_float`. + pub fn isRuntimeFloat(self: Type) bool { return switch (self.tag()) { .f16, .f32, @@ -2536,13 +2537,29 @@ pub const Type = extern union { }; } - /// Asserts the type is a fixed-size float. + /// Returns `true` for `comptime_float`. + pub fn isAnyFloat(self: Type) bool { + return switch (self.tag()) { + .f16, + .f32, + .f64, + .f128, + .c_longdouble, + .comptime_float, + => true, + + else => false, + }; + } + + /// Asserts the type is a fixed-size float or comptime_float. + /// Returns 128 for comptime_float types. pub fn floatBits(self: Type, target: Target) u16 { return switch (self.tag()) { .f16 => 16, .f32 => 32, .f64 => 64, - .f128 => 128, + .f128, .comptime_float => 128, .c_longdouble => CType.longdouble.sizeInBits(target), else => unreachable, @@ -2879,7 +2896,7 @@ pub const Type = extern union { } /// Asserts that self.zigTypeTag() == .Int. - pub fn minInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value { + pub fn minInt(self: Type, arena: *Allocator, target: Target) !Value { assert(self.zigTypeTag() == .Int); const info = self.intInfo(target); @@ -2889,35 +2906,35 @@ pub const Type = extern union { if ((info.bits - 1) <= std.math.maxInt(u6)) { const n: i64 = -(@as(i64, 1) << @truncate(u6, info.bits - 1)); - return Value.Tag.int_i64.create(&arena.allocator, n); + return Value.Tag.int_i64.create(arena, n); } - var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1); + var res = try std.math.big.int.Managed.initSet(arena, 1); try res.shiftLeft(res, info.bits - 1); res.negate(); const res_const = res.toConst(); if (res_const.positive) { - return Value.Tag.int_big_positive.create(&arena.allocator, res_const.limbs); + return Value.Tag.int_big_positive.create(arena, res_const.limbs); } else { - return Value.Tag.int_big_negative.create(&arena.allocator, res_const.limbs); + return Value.Tag.int_big_negative.create(arena, res_const.limbs); } } /// Asserts that self.zigTypeTag() == .Int. - pub fn maxInt(self: Type, arena: *std.heap.ArenaAllocator, target: Target) !Value { + pub fn maxInt(self: Type, arena: *Allocator, target: Target) !Value { assert(self.zigTypeTag() == .Int); const info = self.intInfo(target); if (info.signedness == .signed and (info.bits - 1) <= std.math.maxInt(u6)) { const n: i64 = (@as(i64, 1) << @truncate(u6, info.bits - 1)) - 1; - return Value.Tag.int_i64.create(&arena.allocator, n); + return Value.Tag.int_i64.create(arena, n); } else if (info.signedness == .signed and info.bits <= std.math.maxInt(u6)) { const n: u64 = (@as(u64, 1) << @truncate(u6, info.bits)) - 1; - return Value.Tag.int_u64.create(&arena.allocator, n); + return Value.Tag.int_u64.create(arena, n); } - var res = try std.math.big.int.Managed.initSet(&arena.allocator, 1); + var res = try std.math.big.int.Managed.initSet(arena, 1); try res.shiftLeft(res, info.bits - @boolToInt(info.signedness == .signed)); const one = std.math.big.int.Const{ .limbs = &[_]std.math.big.Limb{1}, @@ -2927,9 +2944,9 @@ pub const Type = extern union { const res_const = res.toConst(); if (res_const.positive) { - return Value.Tag.int_big_positive.create(&arena.allocator, res_const.limbs); + return Value.Tag.int_big_positive.create(arena, res_const.limbs); } else { - return Value.Tag.int_big_negative.create(&arena.allocator, res_const.limbs); + return Value.Tag.int_big_negative.create(arena, res_const.limbs); } } diff --git a/src/value.zig b/src/value.zig index 88d0d04086..177359d652 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1524,6 +1524,230 @@ pub const Value = extern union { }; } + pub fn intToFloat(val: Value, allocator: *Allocator, dest_ty: Type, target: Target) !Value { + switch (val.tag()) { + .undef, .zero, .one => return val, + .int_u64 => { + return intToFloatInner(val.castTag(.int_u64).?.data, allocator, dest_ty, target); + }, + .int_i64 => { + return intToFloatInner(val.castTag(.int_i64).?.data, allocator, dest_ty, target); + }, + .int_big_positive, .int_big_negative => @panic("big int to float"), + else => unreachable, + } + } + + fn intToFloatInner(x: anytype, arena: *Allocator, dest_ty: Type, target: Target) !Value { + switch (dest_ty.floatBits(target)) { + 16 => return Value.Tag.float_16.create(arena, @intToFloat(f16, x)), + 32 => return Value.Tag.float_32.create(arena, @intToFloat(f32, x)), + 64 => return Value.Tag.float_64.create(arena, @intToFloat(f64, x)), + 128 => return Value.Tag.float_128.create(arena, @intToFloat(f128, x)), + else => unreachable, + } + } + + /// Supports both floats and ints; handles undefined. + pub fn numberAddWrap( + lhs: Value, + rhs: Value, + ty: Type, + arena: *Allocator, + target: Target, + ) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + if (ty.isAnyFloat()) { + return floatAdd(lhs, rhs, ty, arena); + } + const result = try intAdd(lhs, rhs, arena); + + const max = try ty.maxInt(arena, target); + if (compare(result, .gt, max, ty)) { + @panic("TODO comptime wrapping integer addition"); + } + + const min = try ty.minInt(arena, target); + if (compare(result, .lt, min, ty)) { + @panic("TODO comptime wrapping integer addition"); + } + + return result; + } + + /// Supports both floats and ints; handles undefined. + pub fn numberSubWrap( + lhs: Value, + rhs: Value, + ty: Type, + arena: *Allocator, + target: Target, + ) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + if (ty.isAnyFloat()) { + return floatSub(lhs, rhs, ty, arena); + } + const result = try intSub(lhs, rhs, arena); + + const max = try ty.maxInt(arena, target); + if (compare(result, .gt, max, ty)) { + @panic("TODO comptime wrapping integer subtraction"); + } + + const min = try ty.minInt(arena, target); + if (compare(result, .lt, min, ty)) { + @panic("TODO comptime wrapping integer subtraction"); + } + + return result; + } + + /// Supports both floats and ints; handles undefined. + pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try arena.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + + switch (lhs_bigint.order(rhs_bigint)) { + .lt => result_bigint.copy(rhs_bigint), + .gt, .eq => result_bigint.copy(lhs_bigint), + } + + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(arena, result_limbs); + } else { + return Value.Tag.int_big_negative.create(arena, result_limbs); + } + } + + /// Supports both floats and ints; handles undefined. + pub fn numberMin(lhs: Value, rhs: Value, arena: *Allocator) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try arena.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + + switch (lhs_bigint.order(rhs_bigint)) { + .lt => result_bigint.copy(lhs_bigint), + .gt, .eq => result_bigint.copy(rhs_bigint), + } + + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(arena, result_limbs); + } else { + return Value.Tag.int_big_negative.create(arena, result_limbs); + } + } + + /// operands must be integers; handles undefined. + pub fn bitwiseAnd(lhs: Value, rhs: Value, arena: *Allocator) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try arena.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.bitAnd(lhs_bigint, rhs_bigint); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(arena, result_limbs); + } else { + return Value.Tag.int_big_negative.create(arena, result_limbs); + } + } + + /// operands must be integers; handles undefined. + pub fn bitwiseNand(lhs: Value, rhs: Value, ty: Type, arena: *Allocator) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + _ = ty; + _ = arena; + @panic("TODO comptime bitwise NAND"); + } + + /// operands must be integers; handles undefined. + pub fn bitwiseOr(lhs: Value, rhs: Value, arena: *Allocator) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try arena.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.bitOr(lhs_bigint, rhs_bigint); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(arena, result_limbs); + } else { + return Value.Tag.int_big_negative.create(arena, result_limbs); + } + } + + /// operands must be integers; handles undefined. + pub fn bitwiseXor(lhs: Value, rhs: Value, arena: *Allocator) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try arena.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.bitXor(lhs_bigint, rhs_bigint); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(arena, result_limbs); + } else { + return Value.Tag.int_big_negative.create(arena, result_limbs); + } + } + pub fn intAdd(lhs: Value, rhs: Value, allocator: *Allocator) !Value { // TODO is this a performance issue? maybe we should try the operation without // resorting to BigInt first. diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index 75e33477bc..311fb4b3b2 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -138,3 +138,32 @@ test "atomic store" { @atomicStore(u32, &x, 12345678, .SeqCst); try expect(@atomicLoad(u32, &x, .SeqCst) == 12345678); } + +test "atomic store comptime" { + comptime try testAtomicStore(); + try testAtomicStore(); +} + +fn testAtomicStore() !void { + var x: u32 = 0; + @atomicStore(u32, &x, 1, .SeqCst); + try expect(@atomicLoad(u32, &x, .SeqCst) == 1); + @atomicStore(u32, &x, 12345678, .SeqCst); + try expect(@atomicLoad(u32, &x, .SeqCst) == 12345678); +} + +test "atomicrmw with floats" { + try testAtomicRmwFloat(); + comptime try testAtomicRmwFloat(); +} + +fn testAtomicRmwFloat() !void { + var x: f32 = 0; + try expect(x == 0); + _ = @atomicRmw(f32, &x, .Xchg, 1, .SeqCst); + try expect(x == 1); + _ = @atomicRmw(f32, &x, .Add, 5, .SeqCst); + try expect(x == 6); + _ = @atomicRmw(f32, &x, .Sub, 2, .SeqCst); + try expect(x == 4); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index 22e3ae5939..424f33b403 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -3,35 +3,6 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const builtin = @import("builtin"); -test "atomic store comptime" { - comptime try testAtomicStore(); - try testAtomicStore(); -} - -fn testAtomicStore() !void { - var x: u32 = 0; - @atomicStore(u32, &x, 1, .SeqCst); - try expect(@atomicLoad(u32, &x, .SeqCst) == 1); - @atomicStore(u32, &x, 12345678, .SeqCst); - try expect(@atomicLoad(u32, &x, .SeqCst) == 12345678); -} - -test "atomicrmw with floats" { - try testAtomicRmwFloat(); - comptime try testAtomicRmwFloat(); -} - -fn testAtomicRmwFloat() !void { - var x: f32 = 0; - try expect(x == 0); - _ = @atomicRmw(f32, &x, .Xchg, 1, .SeqCst); - try expect(x == 1); - _ = @atomicRmw(f32, &x, .Add, 5, .SeqCst); - try expect(x == 6); - _ = @atomicRmw(f32, &x, .Sub, 2, .SeqCst); - try expect(x == 4); -} - test "atomicrmw with ints" { try testAtomicRmwInt(); comptime try testAtomicRmwInt(); From f3147de7a28767a27406355fd2d93d1bbcec9437 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 20 Sep 2021 14:45:40 -0700 Subject: [PATCH 075/160] stage2: extract ZIR printing code into print_zir.zig also implement textual printing of the ZIR instruction `atomic_rmw`. --- CMakeLists.txt | 22 +- src/Zir.zig | 1844 -------------------------------------------- src/main.zig | 2 +- src/print_zir.zig | 1868 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1881 insertions(+), 1855 deletions(-) create mode 100644 src/print_zir.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 54ff9b6179..42c0b7e0da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -537,16 +537,20 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/zig/system.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/system/x86.zig" "${CMAKE_SOURCE_DIR}/lib/std/zig/tokenizer.zig" + "${CMAKE_SOURCE_DIR}/src/Air.zig" + "${CMAKE_SOURCE_DIR}/src/AstGen.zig" "${CMAKE_SOURCE_DIR}/src/Cache.zig" "${CMAKE_SOURCE_DIR}/src/Compilation.zig" "${CMAKE_SOURCE_DIR}/src/DepTokenizer.zig" + "${CMAKE_SOURCE_DIR}/src/Liveness.zig" "${CMAKE_SOURCE_DIR}/src/Module.zig" "${CMAKE_SOURCE_DIR}/src/Package.zig" "${CMAKE_SOURCE_DIR}/src/RangeSet.zig" + "${CMAKE_SOURCE_DIR}/src/Sema.zig" "${CMAKE_SOURCE_DIR}/src/ThreadPool.zig" "${CMAKE_SOURCE_DIR}/src/TypedValue.zig" "${CMAKE_SOURCE_DIR}/src/WaitGroup.zig" - "${CMAKE_SOURCE_DIR}/src/AstGen.zig" + "${CMAKE_SOURCE_DIR}/src/Zir.zig" "${CMAKE_SOURCE_DIR}/src/clang.zig" "${CMAKE_SOURCE_DIR}/src/clang_options.zig" "${CMAKE_SOURCE_DIR}/src/clang_options_data.zig" @@ -561,17 +565,15 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/codegen/x86_64.zig" "${CMAKE_SOURCE_DIR}/src/glibc.zig" "${CMAKE_SOURCE_DIR}/src/introspect.zig" - "${CMAKE_SOURCE_DIR}/src/Air.zig" "${CMAKE_SOURCE_DIR}/src/libc_installation.zig" "${CMAKE_SOURCE_DIR}/src/libcxx.zig" "${CMAKE_SOURCE_DIR}/src/libtsan.zig" "${CMAKE_SOURCE_DIR}/src/libunwind.zig" "${CMAKE_SOURCE_DIR}/src/link.zig" "${CMAKE_SOURCE_DIR}/src/link/C.zig" + "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" "${CMAKE_SOURCE_DIR}/src/link/Coff.zig" "${CMAKE_SOURCE_DIR}/src/link/Elf.zig" - "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig" - "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Atom.zig" @@ -582,20 +584,22 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/bind.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/commands.zig" + "${CMAKE_SOURCE_DIR}/src/link/Plan9.zig" + "${CMAKE_SOURCE_DIR}/src/link/Plan9/aout.zig" "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" + "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" "${CMAKE_SOURCE_DIR}/src/link/tapi.zig" + "${CMAKE_SOURCE_DIR}/src/link/tapi/Tokenizer.zig" "${CMAKE_SOURCE_DIR}/src/link/tapi/parse.zig" "${CMAKE_SOURCE_DIR}/src/link/tapi/parse/test.zig" - "${CMAKE_SOURCE_DIR}/src/link/tapi/Tokenizer.zig" "${CMAKE_SOURCE_DIR}/src/link/tapi/yaml.zig" - "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" - "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" - "${CMAKE_SOURCE_DIR}/src/Liveness.zig" "${CMAKE_SOURCE_DIR}/src/main.zig" "${CMAKE_SOURCE_DIR}/src/mingw.zig" "${CMAKE_SOURCE_DIR}/src/musl.zig" + "${CMAKE_SOURCE_DIR}/src/print_air.zig" "${CMAKE_SOURCE_DIR}/src/print_env.zig" "${CMAKE_SOURCE_DIR}/src/print_targets.zig" + "${CMAKE_SOURCE_DIR}/src/print_zir.zig" "${CMAKE_SOURCE_DIR}/src/stage1.zig" "${CMAKE_SOURCE_DIR}/src/target.zig" "${CMAKE_SOURCE_DIR}/src/tracy.zig" @@ -605,8 +609,6 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/value.zig" "${CMAKE_SOURCE_DIR}/src/wasi_libc.zig" "${CMAKE_SOURCE_DIR}/src/windows_sdk.zig" - "${CMAKE_SOURCE_DIR}/src/Zir.zig" - "${CMAKE_SOURCE_DIR}/src/Sema.zig" ) if(MSVC) diff --git a/src/Zir.zig b/src/Zir.zig index f8dbef2534..f4c3e58eb0 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -112,51 +112,6 @@ pub fn deinit(code: *Zir, gpa: *Allocator) void { code.* = undefined; } -/// Write human-readable, debug formatted ZIR code to a file. -pub fn renderAsTextToFile( - gpa: *Allocator, - scope_file: *Module.Scope.File, - fs_file: std.fs.File, -) !void { - var arena = std.heap.ArenaAllocator.init(gpa); - defer arena.deinit(); - - var writer: Writer = .{ - .gpa = gpa, - .arena = &arena.allocator, - .file = scope_file, - .code = scope_file.zir, - .indent = 0, - .parent_decl_node = 0, - }; - - const main_struct_inst = scope_file.zir.getMainStruct(); - try fs_file.writer().print("%{d} ", .{main_struct_inst}); - try writer.writeInstToStream(fs_file.writer(), main_struct_inst); - try fs_file.writeAll("\n"); - const imports_index = scope_file.zir.extra[@enumToInt(ExtraIndex.imports)]; - if (imports_index != 0) { - try fs_file.writeAll("Imports:\n"); - - const extra = scope_file.zir.extraData(Inst.Imports, imports_index); - var import_i: u32 = 0; - var extra_index = extra.end; - - while (import_i < extra.data.imports_len) : (import_i += 1) { - const item = scope_file.zir.extraData(Inst.Imports.Item, extra_index); - extra_index = item.end; - - const src: LazySrcLoc = .{ .token_abs = item.data.token }; - const import_path = scope_file.zir.nullTerminatedString(item.data.name); - try fs_file.writer().print(" @import(\"{}\") ", .{ - std.zig.fmtEscapes(import_path), - }); - try writer.writeSrc(fs_file.writer(), src); - try fs_file.writer().writeAll("\n"); - } - } -} - /// These are untyped instructions generated from an Abstract Syntax Tree. /// The data here is immutable because it is possible to have multiple /// analyses on the same ZIR happening at the same time. @@ -2904,1805 +2859,6 @@ pub const Inst = struct { pub const SpecialProng = enum { none, @"else", under }; -const Writer = struct { - gpa: *Allocator, - arena: *Allocator, - file: *Module.Scope.File, - code: Zir, - indent: u32, - parent_decl_node: u32, - - fn relativeToNodeIndex(self: *Writer, offset: i32) Ast.Node.Index { - return @bitCast(Ast.Node.Index, offset + @bitCast(i32, self.parent_decl_node)); - } - - fn writeInstToStream( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const tags = self.code.instructions.items(.tag); - const tag = tags[inst]; - try stream.print("= {s}(", .{@tagName(tags[inst])}); - switch (tag) { - .array_type, - .as, - .coerce_result_ptr, - .elem_ptr, - .elem_val, - .store, - .store_to_block_ptr, - .store_to_inferred_ptr, - .field_ptr_type, - => try self.writeBin(stream, inst), - - .alloc, - .alloc_mut, - .alloc_comptime, - .indexable_ptr_len, - .anyframe_type, - .bit_not, - .bool_not, - .negate, - .negate_wrap, - .load, - .ensure_result_used, - .ensure_result_non_error, - .ret_node, - .ret_load, - .resolve_inferred_alloc, - .optional_type, - .optional_payload_safe, - .optional_payload_unsafe, - .optional_payload_safe_ptr, - .optional_payload_unsafe_ptr, - .err_union_payload_safe, - .err_union_payload_unsafe, - .err_union_payload_safe_ptr, - .err_union_payload_unsafe_ptr, - .err_union_code, - .err_union_code_ptr, - .is_non_null, - .is_non_null_ptr, - .is_non_err, - .is_non_err_ptr, - .typeof, - .typeof_elem, - .struct_init_empty, - .type_info, - .size_of, - .bit_size_of, - .typeof_log2_int_type, - .log2_int_type, - .ptr_to_int, - .error_to_int, - .int_to_error, - .compile_error, - .set_eval_branch_quota, - .enum_to_int, - .align_of, - .bool_to_int, - .embed_file, - .error_name, - .panic, - .set_align_stack, - .set_cold, - .set_float_mode, - .set_runtime_safety, - .sqrt, - .sin, - .cos, - .exp, - .exp2, - .log, - .log2, - .log10, - .fabs, - .floor, - .ceil, - .trunc, - .round, - .tag_name, - .reify, - .type_name, - .frame_type, - .frame_size, - .clz, - .ctz, - .pop_count, - .byte_swap, - .bit_reverse, - .elem_type, - .@"resume", - .@"await", - .await_nosuspend, - .fence, - => try self.writeUnNode(stream, inst), - - .ref, - .ret_coerce, - .ensure_err_payload_void, - => try self.writeUnTok(stream, inst), - - .bool_br_and, - .bool_br_or, - => try self.writeBoolBr(stream, inst), - - .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), - .param_type => try self.writeParamType(stream, inst), - .ptr_type_simple => try self.writePtrTypeSimple(stream, inst), - .ptr_type => try self.writePtrType(stream, inst), - .int => try self.writeInt(stream, inst), - .int_big => try self.writeIntBig(stream, inst), - .float => try self.writeFloat(stream, inst), - .float128 => try self.writeFloat128(stream, inst), - .str => try self.writeStr(stream, inst), - .int_type => try self.writeIntType(stream, inst), - - .@"break", - .break_inline, - => try self.writeBreak(stream, inst), - - .elem_ptr_node, - .elem_val_node, - .field_ptr_named, - .field_val_named, - .slice_start, - .slice_end, - .slice_sentinel, - .array_init, - .array_init_anon, - .array_init_ref, - .array_init_anon_ref, - .union_init_ptr, - .shuffle, - .select, - .atomic_rmw, - .mul_add, - .builtin_call, - .field_parent_ptr, - .memcpy, - .memset, - .builtin_async_call, - => try self.writePlNode(stream, inst), - - .struct_init, - .struct_init_ref, - => try self.writeStructInit(stream, inst), - - .cmpxchg_strong, .cmpxchg_weak => try self.writeCmpxchg(stream, inst), - .atomic_store => try self.writeAtomicStore(stream, inst), - - .struct_init_anon, - .struct_init_anon_ref, - => try self.writeStructInitAnon(stream, inst), - - .field_type => try self.writeFieldType(stream, inst), - .field_type_ref => try self.writeFieldTypeRef(stream, inst), - - .add, - .addwrap, - .array_cat, - .array_mul, - .mul, - .mulwrap, - .sub, - .subwrap, - .cmp_lt, - .cmp_lte, - .cmp_eq, - .cmp_gte, - .cmp_gt, - .cmp_neq, - .div, - .has_decl, - .has_field, - .mod_rem, - .shl, - .shl_exact, - .shr, - .shr_exact, - .xor, - .store_node, - .error_union_type, - .merge_error_sets, - .bit_and, - .bit_or, - .float_to_int, - .int_to_float, - .int_to_ptr, - .int_to_enum, - .float_cast, - .int_cast, - .err_set_cast, - .ptr_cast, - .truncate, - .align_cast, - .div_exact, - .div_floor, - .div_trunc, - .mod, - .rem, - .bit_offset_of, - .offset_of, - .splat, - .reduce, - .atomic_load, - .bitcast, - .bitcast_result_ptr, - .vector_type, - .maximum, - .minimum, - => try self.writePlNodeBin(stream, inst), - - .@"export" => try self.writePlNodeExport(stream, inst), - - .call, - .call_chkused, - .call_compile_time, - .call_nosuspend, - .call_async, - => try self.writePlNodeCall(stream, inst), - - .block, - .block_inline, - .suspend_block, - .loop, - .validate_struct_init_ptr, - .validate_array_init_ptr, - .c_import, - => try self.writePlNodeBlock(stream, inst), - - .condbr, - .condbr_inline, - => try self.writePlNodeCondBr(stream, inst), - - .opaque_decl => try self.writeOpaqueDecl(stream, inst, .parent), - .opaque_decl_anon => try self.writeOpaqueDecl(stream, inst, .anon), - .opaque_decl_func => try self.writeOpaqueDecl(stream, inst, .func), - - .error_set_decl => try self.writeErrorSetDecl(stream, inst, .parent), - .error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon), - .error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func), - - .switch_block => try self.writePlNodeSwitchBr(stream, inst, .none), - .switch_block_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"), - .switch_block_under => try self.writePlNodeSwitchBr(stream, inst, .under), - .switch_block_ref => try self.writePlNodeSwitchBr(stream, inst, .none), - .switch_block_ref_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"), - .switch_block_ref_under => try self.writePlNodeSwitchBr(stream, inst, .under), - - .switch_block_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none), - .switch_block_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"), - .switch_block_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under), - .switch_block_ref_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none), - .switch_block_ref_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"), - .switch_block_ref_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under), - - .field_ptr, - .field_val, - => try self.writePlNodeField(stream, inst), - - .as_node => try self.writeAs(stream, inst), - - .breakpoint, - .repeat, - .repeat_inline, - .alloc_inferred, - .alloc_inferred_mut, - .alloc_inferred_comptime, - => try self.writeNode(stream, inst), - - .error_value, - .enum_literal, - .decl_ref, - .decl_val, - .import, - .ret_err_value, - .ret_err_value_code, - .param_anytype, - .param_anytype_comptime, - => try self.writeStrTok(stream, inst), - - .param, .param_comptime => try self.writeParam(stream, inst), - - .func => try self.writeFunc(stream, inst, false), - .func_inferred => try self.writeFunc(stream, inst, true), - - .@"unreachable" => try self.writeUnreachable(stream, inst), - - .switch_capture, - .switch_capture_ref, - .switch_capture_multi, - .switch_capture_multi_ref, - .switch_capture_else, - .switch_capture_else_ref, - => try self.writeSwitchCapture(stream, inst), - - .dbg_stmt => try self.writeDbgStmt(stream, inst), - - .extended => try self.writeExtended(stream, inst), - } - } - - fn writeExtended(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const extended = self.code.instructions.items(.data)[inst].extended; - try stream.print("{s}(", .{@tagName(extended.opcode)}); - switch (extended.opcode) { - .ret_ptr, - .ret_type, - .this, - .ret_addr, - .error_return_trace, - .frame, - .frame_address, - .builtin_src, - => try self.writeExtNode(stream, extended), - - .@"asm" => try self.writeAsm(stream, extended), - .func => try self.writeFuncExtended(stream, extended), - .variable => try self.writeVarExtended(stream, extended), - - .compile_log, - .typeof_peer, - => try self.writeNodeMultiOp(stream, extended), - - .add_with_overflow, - .sub_with_overflow, - .mul_with_overflow, - .shl_with_overflow, - => try self.writeOverflowArithmetic(stream, extended), - - .add_with_saturation, - .sub_with_saturation, - .mul_with_saturation, - .shl_with_saturation, - => try self.writeSaturatingArithmetic(stream, extended), - .struct_decl => try self.writeStructDecl(stream, extended), - .union_decl => try self.writeUnionDecl(stream, extended), - .enum_decl => try self.writeEnumDecl(stream, extended), - - .alloc, - .builtin_extern, - .c_undef, - .c_include, - .c_define, - .wasm_memory_size, - .wasm_memory_grow, - => try stream.writeAll("TODO))"), - } - } - - fn writeExtNode(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; - try stream.writeAll(")) "); - try self.writeSrc(stream, src); - } - - fn writeBin(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].bin; - try self.writeInstRef(stream, inst_data.lhs); - try stream.writeAll(", "); - try self.writeInstRef(stream, inst_data.rhs); - try stream.writeByte(')'); - } - - fn writeUnNode( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].un_node; - try self.writeInstRef(stream, inst_data.operand); - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeUnTok( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].un_tok; - try self.writeInstRef(stream, inst_data.operand); - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeArrayTypeSentinel( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].array_type_sentinel; - _ = inst_data; - try stream.writeAll("TODO)"); - } - - fn writeParamType( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].param_type; - try self.writeInstRef(stream, inst_data.callee); - try stream.print(", {d})", .{inst_data.param_index}); - } - - fn writePtrTypeSimple( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].ptr_type_simple; - const str_allowzero = if (inst_data.is_allowzero) "allowzero, " else ""; - const str_const = if (!inst_data.is_mutable) "const, " else ""; - const str_volatile = if (inst_data.is_volatile) "volatile, " else ""; - try self.writeInstRef(stream, inst_data.elem_type); - try stream.print(", {s}{s}{s}{s})", .{ - str_allowzero, - str_const, - str_volatile, - @tagName(inst_data.size), - }); - } - - fn writePtrType( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].ptr_type; - _ = inst_data; - try stream.writeAll("TODO)"); - } - - fn writeInt(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].int; - try stream.print("{d})", .{inst_data}); - } - - fn writeIntBig(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].str; - const byte_count = inst_data.len * @sizeOf(std.math.big.Limb); - const limb_bytes = self.code.string_bytes[inst_data.start..][0..byte_count]; - // limb_bytes is not aligned properly; we must allocate and copy the bytes - // in order to accomplish this. - const limbs = try self.gpa.alloc(std.math.big.Limb, inst_data.len); - defer self.gpa.free(limbs); - - mem.copy(u8, mem.sliceAsBytes(limbs), limb_bytes); - const big_int: std.math.big.int.Const = .{ - .limbs = limbs, - .positive = true, - }; - const as_string = try big_int.toStringAlloc(self.gpa, 10, .lower); - defer self.gpa.free(as_string); - try stream.print("{s})", .{as_string}); - } - - fn writeFloat(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const number = self.code.instructions.items(.data)[inst].float; - try stream.print("{d})", .{number}); - } - - fn writeFloat128(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.Float128, inst_data.payload_index).data; - const src = inst_data.src(); - const number = extra.get(); - // TODO improve std.format to be able to print f128 values - try stream.print("{d}) ", .{@floatCast(f64, number)}); - try self.writeSrc(stream, src); - } - - fn writeStr( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].str; - const str = inst_data.get(self.code); - try stream.print("\"{}\")", .{std.zig.fmtEscapes(str)}); - } - - fn writePlNode(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - try stream.writeAll("TODO) "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeParam(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_tok; - const extra = self.code.extraData(Inst.Param, inst_data.payload_index); - const body = self.code.extra[extra.end..][0..extra.data.body_len]; - try stream.print("\"{}\", ", .{ - std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)), - }); - try stream.writeAll("{\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writePlNodeBin(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.Bin, inst_data.payload_index).data; - try self.writeInstRef(stream, extra.lhs); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.rhs); - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writePlNodeExport(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.Export, inst_data.payload_index).data; - const decl_name = self.code.nullTerminatedString(extra.decl_name); - - try self.writeInstRef(stream, extra.namespace); - try stream.print(", {}, ", .{std.zig.fmtId(decl_name)}); - try self.writeInstRef(stream, extra.options); - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeStructInit(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.StructInit, inst_data.payload_index); - var field_i: u32 = 0; - var extra_index = extra.end; - - while (field_i < extra.data.fields_len) : (field_i += 1) { - const item = self.code.extraData(Inst.StructInit.Item, extra_index); - extra_index = item.end; - - if (field_i != 0) { - try stream.writeAll(", ["); - } else { - try stream.writeAll("["); - } - try self.writeInstIndex(stream, item.data.field_type); - try stream.writeAll(", "); - try self.writeInstRef(stream, item.data.init); - try stream.writeAll("]"); - } - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeCmpxchg(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.Cmpxchg, inst_data.payload_index).data; - - try self.writeInstRef(stream, extra.ptr); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.expected_value); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.new_value); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.success_order); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.failure_order); - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeAtomicStore(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.AtomicStore, inst_data.payload_index).data; - - try self.writeInstRef(stream, extra.ptr); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.operand); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.ordering); - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeStructInitAnon(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.StructInitAnon, inst_data.payload_index); - var field_i: u32 = 0; - var extra_index = extra.end; - - while (field_i < extra.data.fields_len) : (field_i += 1) { - const item = self.code.extraData(Inst.StructInitAnon.Item, extra_index); - extra_index = item.end; - - const field_name = self.code.nullTerminatedString(item.data.field_name); - - const prefix = if (field_i != 0) ", [" else "["; - try stream.print("{s}[{s}=", .{ prefix, field_name }); - try self.writeInstRef(stream, item.data.init); - try stream.writeAll("]"); - } - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeFieldType(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.FieldType, inst_data.payload_index).data; - try self.writeInstRef(stream, extra.container_type); - const field_name = self.code.nullTerminatedString(extra.name_start); - try stream.print(", {s}) ", .{field_name}); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeFieldTypeRef(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.FieldTypeRef, inst_data.payload_index).data; - try self.writeInstRef(stream, extra.container_type); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.field_name); - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeNodeMultiOp(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const extra = self.code.extraData(Inst.NodeMultiOp, extended.operand); - const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; - const operands = self.code.refSlice(extra.end, extended.small); - - for (operands) |operand, i| { - if (i != 0) try stream.writeAll(", "); - try self.writeInstRef(stream, operand); - } - try stream.writeAll(")) "); - try self.writeSrc(stream, src); - } - - fn writeAsm(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const extra = self.code.extraData(Inst.Asm, extended.operand); - const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; - const outputs_len = @truncate(u5, extended.small); - const inputs_len = @truncate(u5, extended.small >> 5); - const clobbers_len = @truncate(u5, extended.small >> 10); - const is_volatile = @truncate(u1, extended.small >> 15) != 0; - const asm_source = self.code.nullTerminatedString(extra.data.asm_source); - - try self.writeFlag(stream, "volatile, ", is_volatile); - try stream.print("\"{}\", ", .{std.zig.fmtEscapes(asm_source)}); - try stream.writeAll(", "); - - var extra_i: usize = extra.end; - var output_type_bits = extra.data.output_type_bits; - { - var i: usize = 0; - while (i < outputs_len) : (i += 1) { - const output = self.code.extraData(Inst.Asm.Output, extra_i); - extra_i = output.end; - - const is_type = @truncate(u1, output_type_bits) != 0; - output_type_bits >>= 1; - - const name = self.code.nullTerminatedString(output.data.name); - const constraint = self.code.nullTerminatedString(output.data.constraint); - try stream.print("output({}, \"{}\", ", .{ - std.zig.fmtId(name), std.zig.fmtEscapes(constraint), - }); - try self.writeFlag(stream, "->", is_type); - try self.writeInstRef(stream, output.data.operand); - try stream.writeAll(")"); - if (i + 1 < outputs_len) { - try stream.writeAll("), "); - } - } - } - { - var i: usize = 0; - while (i < inputs_len) : (i += 1) { - const input = self.code.extraData(Inst.Asm.Input, extra_i); - extra_i = input.end; - - const name = self.code.nullTerminatedString(input.data.name); - const constraint = self.code.nullTerminatedString(input.data.constraint); - try stream.print("input({}, \"{}\", ", .{ - std.zig.fmtId(name), std.zig.fmtEscapes(constraint), - }); - try self.writeInstRef(stream, input.data.operand); - try stream.writeAll(")"); - if (i + 1 < inputs_len) { - try stream.writeAll(", "); - } - } - } - { - var i: usize = 0; - while (i < clobbers_len) : (i += 1) { - const str_index = self.code.extra[extra_i]; - extra_i += 1; - const clobber = self.code.nullTerminatedString(str_index); - try stream.print("{}", .{std.zig.fmtId(clobber)}); - if (i + 1 < clobbers_len) { - try stream.writeAll(", "); - } - } - } - try stream.writeAll(")) "); - try self.writeSrc(stream, src); - } - - fn writeOverflowArithmetic(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const extra = self.code.extraData(Zir.Inst.OverflowArithmetic, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; - - try self.writeInstRef(stream, extra.lhs); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.rhs); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.ptr); - try stream.writeAll(")) "); - try self.writeSrc(stream, src); - } - - fn writeSaturatingArithmetic(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const extra = self.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; - - try self.writeInstRef(stream, extra.lhs); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.rhs); - try stream.writeAll(", "); - try stream.writeAll(") "); - try self.writeSrc(stream, src); - } - - fn writePlNodeCall(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.Call, inst_data.payload_index); - const args = self.code.refSlice(extra.end, extra.data.args_len); - - try self.writeInstRef(stream, extra.data.callee); - try stream.writeAll(", ["); - for (args) |arg, i| { - if (i != 0) try stream.writeAll(", "); - try self.writeInstRef(stream, arg); - } - try stream.writeAll("]) "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writePlNodeBlock(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - try self.writePlNodeBlockWithoutSrc(stream, inst); - try self.writeSrc(stream, inst_data.src()); - } - - fn writePlNodeBlockWithoutSrc(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.Block, inst_data.payload_index); - const body = self.code.extra[extra.end..][0..extra.data.body_len]; - try stream.writeAll("{\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); - } - - fn writePlNodeCondBr(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.CondBr, inst_data.payload_index); - const then_body = self.code.extra[extra.end..][0..extra.data.then_body_len]; - const else_body = self.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; - try self.writeInstRef(stream, extra.data.condition); - try stream.writeAll(", {\n"); - self.indent += 2; - try self.writeBody(stream, then_body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}, {\n"); - self.indent += 2; - try self.writeBody(stream, else_body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeStructDecl(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const small = @bitCast(Inst.StructDecl.Small, extended.small); - - var extra_index: usize = extended.operand; - - const src_node: ?i32 = if (small.has_src_node) blk: { - const src_node = @bitCast(i32, self.code.extra[extra_index]); - extra_index += 1; - break :blk src_node; - } else null; - - const body_len = if (small.has_body_len) blk: { - const body_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk body_len; - } else 0; - - const fields_len = if (small.has_fields_len) blk: { - const fields_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk fields_len; - } else 0; - - const decls_len = if (small.has_decls_len) blk: { - const decls_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - - try self.writeFlag(stream, "known_has_bits, ", small.known_has_bits); - try stream.print("{s}, {s}, ", .{ - @tagName(small.name_strategy), @tagName(small.layout), - }); - - if (decls_len == 0) { - try stream.writeAll("{}, "); - } else { - try stream.writeAll("{\n"); - self.indent += 2; - extra_index = try self.writeDecls(stream, decls_len, extra_index); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}, "); - } - - const body = self.code.extra[extra_index..][0..body_len]; - extra_index += body.len; - - if (fields_len == 0) { - assert(body.len == 0); - try stream.writeAll("{}, {})"); - } else { - const prev_parent_decl_node = self.parent_decl_node; - if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); - self.indent += 2; - if (body.len == 0) { - try stream.writeAll("{}, {\n"); - } else { - try stream.writeAll("{\n"); - try self.writeBody(stream, body); - - try stream.writeByteNTimes(' ', self.indent - 2); - try stream.writeAll("}, {\n"); - } - - const bits_per_field = 4; - const fields_per_u32 = 32 / bits_per_field; - const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; - var bit_bag_index: usize = extra_index; - extra_index += bit_bags_count; - var cur_bit_bag: u32 = undefined; - var field_i: u32 = 0; - while (field_i < fields_len) : (field_i += 1) { - if (field_i % fields_per_u32 == 0) { - cur_bit_bag = self.code.extra[bit_bag_index]; - bit_bag_index += 1; - } - const has_align = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const has_default = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const is_comptime = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const unused = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - - _ = unused; - - const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]); - extra_index += 1; - const field_type = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeByteNTimes(' ', self.indent); - try self.writeFlag(stream, "comptime ", is_comptime); - try stream.print("{}: ", .{std.zig.fmtId(field_name)}); - try self.writeInstRef(stream, field_type); - - if (has_align) { - const align_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeAll(" align("); - try self.writeInstRef(stream, align_ref); - try stream.writeAll(")"); - } - if (has_default) { - const default_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeAll(" = "); - try self.writeInstRef(stream, default_ref); - } - try stream.writeAll(",\n"); - } - - self.parent_decl_node = prev_parent_decl_node; - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("})"); - } - try self.writeSrcNode(stream, src_node); - } - - fn writeUnionDecl(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const small = @bitCast(Inst.UnionDecl.Small, extended.small); - - var extra_index: usize = extended.operand; - - const src_node: ?i32 = if (small.has_src_node) blk: { - const src_node = @bitCast(i32, self.code.extra[extra_index]); - extra_index += 1; - break :blk src_node; - } else null; - - const tag_type_ref = if (small.has_tag_type) blk: { - const tag_type_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - break :blk tag_type_ref; - } else .none; - - const body_len = if (small.has_body_len) blk: { - const body_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk body_len; - } else 0; - - const fields_len = if (small.has_fields_len) blk: { - const fields_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk fields_len; - } else 0; - - const decls_len = if (small.has_decls_len) blk: { - const decls_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - - try stream.print("{s}, {s}, ", .{ - @tagName(small.name_strategy), @tagName(small.layout), - }); - try self.writeFlag(stream, "autoenum, ", small.auto_enum_tag); - - if (decls_len == 0) { - try stream.writeAll("{}, "); - } else { - try stream.writeAll("{\n"); - self.indent += 2; - extra_index = try self.writeDecls(stream, decls_len, extra_index); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}, "); - } - - assert(fields_len != 0); - - if (tag_type_ref != .none) { - try self.writeInstRef(stream, tag_type_ref); - try stream.writeAll(", "); - } - - const body = self.code.extra[extra_index..][0..body_len]; - extra_index += body.len; - - const prev_parent_decl_node = self.parent_decl_node; - if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); - self.indent += 2; - if (body.len == 0) { - try stream.writeAll("{}, {\n"); - } else { - try stream.writeAll("{\n"); - try self.writeBody(stream, body); - - try stream.writeByteNTimes(' ', self.indent - 2); - try stream.writeAll("}, {\n"); - } - - const bits_per_field = 4; - const fields_per_u32 = 32 / bits_per_field; - const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; - const body_end = extra_index; - extra_index += bit_bags_count; - var bit_bag_index: usize = body_end; - var cur_bit_bag: u32 = undefined; - var field_i: u32 = 0; - while (field_i < fields_len) : (field_i += 1) { - if (field_i % fields_per_u32 == 0) { - cur_bit_bag = self.code.extra[bit_bag_index]; - bit_bag_index += 1; - } - const has_type = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const has_align = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const has_value = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const unused = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - - _ = unused; - - const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]); - extra_index += 1; - try stream.writeByteNTimes(' ', self.indent); - try stream.print("{}", .{std.zig.fmtId(field_name)}); - - if (has_type) { - const field_type = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeAll(": "); - try self.writeInstRef(stream, field_type); - } - if (has_align) { - const align_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeAll(" align("); - try self.writeInstRef(stream, align_ref); - try stream.writeAll(")"); - } - if (has_value) { - const default_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeAll(" = "); - try self.writeInstRef(stream, default_ref); - } - try stream.writeAll(",\n"); - } - - self.parent_decl_node = prev_parent_decl_node; - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("})"); - try self.writeSrcNode(stream, src_node); - } - - fn writeDecls(self: *Writer, stream: anytype, decls_len: u32, extra_start: usize) !usize { - const parent_decl_node = self.parent_decl_node; - const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable; - var extra_index = extra_start + bit_bags_count; - var bit_bag_index: usize = extra_start; - var cur_bit_bag: u32 = undefined; - var decl_i: u32 = 0; - while (decl_i < decls_len) : (decl_i += 1) { - if (decl_i % 8 == 0) { - cur_bit_bag = self.code.extra[bit_bag_index]; - bit_bag_index += 1; - } - const is_pub = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const is_exported = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const has_align = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - const has_section = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - - const sub_index = extra_index; - - const hash_u32s = self.code.extra[extra_index..][0..4]; - extra_index += 4; - const line = self.code.extra[extra_index]; - extra_index += 1; - const decl_name_index = self.code.extra[extra_index]; - extra_index += 1; - const decl_index = self.code.extra[extra_index]; - extra_index += 1; - const align_inst: Inst.Ref = if (!has_align) .none else inst: { - const inst = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - break :inst inst; - }; - const section_inst: Inst.Ref = if (!has_section) .none else inst: { - const inst = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - break :inst inst; - }; - - const pub_str = if (is_pub) "pub " else ""; - const hash_bytes = @bitCast([16]u8, hash_u32s.*); - try stream.writeByteNTimes(' ', self.indent); - if (decl_name_index == 0) { - const name = if (is_exported) "usingnamespace" else "comptime"; - try stream.writeAll(pub_str); - try stream.writeAll(name); - } else if (decl_name_index == 1) { - try stream.writeAll("test"); - } else { - const raw_decl_name = self.code.nullTerminatedString(decl_name_index); - const decl_name = if (raw_decl_name.len == 0) - self.code.nullTerminatedString(decl_name_index + 1) - else - raw_decl_name; - const test_str = if (raw_decl_name.len == 0) "test " else ""; - const export_str = if (is_exported) "export " else ""; - try stream.print("[{d}] {s}{s}{s}{}", .{ - sub_index, pub_str, test_str, export_str, std.zig.fmtId(decl_name), - }); - if (align_inst != .none) { - try stream.writeAll(" align("); - try self.writeInstRef(stream, align_inst); - try stream.writeAll(")"); - } - if (section_inst != .none) { - try stream.writeAll(" linksection("); - try self.writeInstRef(stream, section_inst); - try stream.writeAll(")"); - } - } - const tag = self.code.instructions.items(.tag)[decl_index]; - try stream.print(" line({d}) hash({}): %{d} = {s}(", .{ - line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index, @tagName(tag), - }); - - const decl_block_inst_data = self.code.instructions.items(.data)[decl_index].pl_node; - const sub_decl_node_off = decl_block_inst_data.src_node; - self.parent_decl_node = self.relativeToNodeIndex(sub_decl_node_off); - try self.writePlNodeBlockWithoutSrc(stream, decl_index); - self.parent_decl_node = parent_decl_node; - try self.writeSrc(stream, decl_block_inst_data.src()); - try stream.writeAll("\n"); - } - return extra_index; - } - - fn writeEnumDecl(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const small = @bitCast(Inst.EnumDecl.Small, extended.small); - var extra_index: usize = extended.operand; - - const src_node: ?i32 = if (small.has_src_node) blk: { - const src_node = @bitCast(i32, self.code.extra[extra_index]); - extra_index += 1; - break :blk src_node; - } else null; - - const tag_type_ref = if (small.has_tag_type) blk: { - const tag_type_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - break :blk tag_type_ref; - } else .none; - - const body_len = if (small.has_body_len) blk: { - const body_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk body_len; - } else 0; - - const fields_len = if (small.has_fields_len) blk: { - const fields_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk fields_len; - } else 0; - - const decls_len = if (small.has_decls_len) blk: { - const decls_len = self.code.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - - try stream.print("{s}, ", .{@tagName(small.name_strategy)}); - try self.writeFlag(stream, "nonexhaustive, ", small.nonexhaustive); - - if (decls_len == 0) { - try stream.writeAll("{}, "); - } else { - try stream.writeAll("{\n"); - self.indent += 2; - extra_index = try self.writeDecls(stream, decls_len, extra_index); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}, "); - } - - if (tag_type_ref != .none) { - try self.writeInstRef(stream, tag_type_ref); - try stream.writeAll(", "); - } - - const body = self.code.extra[extra_index..][0..body_len]; - extra_index += body.len; - - if (fields_len == 0) { - assert(body.len == 0); - try stream.writeAll("{}, {})"); - } else { - const prev_parent_decl_node = self.parent_decl_node; - if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); - self.indent += 2; - if (body.len == 0) { - try stream.writeAll("{}, {\n"); - } else { - try stream.writeAll("{\n"); - try self.writeBody(stream, body); - - try stream.writeByteNTimes(' ', self.indent - 2); - try stream.writeAll("}, {\n"); - } - - const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable; - const body_end = extra_index; - extra_index += bit_bags_count; - var bit_bag_index: usize = body_end; - var cur_bit_bag: u32 = undefined; - var field_i: u32 = 0; - while (field_i < fields_len) : (field_i += 1) { - if (field_i % 32 == 0) { - cur_bit_bag = self.code.extra[bit_bag_index]; - bit_bag_index += 1; - } - const has_tag_value = @truncate(u1, cur_bit_bag) != 0; - cur_bit_bag >>= 1; - - const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeByteNTimes(' ', self.indent); - try stream.print("{}", .{std.zig.fmtId(field_name)}); - - if (has_tag_value) { - const tag_value_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeAll(" = "); - try self.writeInstRef(stream, tag_value_ref); - } - try stream.writeAll(",\n"); - } - self.parent_decl_node = prev_parent_decl_node; - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("})"); - } - try self.writeSrcNode(stream, src_node); - } - - fn writeOpaqueDecl( - self: *Writer, - stream: anytype, - inst: Inst.Index, - name_strategy: Inst.NameStrategy, - ) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.OpaqueDecl, inst_data.payload_index); - const decls_len = extra.data.decls_len; - - try stream.print("{s}, ", .{@tagName(name_strategy)}); - - if (decls_len == 0) { - try stream.writeAll("}) "); - } else { - try stream.writeAll("\n"); - self.indent += 2; - _ = try self.writeDecls(stream, decls_len, extra.end); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); - } - try self.writeSrc(stream, inst_data.src()); - } - - fn writeErrorSetDecl( - self: *Writer, - stream: anytype, - inst: Inst.Index, - name_strategy: Inst.NameStrategy, - ) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.ErrorSetDecl, inst_data.payload_index); - const fields = self.code.extra[extra.end..][0..extra.data.fields_len]; - - try stream.print("{s}, ", .{@tagName(name_strategy)}); - - try stream.writeAll("{\n"); - self.indent += 2; - for (fields) |str_index| { - const name = self.code.nullTerminatedString(str_index); - try stream.writeByteNTimes(' ', self.indent); - try stream.print("{},\n", .{std.zig.fmtId(name)}); - } - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); - - try self.writeSrc(stream, inst_data.src()); - } - - fn writePlNodeSwitchBr( - self: *Writer, - stream: anytype, - inst: Inst.Index, - special_prong: SpecialProng, - ) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.SwitchBlock, inst_data.payload_index); - const special: struct { - body: []const Inst.Index, - end: usize, - } = switch (special_prong) { - .none => .{ .body = &.{}, .end = extra.end }, - .under, .@"else" => blk: { - const body_len = self.code.extra[extra.end]; - const extra_body_start = extra.end + 1; - break :blk .{ - .body = self.code.extra[extra_body_start..][0..body_len], - .end = extra_body_start + body_len, - }; - }, - }; - - try self.writeInstRef(stream, extra.data.operand); - - if (special.body.len != 0) { - const prong_name = switch (special_prong) { - .@"else" => "else", - .under => "_", - else => unreachable, - }; - try stream.print(", {s} => {{\n", .{prong_name}); - self.indent += 2; - try self.writeBody(stream, special.body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); - } - - var extra_index: usize = special.end; - { - var scalar_i: usize = 0; - while (scalar_i < extra.data.cases_len) : (scalar_i += 1) { - const item_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - const body_len = self.code.extra[extra_index]; - extra_index += 1; - const body = self.code.extra[extra_index..][0..body_len]; - extra_index += body_len; - - try stream.writeAll(", "); - try self.writeInstRef(stream, item_ref); - try stream.writeAll(" => {\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); - } - } - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writePlNodeSwitchBlockMulti( - self: *Writer, - stream: anytype, - inst: Inst.Index, - special_prong: SpecialProng, - ) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.SwitchBlockMulti, inst_data.payload_index); - const special: struct { - body: []const Inst.Index, - end: usize, - } = switch (special_prong) { - .none => .{ .body = &.{}, .end = extra.end }, - .under, .@"else" => blk: { - const body_len = self.code.extra[extra.end]; - const extra_body_start = extra.end + 1; - break :blk .{ - .body = self.code.extra[extra_body_start..][0..body_len], - .end = extra_body_start + body_len, - }; - }, - }; - - try self.writeInstRef(stream, extra.data.operand); - - if (special.body.len != 0) { - const prong_name = switch (special_prong) { - .@"else" => "else", - .under => "_", - else => unreachable, - }; - try stream.print(", {s} => {{\n", .{prong_name}); - self.indent += 2; - try self.writeBody(stream, special.body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); - } - - var extra_index: usize = special.end; - { - var scalar_i: usize = 0; - while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) { - const item_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - const body_len = self.code.extra[extra_index]; - extra_index += 1; - const body = self.code.extra[extra_index..][0..body_len]; - extra_index += body_len; - - try stream.writeAll(", "); - try self.writeInstRef(stream, item_ref); - try stream.writeAll(" => {\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); - } - } - { - var multi_i: usize = 0; - while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) { - const items_len = self.code.extra[extra_index]; - extra_index += 1; - const ranges_len = self.code.extra[extra_index]; - extra_index += 1; - const body_len = self.code.extra[extra_index]; - extra_index += 1; - const items = self.code.refSlice(extra_index, items_len); - extra_index += items_len; - - for (items) |item_ref| { - try stream.writeAll(", "); - try self.writeInstRef(stream, item_ref); - } - - var range_i: usize = 0; - while (range_i < ranges_len) : (range_i += 1) { - const item_first = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - const item_last = @intToEnum(Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - - try stream.writeAll(", "); - try self.writeInstRef(stream, item_first); - try stream.writeAll("..."); - try self.writeInstRef(stream, item_last); - } - - const body = self.code.extra[extra_index..][0..body_len]; - extra_index += body_len; - try stream.writeAll(" => {\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); - } - } - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writePlNodeField(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.Field, inst_data.payload_index).data; - const name = self.code.nullTerminatedString(extra.field_name_start); - try self.writeInstRef(stream, extra.lhs); - try stream.print(", \"{}\") ", .{std.zig.fmtEscapes(name)}); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeAs(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Inst.As, inst_data.payload_index).data; - try self.writeInstRef(stream, extra.dest_type); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.operand); - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeNode( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const src_node = self.code.instructions.items(.data)[inst].node; - const src: LazySrcLoc = .{ .node_offset = src_node }; - try stream.writeAll(") "); - try self.writeSrc(stream, src); - } - - fn writeStrTok( - self: *Writer, - stream: anytype, - inst: Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].str_tok; - const str = inst_data.get(self.code); - try stream.print("\"{}\") ", .{std.zig.fmtEscapes(str)}); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeFunc( - self: *Writer, - stream: anytype, - inst: Inst.Index, - inferred_error_set: bool, - ) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - const extra = self.code.extraData(Inst.Func, inst_data.payload_index); - var extra_index = extra.end; - - const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; - extra_index += ret_ty_body.len; - - const body = self.code.extra[extra_index..][0..extra.data.body_len]; - extra_index += body.len; - - var src_locs: Zir.Inst.Func.SrcLocs = undefined; - if (body.len != 0) { - src_locs = self.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; - } - return self.writeFuncCommon( - stream, - ret_ty_body, - inferred_error_set, - false, - false, - .none, - .none, - body, - src, - src_locs, - ); - } - - fn writeFuncExtended(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const extra = self.code.extraData(Inst.ExtendedFunc, extended.operand); - const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; - const small = @bitCast(Inst.ExtendedFunc.Small, extended.small); - - var extra_index: usize = extra.end; - if (small.has_lib_name) { - const lib_name = self.code.nullTerminatedString(self.code.extra[extra_index]); - extra_index += 1; - try stream.print("lib_name=\"{}\", ", .{std.zig.fmtEscapes(lib_name)}); - } - try self.writeFlag(stream, "test, ", small.is_test); - const cc: Inst.Ref = if (!small.has_cc) .none else blk: { - const cc = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - break :blk cc; - }; - const align_inst: Inst.Ref = if (!small.has_align) .none else blk: { - const align_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - break :blk align_inst; - }; - - const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; - extra_index += ret_ty_body.len; - - const body = self.code.extra[extra_index..][0..extra.data.body_len]; - extra_index += body.len; - - var src_locs: Zir.Inst.Func.SrcLocs = undefined; - if (body.len != 0) { - src_locs = self.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; - } - return self.writeFuncCommon( - stream, - ret_ty_body, - small.is_inferred_error, - small.is_var_args, - small.is_extern, - cc, - align_inst, - body, - src, - src_locs, - ); - } - - fn writeVarExtended(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { - const extra = self.code.extraData(Inst.ExtendedVar, extended.operand); - const small = @bitCast(Inst.ExtendedVar.Small, extended.small); - - try self.writeInstRef(stream, extra.data.var_type); - - var extra_index: usize = extra.end; - if (small.has_lib_name) { - const lib_name = self.code.nullTerminatedString(self.code.extra[extra_index]); - extra_index += 1; - try stream.print(", lib_name=\"{}\"", .{std.zig.fmtEscapes(lib_name)}); - } - const align_inst: Inst.Ref = if (!small.has_align) .none else blk: { - const align_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - break :blk align_inst; - }; - const init_inst: Inst.Ref = if (!small.has_init) .none else blk: { - const init_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - break :blk init_inst; - }; - try self.writeFlag(stream, ", is_extern", small.is_extern); - try self.writeOptionalInstRef(stream, ", align=", align_inst); - try self.writeOptionalInstRef(stream, ", init=", init_inst); - try stream.writeAll("))"); - } - - fn writeBoolBr(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].bool_br; - const extra = self.code.extraData(Inst.Block, inst_data.payload_index); - const body = self.code.extra[extra.end..][0..extra.data.body_len]; - try self.writeInstRef(stream, inst_data.lhs); - try stream.writeAll(", {\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("})"); - } - - fn writeIntType(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const int_type = self.code.instructions.items(.data)[inst].int_type; - const prefix: u8 = switch (int_type.signedness) { - .signed => 'i', - .unsigned => 'u', - }; - try stream.print("{c}{d}) ", .{ prefix, int_type.bit_count }); - try self.writeSrc(stream, int_type.src()); - } - - fn writeBreak(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].@"break"; - - try self.writeInstIndex(stream, inst_data.block_inst); - try stream.writeAll(", "); - try self.writeInstRef(stream, inst_data.operand); - try stream.writeAll(")"); - } - - fn writeUnreachable(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].@"unreachable"; - const safety_str = if (inst_data.safety) "safe" else "unsafe"; - try stream.print("{s}) ", .{safety_str}); - try self.writeSrc(stream, inst_data.src()); - } - - fn writeFuncCommon( - self: *Writer, - stream: anytype, - ret_ty_body: []const Inst.Index, - inferred_error_set: bool, - var_args: bool, - is_extern: bool, - cc: Inst.Ref, - align_inst: Inst.Ref, - body: []const Inst.Index, - src: LazySrcLoc, - src_locs: Zir.Inst.Func.SrcLocs, - ) !void { - if (ret_ty_body.len == 0) { - try stream.writeAll("ret_ty=void"); - } else { - try stream.writeAll("ret_ty={\n"); - self.indent += 2; - try self.writeBody(stream, ret_ty_body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); - } - - try self.writeOptionalInstRef(stream, ", cc=", cc); - try self.writeOptionalInstRef(stream, ", align=", align_inst); - try self.writeFlag(stream, ", vargs", var_args); - try self.writeFlag(stream, ", extern", is_extern); - try self.writeFlag(stream, ", inferror", inferred_error_set); - - if (body.len == 0) { - try stream.writeAll(", body={}) "); - } else { - try stream.writeAll(", body={\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); - } - if (body.len != 0) { - try stream.print("(lbrace={d}:{d},rbrace={d}:{d}) ", .{ - src_locs.lbrace_line, @truncate(u16, src_locs.columns), - src_locs.rbrace_line, @truncate(u16, src_locs.columns >> 16), - }); - } - try self.writeSrc(stream, src); - } - - fn writeSwitchCapture(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].switch_capture; - try self.writeInstIndex(stream, inst_data.switch_inst); - try stream.print(", {d})", .{inst_data.prong_index}); - } - - fn writeDbgStmt(self: *Writer, stream: anytype, inst: Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].dbg_stmt; - try stream.print("{d}, {d})", .{ inst_data.line, inst_data.column }); - } - - fn writeInstRef(self: *Writer, stream: anytype, ref: Inst.Ref) !void { - var i: usize = @enumToInt(ref); - - if (i < Inst.Ref.typed_value_map.len) { - return stream.print("@{}", .{ref}); - } - i -= Inst.Ref.typed_value_map.len; - - return self.writeInstIndex(stream, @intCast(Inst.Index, i)); - } - - fn writeInstIndex(self: *Writer, stream: anytype, inst: Inst.Index) !void { - _ = self; - return stream.print("%{d}", .{inst}); - } - - fn writeOptionalInstRef( - self: *Writer, - stream: anytype, - prefix: []const u8, - inst: Inst.Ref, - ) !void { - if (inst == .none) return; - try stream.writeAll(prefix); - try self.writeInstRef(stream, inst); - } - - fn writeFlag( - self: *Writer, - stream: anytype, - name: []const u8, - flag: bool, - ) !void { - _ = self; - if (!flag) return; - try stream.writeAll(name); - } - - fn writeSrc(self: *Writer, stream: anytype, src: LazySrcLoc) !void { - const tree = self.file.tree; - const src_loc: Module.SrcLoc = .{ - .file_scope = self.file, - .parent_decl_node = self.parent_decl_node, - .lazy = src, - }; - // Caller must ensure AST tree is loaded. - const abs_byte_off = src_loc.byteOffset(self.gpa) catch unreachable; - const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off); - try stream.print("{s}:{d}:{d}", .{ - @tagName(src), delta_line.line + 1, delta_line.column + 1, - }); - } - - fn writeSrcNode(self: *Writer, stream: anytype, src_node: ?i32) !void { - const node_offset = src_node orelse return; - const src: LazySrcLoc = .{ .node_offset = node_offset }; - try stream.writeAll(" "); - return self.writeSrc(stream, src); - } - - fn writeBody(self: *Writer, stream: anytype, body: []const Inst.Index) !void { - for (body) |inst| { - try stream.writeByteNTimes(' ', self.indent); - try stream.print("%{d} ", .{inst}); - try self.writeInstToStream(stream, inst); - try stream.writeByte('\n'); - } - } -}; - pub const DeclIterator = struct { extra_index: usize, bit_bag_index: usize, diff --git a/src/main.zig b/src/main.zig index b7b5d06264..74bf45b62c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4132,7 +4132,7 @@ pub fn cmdAstCheck( // zig fmt: on } - return Zir.renderAsTextToFile(gpa, &file, io.getStdOut()); + return @import("print_zir.zig").renderAsTextToFile(gpa, &file, io.getStdOut()); } /// This is only enabled for debug builds. diff --git a/src/print_zir.zig b/src/print_zir.zig new file mode 100644 index 0000000000..911cf05baf --- /dev/null +++ b/src/print_zir.zig @@ -0,0 +1,1868 @@ +const std = @import("std"); +const mem = std.mem; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const Ast = std.zig.Ast; + +const Zir = @import("Zir.zig"); +const Module = @import("Module.zig"); +const LazySrcLoc = Module.LazySrcLoc; + +/// Write human-readable, debug formatted ZIR code to a file. +pub fn renderAsTextToFile( + gpa: *Allocator, + scope_file: *Module.Scope.File, + fs_file: std.fs.File, +) !void { + var arena = std.heap.ArenaAllocator.init(gpa); + defer arena.deinit(); + + var writer: Writer = .{ + .gpa = gpa, + .arena = &arena.allocator, + .file = scope_file, + .code = scope_file.zir, + .indent = 0, + .parent_decl_node = 0, + }; + + const main_struct_inst = scope_file.zir.getMainStruct(); + try fs_file.writer().print("%{d} ", .{main_struct_inst}); + try writer.writeInstToStream(fs_file.writer(), main_struct_inst); + try fs_file.writeAll("\n"); + const imports_index = scope_file.zir.extra[@enumToInt(Zir.ExtraIndex.imports)]; + if (imports_index != 0) { + try fs_file.writeAll("Imports:\n"); + + const extra = scope_file.zir.extraData(Zir.Inst.Imports, imports_index); + var import_i: u32 = 0; + var extra_index = extra.end; + + while (import_i < extra.data.imports_len) : (import_i += 1) { + const item = scope_file.zir.extraData(Zir.Inst.Imports.Item, extra_index); + extra_index = item.end; + + const src: LazySrcLoc = .{ .token_abs = item.data.token }; + const import_path = scope_file.zir.nullTerminatedString(item.data.name); + try fs_file.writer().print(" @import(\"{}\") ", .{ + std.zig.fmtEscapes(import_path), + }); + try writer.writeSrc(fs_file.writer(), src); + try fs_file.writer().writeAll("\n"); + } + } +} + +const Writer = struct { + gpa: *Allocator, + arena: *Allocator, + file: *Module.Scope.File, + code: Zir, + indent: u32, + parent_decl_node: u32, + + fn relativeToNodeIndex(self: *Writer, offset: i32) Ast.Node.Index { + return @bitCast(Ast.Node.Index, offset + @bitCast(i32, self.parent_decl_node)); + } + + fn writeInstToStream( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const tags = self.code.instructions.items(.tag); + const tag = tags[inst]; + try stream.print("= {s}(", .{@tagName(tags[inst])}); + switch (tag) { + .array_type, + .as, + .coerce_result_ptr, + .elem_ptr, + .elem_val, + .store, + .store_to_block_ptr, + .store_to_inferred_ptr, + .field_ptr_type, + => try self.writeBin(stream, inst), + + .alloc, + .alloc_mut, + .alloc_comptime, + .indexable_ptr_len, + .anyframe_type, + .bit_not, + .bool_not, + .negate, + .negate_wrap, + .load, + .ensure_result_used, + .ensure_result_non_error, + .ret_node, + .ret_load, + .resolve_inferred_alloc, + .optional_type, + .optional_payload_safe, + .optional_payload_unsafe, + .optional_payload_safe_ptr, + .optional_payload_unsafe_ptr, + .err_union_payload_safe, + .err_union_payload_unsafe, + .err_union_payload_safe_ptr, + .err_union_payload_unsafe_ptr, + .err_union_code, + .err_union_code_ptr, + .is_non_null, + .is_non_null_ptr, + .is_non_err, + .is_non_err_ptr, + .typeof, + .typeof_elem, + .struct_init_empty, + .type_info, + .size_of, + .bit_size_of, + .typeof_log2_int_type, + .log2_int_type, + .ptr_to_int, + .error_to_int, + .int_to_error, + .compile_error, + .set_eval_branch_quota, + .enum_to_int, + .align_of, + .bool_to_int, + .embed_file, + .error_name, + .panic, + .set_align_stack, + .set_cold, + .set_float_mode, + .set_runtime_safety, + .sqrt, + .sin, + .cos, + .exp, + .exp2, + .log, + .log2, + .log10, + .fabs, + .floor, + .ceil, + .trunc, + .round, + .tag_name, + .reify, + .type_name, + .frame_type, + .frame_size, + .clz, + .ctz, + .pop_count, + .byte_swap, + .bit_reverse, + .elem_type, + .@"resume", + .@"await", + .await_nosuspend, + .fence, + => try self.writeUnNode(stream, inst), + + .ref, + .ret_coerce, + .ensure_err_payload_void, + => try self.writeUnTok(stream, inst), + + .bool_br_and, + .bool_br_or, + => try self.writeBoolBr(stream, inst), + + .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), + .param_type => try self.writeParamType(stream, inst), + .ptr_type_simple => try self.writePtrTypeSimple(stream, inst), + .ptr_type => try self.writePtrType(stream, inst), + .int => try self.writeInt(stream, inst), + .int_big => try self.writeIntBig(stream, inst), + .float => try self.writeFloat(stream, inst), + .float128 => try self.writeFloat128(stream, inst), + .str => try self.writeStr(stream, inst), + .int_type => try self.writeIntType(stream, inst), + + .@"break", + .break_inline, + => try self.writeBreak(stream, inst), + + .elem_ptr_node, + .elem_val_node, + .field_ptr_named, + .field_val_named, + .slice_start, + .slice_end, + .slice_sentinel, + .array_init, + .array_init_anon, + .array_init_ref, + .array_init_anon_ref, + .union_init_ptr, + .shuffle, + .select, + .mul_add, + .builtin_call, + .field_parent_ptr, + .memcpy, + .memset, + .builtin_async_call, + => try self.writePlNode(stream, inst), + + .struct_init, + .struct_init_ref, + => try self.writeStructInit(stream, inst), + + .cmpxchg_strong, .cmpxchg_weak => try self.writeCmpxchg(stream, inst), + .atomic_store => try self.writeAtomicStore(stream, inst), + .atomic_rmw => try self.writeAtomicRmw(stream, inst), + + .struct_init_anon, + .struct_init_anon_ref, + => try self.writeStructInitAnon(stream, inst), + + .field_type => try self.writeFieldType(stream, inst), + .field_type_ref => try self.writeFieldTypeRef(stream, inst), + + .add, + .addwrap, + .array_cat, + .array_mul, + .mul, + .mulwrap, + .sub, + .subwrap, + .cmp_lt, + .cmp_lte, + .cmp_eq, + .cmp_gte, + .cmp_gt, + .cmp_neq, + .div, + .has_decl, + .has_field, + .mod_rem, + .shl, + .shl_exact, + .shr, + .shr_exact, + .xor, + .store_node, + .error_union_type, + .merge_error_sets, + .bit_and, + .bit_or, + .float_to_int, + .int_to_float, + .int_to_ptr, + .int_to_enum, + .float_cast, + .int_cast, + .err_set_cast, + .ptr_cast, + .truncate, + .align_cast, + .div_exact, + .div_floor, + .div_trunc, + .mod, + .rem, + .bit_offset_of, + .offset_of, + .splat, + .reduce, + .atomic_load, + .bitcast, + .bitcast_result_ptr, + .vector_type, + .maximum, + .minimum, + => try self.writePlNodeBin(stream, inst), + + .@"export" => try self.writePlNodeExport(stream, inst), + + .call, + .call_chkused, + .call_compile_time, + .call_nosuspend, + .call_async, + => try self.writePlNodeCall(stream, inst), + + .block, + .block_inline, + .suspend_block, + .loop, + .validate_struct_init_ptr, + .validate_array_init_ptr, + .c_import, + => try self.writePlNodeBlock(stream, inst), + + .condbr, + .condbr_inline, + => try self.writePlNodeCondBr(stream, inst), + + .opaque_decl => try self.writeOpaqueDecl(stream, inst, .parent), + .opaque_decl_anon => try self.writeOpaqueDecl(stream, inst, .anon), + .opaque_decl_func => try self.writeOpaqueDecl(stream, inst, .func), + + .error_set_decl => try self.writeErrorSetDecl(stream, inst, .parent), + .error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon), + .error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func), + + .switch_block => try self.writePlNodeSwitchBr(stream, inst, .none), + .switch_block_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"), + .switch_block_under => try self.writePlNodeSwitchBr(stream, inst, .under), + .switch_block_ref => try self.writePlNodeSwitchBr(stream, inst, .none), + .switch_block_ref_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"), + .switch_block_ref_under => try self.writePlNodeSwitchBr(stream, inst, .under), + + .switch_block_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none), + .switch_block_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"), + .switch_block_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under), + .switch_block_ref_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none), + .switch_block_ref_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"), + .switch_block_ref_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under), + + .field_ptr, + .field_val, + => try self.writePlNodeField(stream, inst), + + .as_node => try self.writeAs(stream, inst), + + .breakpoint, + .repeat, + .repeat_inline, + .alloc_inferred, + .alloc_inferred_mut, + .alloc_inferred_comptime, + => try self.writeNode(stream, inst), + + .error_value, + .enum_literal, + .decl_ref, + .decl_val, + .import, + .ret_err_value, + .ret_err_value_code, + .param_anytype, + .param_anytype_comptime, + => try self.writeStrTok(stream, inst), + + .param, .param_comptime => try self.writeParam(stream, inst), + + .func => try self.writeFunc(stream, inst, false), + .func_inferred => try self.writeFunc(stream, inst, true), + + .@"unreachable" => try self.writeUnreachable(stream, inst), + + .switch_capture, + .switch_capture_ref, + .switch_capture_multi, + .switch_capture_multi_ref, + .switch_capture_else, + .switch_capture_else_ref, + => try self.writeSwitchCapture(stream, inst), + + .dbg_stmt => try self.writeDbgStmt(stream, inst), + + .extended => try self.writeExtended(stream, inst), + } + } + + fn writeExtended(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const extended = self.code.instructions.items(.data)[inst].extended; + try stream.print("{s}(", .{@tagName(extended.opcode)}); + switch (extended.opcode) { + .ret_ptr, + .ret_type, + .this, + .ret_addr, + .error_return_trace, + .frame, + .frame_address, + .builtin_src, + => try self.writeExtNode(stream, extended), + + .@"asm" => try self.writeAsm(stream, extended), + .func => try self.writeFuncExtended(stream, extended), + .variable => try self.writeVarExtended(stream, extended), + + .compile_log, + .typeof_peer, + => try self.writeNodeMultiOp(stream, extended), + + .add_with_overflow, + .sub_with_overflow, + .mul_with_overflow, + .shl_with_overflow, + => try self.writeOverflowArithmetic(stream, extended), + + .add_with_saturation, + .sub_with_saturation, + .mul_with_saturation, + .shl_with_saturation, + => try self.writeSaturatingArithmetic(stream, extended), + .struct_decl => try self.writeStructDecl(stream, extended), + .union_decl => try self.writeUnionDecl(stream, extended), + .enum_decl => try self.writeEnumDecl(stream, extended), + + .alloc, + .builtin_extern, + .c_undef, + .c_include, + .c_define, + .wasm_memory_size, + .wasm_memory_grow, + => try stream.writeAll("TODO))"), + } + } + + fn writeExtNode(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; + try stream.writeAll(")) "); + try self.writeSrc(stream, src); + } + + fn writeBin(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].bin; + try self.writeInstRef(stream, inst_data.lhs); + try stream.writeAll(", "); + try self.writeInstRef(stream, inst_data.rhs); + try stream.writeByte(')'); + } + + fn writeUnNode( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].un_node; + try self.writeInstRef(stream, inst_data.operand); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeUnTok( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].un_tok; + try self.writeInstRef(stream, inst_data.operand); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeArrayTypeSentinel( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].array_type_sentinel; + _ = inst_data; + try stream.writeAll("TODO)"); + } + + fn writeParamType( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].param_type; + try self.writeInstRef(stream, inst_data.callee); + try stream.print(", {d})", .{inst_data.param_index}); + } + + fn writePtrTypeSimple( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].ptr_type_simple; + const str_allowzero = if (inst_data.is_allowzero) "allowzero, " else ""; + const str_const = if (!inst_data.is_mutable) "const, " else ""; + const str_volatile = if (inst_data.is_volatile) "volatile, " else ""; + try self.writeInstRef(stream, inst_data.elem_type); + try stream.print(", {s}{s}{s}{s})", .{ + str_allowzero, + str_const, + str_volatile, + @tagName(inst_data.size), + }); + } + + fn writePtrType( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].ptr_type; + _ = inst_data; + try stream.writeAll("TODO)"); + } + + fn writeInt(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].int; + try stream.print("{d})", .{inst_data}); + } + + fn writeIntBig(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].str; + const byte_count = inst_data.len * @sizeOf(std.math.big.Limb); + const limb_bytes = self.code.string_bytes[inst_data.start..][0..byte_count]; + // limb_bytes is not aligned properly; we must allocate and copy the bytes + // in order to accomplish this. + const limbs = try self.gpa.alloc(std.math.big.Limb, inst_data.len); + defer self.gpa.free(limbs); + + mem.copy(u8, mem.sliceAsBytes(limbs), limb_bytes); + const big_int: std.math.big.int.Const = .{ + .limbs = limbs, + .positive = true, + }; + const as_string = try big_int.toStringAlloc(self.gpa, 10, .lower); + defer self.gpa.free(as_string); + try stream.print("{s})", .{as_string}); + } + + fn writeFloat(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const number = self.code.instructions.items(.data)[inst].float; + try stream.print("{d})", .{number}); + } + + fn writeFloat128(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Float128, inst_data.payload_index).data; + const src = inst_data.src(); + const number = extra.get(); + // TODO improve std.format to be able to print f128 values + try stream.print("{d}) ", .{@floatCast(f64, number)}); + try self.writeSrc(stream, src); + } + + fn writeStr( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].str; + const str = inst_data.get(self.code); + try stream.print("\"{}\")", .{std.zig.fmtEscapes(str)}); + } + + fn writePlNode(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + try stream.writeAll("TODO) "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeParam(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_tok; + const extra = self.code.extraData(Zir.Inst.Param, inst_data.payload_index); + const body = self.code.extra[extra.end..][0..extra.data.body_len]; + try stream.print("\"{}\", ", .{ + std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)), + }); + try stream.writeAll("{\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}) "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writePlNodeBin(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.lhs); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.rhs); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writePlNodeExport(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Export, inst_data.payload_index).data; + const decl_name = self.code.nullTerminatedString(extra.decl_name); + + try self.writeInstRef(stream, extra.namespace); + try stream.print(", {}, ", .{std.zig.fmtId(decl_name)}); + try self.writeInstRef(stream, extra.options); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeStructInit(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.StructInit, inst_data.payload_index); + var field_i: u32 = 0; + var extra_index = extra.end; + + while (field_i < extra.data.fields_len) : (field_i += 1) { + const item = self.code.extraData(Zir.Inst.StructInit.Item, extra_index); + extra_index = item.end; + + if (field_i != 0) { + try stream.writeAll(", ["); + } else { + try stream.writeAll("["); + } + try self.writeInstIndex(stream, item.data.field_type); + try stream.writeAll(", "); + try self.writeInstRef(stream, item.data.init); + try stream.writeAll("]"); + } + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeCmpxchg(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Cmpxchg, inst_data.payload_index).data; + + try self.writeInstRef(stream, extra.ptr); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.expected_value); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.new_value); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.success_order); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.failure_order); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeAtomicStore(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.AtomicStore, inst_data.payload_index).data; + + try self.writeInstRef(stream, extra.ptr); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.operand); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.ordering); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeAtomicRmw(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.AtomicRmw, inst_data.payload_index).data; + + try self.writeInstRef(stream, extra.ptr); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.operation); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.operand); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.ordering); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeStructInitAnon(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.StructInitAnon, inst_data.payload_index); + var field_i: u32 = 0; + var extra_index = extra.end; + + while (field_i < extra.data.fields_len) : (field_i += 1) { + const item = self.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index); + extra_index = item.end; + + const field_name = self.code.nullTerminatedString(item.data.field_name); + + const prefix = if (field_i != 0) ", [" else "["; + try stream.print("{s}[{s}=", .{ prefix, field_name }); + try self.writeInstRef(stream, item.data.init); + try stream.writeAll("]"); + } + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeFieldType(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.container_type); + const field_name = self.code.nullTerminatedString(extra.name_start); + try stream.print(", {s}) ", .{field_name}); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeFieldTypeRef(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.FieldTypeRef, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.container_type); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.field_name); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeNodeMultiOp(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.NodeMultiOp, extended.operand); + const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; + const operands = self.code.refSlice(extra.end, extended.small); + + for (operands) |operand, i| { + if (i != 0) try stream.writeAll(", "); + try self.writeInstRef(stream, operand); + } + try stream.writeAll(")) "); + try self.writeSrc(stream, src); + } + + fn writeAsm(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.Asm, extended.operand); + const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; + const outputs_len = @truncate(u5, extended.small); + const inputs_len = @truncate(u5, extended.small >> 5); + const clobbers_len = @truncate(u5, extended.small >> 10); + const is_volatile = @truncate(u1, extended.small >> 15) != 0; + const asm_source = self.code.nullTerminatedString(extra.data.asm_source); + + try self.writeFlag(stream, "volatile, ", is_volatile); + try stream.print("\"{}\", ", .{std.zig.fmtEscapes(asm_source)}); + try stream.writeAll(", "); + + var extra_i: usize = extra.end; + var output_type_bits = extra.data.output_type_bits; + { + var i: usize = 0; + while (i < outputs_len) : (i += 1) { + const output = self.code.extraData(Zir.Inst.Asm.Output, extra_i); + extra_i = output.end; + + const is_type = @truncate(u1, output_type_bits) != 0; + output_type_bits >>= 1; + + const name = self.code.nullTerminatedString(output.data.name); + const constraint = self.code.nullTerminatedString(output.data.constraint); + try stream.print("output({}, \"{}\", ", .{ + std.zig.fmtId(name), std.zig.fmtEscapes(constraint), + }); + try self.writeFlag(stream, "->", is_type); + try self.writeInstRef(stream, output.data.operand); + try stream.writeAll(")"); + if (i + 1 < outputs_len) { + try stream.writeAll("), "); + } + } + } + { + var i: usize = 0; + while (i < inputs_len) : (i += 1) { + const input = self.code.extraData(Zir.Inst.Asm.Input, extra_i); + extra_i = input.end; + + const name = self.code.nullTerminatedString(input.data.name); + const constraint = self.code.nullTerminatedString(input.data.constraint); + try stream.print("input({}, \"{}\", ", .{ + std.zig.fmtId(name), std.zig.fmtEscapes(constraint), + }); + try self.writeInstRef(stream, input.data.operand); + try stream.writeAll(")"); + if (i + 1 < inputs_len) { + try stream.writeAll(", "); + } + } + } + { + var i: usize = 0; + while (i < clobbers_len) : (i += 1) { + const str_index = self.code.extra[extra_i]; + extra_i += 1; + const clobber = self.code.nullTerminatedString(str_index); + try stream.print("{}", .{std.zig.fmtId(clobber)}); + if (i + 1 < clobbers_len) { + try stream.writeAll(", "); + } + } + } + try stream.writeAll(")) "); + try self.writeSrc(stream, src); + } + + fn writeOverflowArithmetic(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.OverflowArithmetic, extended.operand).data; + const src: LazySrcLoc = .{ .node_offset = extra.node }; + + try self.writeInstRef(stream, extra.lhs); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.rhs); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.ptr); + try stream.writeAll(")) "); + try self.writeSrc(stream, src); + } + + fn writeSaturatingArithmetic(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data; + const src: LazySrcLoc = .{ .node_offset = extra.node }; + + try self.writeInstRef(stream, extra.lhs); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.rhs); + try stream.writeAll(", "); + try stream.writeAll(") "); + try self.writeSrc(stream, src); + } + + fn writePlNodeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index); + const args = self.code.refSlice(extra.end, extra.data.args_len); + + try self.writeInstRef(stream, extra.data.callee); + try stream.writeAll(", ["); + for (args) |arg, i| { + if (i != 0) try stream.writeAll(", "); + try self.writeInstRef(stream, arg); + } + try stream.writeAll("]) "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writePlNodeBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + try self.writePlNodeBlockWithoutSrc(stream, inst); + try self.writeSrc(stream, inst_data.src()); + } + + fn writePlNodeBlockWithoutSrc(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index); + const body = self.code.extra[extra.end..][0..extra.data.body_len]; + try stream.writeAll("{\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}) "); + } + + fn writePlNodeCondBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.CondBr, inst_data.payload_index); + const then_body = self.code.extra[extra.end..][0..extra.data.then_body_len]; + const else_body = self.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; + try self.writeInstRef(stream, extra.data.condition); + try stream.writeAll(", {\n"); + self.indent += 2; + try self.writeBody(stream, then_body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}, {\n"); + self.indent += 2; + try self.writeBody(stream, else_body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}) "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeStructDecl(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small); + + var extra_index: usize = extended.operand; + + const src_node: ?i32 = if (small.has_src_node) blk: { + const src_node = @bitCast(i32, self.code.extra[extra_index]); + extra_index += 1; + break :blk src_node; + } else null; + + const body_len = if (small.has_body_len) blk: { + const body_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk body_len; + } else 0; + + const fields_len = if (small.has_fields_len) blk: { + const fields_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + + const decls_len = if (small.has_decls_len) blk: { + const decls_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + + try self.writeFlag(stream, "known_has_bits, ", small.known_has_bits); + try stream.print("{s}, {s}, ", .{ + @tagName(small.name_strategy), @tagName(small.layout), + }); + + if (decls_len == 0) { + try stream.writeAll("{}, "); + } else { + try stream.writeAll("{\n"); + self.indent += 2; + extra_index = try self.writeDecls(stream, decls_len, extra_index); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}, "); + } + + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body.len; + + if (fields_len == 0) { + assert(body.len == 0); + try stream.writeAll("{}, {})"); + } else { + const prev_parent_decl_node = self.parent_decl_node; + if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); + self.indent += 2; + if (body.len == 0) { + try stream.writeAll("{}, {\n"); + } else { + try stream.writeAll("{\n"); + try self.writeBody(stream, body); + + try stream.writeByteNTimes(' ', self.indent - 2); + try stream.writeAll("}, {\n"); + } + + const bits_per_field = 4; + const fields_per_u32 = 32 / bits_per_field; + const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; + var bit_bag_index: usize = extra_index; + extra_index += bit_bags_count; + var cur_bit_bag: u32 = undefined; + var field_i: u32 = 0; + while (field_i < fields_len) : (field_i += 1) { + if (field_i % fields_per_u32 == 0) { + cur_bit_bag = self.code.extra[bit_bag_index]; + bit_bag_index += 1; + } + const has_align = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const has_default = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const is_comptime = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const unused = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + + _ = unused; + + const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]); + extra_index += 1; + const field_type = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeByteNTimes(' ', self.indent); + try self.writeFlag(stream, "comptime ", is_comptime); + try stream.print("{}: ", .{std.zig.fmtId(field_name)}); + try self.writeInstRef(stream, field_type); + + if (has_align) { + const align_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeAll(" align("); + try self.writeInstRef(stream, align_ref); + try stream.writeAll(")"); + } + if (has_default) { + const default_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeAll(" = "); + try self.writeInstRef(stream, default_ref); + } + try stream.writeAll(",\n"); + } + + self.parent_decl_node = prev_parent_decl_node; + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("})"); + } + try self.writeSrcNode(stream, src_node); + } + + fn writeUnionDecl(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small); + + var extra_index: usize = extended.operand; + + const src_node: ?i32 = if (small.has_src_node) blk: { + const src_node = @bitCast(i32, self.code.extra[extra_index]); + extra_index += 1; + break :blk src_node; + } else null; + + const tag_type_ref = if (small.has_tag_type) blk: { + const tag_type_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :blk tag_type_ref; + } else .none; + + const body_len = if (small.has_body_len) blk: { + const body_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk body_len; + } else 0; + + const fields_len = if (small.has_fields_len) blk: { + const fields_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + + const decls_len = if (small.has_decls_len) blk: { + const decls_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + + try stream.print("{s}, {s}, ", .{ + @tagName(small.name_strategy), @tagName(small.layout), + }); + try self.writeFlag(stream, "autoenum, ", small.auto_enum_tag); + + if (decls_len == 0) { + try stream.writeAll("{}, "); + } else { + try stream.writeAll("{\n"); + self.indent += 2; + extra_index = try self.writeDecls(stream, decls_len, extra_index); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}, "); + } + + assert(fields_len != 0); + + if (tag_type_ref != .none) { + try self.writeInstRef(stream, tag_type_ref); + try stream.writeAll(", "); + } + + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body.len; + + const prev_parent_decl_node = self.parent_decl_node; + if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); + self.indent += 2; + if (body.len == 0) { + try stream.writeAll("{}, {\n"); + } else { + try stream.writeAll("{\n"); + try self.writeBody(stream, body); + + try stream.writeByteNTimes(' ', self.indent - 2); + try stream.writeAll("}, {\n"); + } + + const bits_per_field = 4; + const fields_per_u32 = 32 / bits_per_field; + const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; + const body_end = extra_index; + extra_index += bit_bags_count; + var bit_bag_index: usize = body_end; + var cur_bit_bag: u32 = undefined; + var field_i: u32 = 0; + while (field_i < fields_len) : (field_i += 1) { + if (field_i % fields_per_u32 == 0) { + cur_bit_bag = self.code.extra[bit_bag_index]; + bit_bag_index += 1; + } + const has_type = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const has_align = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const has_value = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const unused = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + + _ = unused; + + const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]); + extra_index += 1; + try stream.writeByteNTimes(' ', self.indent); + try stream.print("{}", .{std.zig.fmtId(field_name)}); + + if (has_type) { + const field_type = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeAll(": "); + try self.writeInstRef(stream, field_type); + } + if (has_align) { + const align_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeAll(" align("); + try self.writeInstRef(stream, align_ref); + try stream.writeAll(")"); + } + if (has_value) { + const default_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeAll(" = "); + try self.writeInstRef(stream, default_ref); + } + try stream.writeAll(",\n"); + } + + self.parent_decl_node = prev_parent_decl_node; + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("})"); + try self.writeSrcNode(stream, src_node); + } + + fn writeDecls(self: *Writer, stream: anytype, decls_len: u32, extra_start: usize) !usize { + const parent_decl_node = self.parent_decl_node; + const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable; + var extra_index = extra_start + bit_bags_count; + var bit_bag_index: usize = extra_start; + var cur_bit_bag: u32 = undefined; + var decl_i: u32 = 0; + while (decl_i < decls_len) : (decl_i += 1) { + if (decl_i % 8 == 0) { + cur_bit_bag = self.code.extra[bit_bag_index]; + bit_bag_index += 1; + } + const is_pub = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const is_exported = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const has_align = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + const has_section = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + + const sub_index = extra_index; + + const hash_u32s = self.code.extra[extra_index..][0..4]; + extra_index += 4; + const line = self.code.extra[extra_index]; + extra_index += 1; + const decl_name_index = self.code.extra[extra_index]; + extra_index += 1; + const decl_index = self.code.extra[extra_index]; + extra_index += 1; + const align_inst: Zir.Inst.Ref = if (!has_align) .none else inst: { + const inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :inst inst; + }; + const section_inst: Zir.Inst.Ref = if (!has_section) .none else inst: { + const inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :inst inst; + }; + + const pub_str = if (is_pub) "pub " else ""; + const hash_bytes = @bitCast([16]u8, hash_u32s.*); + try stream.writeByteNTimes(' ', self.indent); + if (decl_name_index == 0) { + const name = if (is_exported) "usingnamespace" else "comptime"; + try stream.writeAll(pub_str); + try stream.writeAll(name); + } else if (decl_name_index == 1) { + try stream.writeAll("test"); + } else { + const raw_decl_name = self.code.nullTerminatedString(decl_name_index); + const decl_name = if (raw_decl_name.len == 0) + self.code.nullTerminatedString(decl_name_index + 1) + else + raw_decl_name; + const test_str = if (raw_decl_name.len == 0) "test " else ""; + const export_str = if (is_exported) "export " else ""; + try stream.print("[{d}] {s}{s}{s}{}", .{ + sub_index, pub_str, test_str, export_str, std.zig.fmtId(decl_name), + }); + if (align_inst != .none) { + try stream.writeAll(" align("); + try self.writeInstRef(stream, align_inst); + try stream.writeAll(")"); + } + if (section_inst != .none) { + try stream.writeAll(" linksection("); + try self.writeInstRef(stream, section_inst); + try stream.writeAll(")"); + } + } + const tag = self.code.instructions.items(.tag)[decl_index]; + try stream.print(" line({d}) hash({}): %{d} = {s}(", .{ + line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index, @tagName(tag), + }); + + const decl_block_inst_data = self.code.instructions.items(.data)[decl_index].pl_node; + const sub_decl_node_off = decl_block_inst_data.src_node; + self.parent_decl_node = self.relativeToNodeIndex(sub_decl_node_off); + try self.writePlNodeBlockWithoutSrc(stream, decl_index); + self.parent_decl_node = parent_decl_node; + try self.writeSrc(stream, decl_block_inst_data.src()); + try stream.writeAll("\n"); + } + return extra_index; + } + + fn writeEnumDecl(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const small = @bitCast(Zir.Inst.EnumDecl.Small, extended.small); + var extra_index: usize = extended.operand; + + const src_node: ?i32 = if (small.has_src_node) blk: { + const src_node = @bitCast(i32, self.code.extra[extra_index]); + extra_index += 1; + break :blk src_node; + } else null; + + const tag_type_ref = if (small.has_tag_type) blk: { + const tag_type_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :blk tag_type_ref; + } else .none; + + const body_len = if (small.has_body_len) blk: { + const body_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk body_len; + } else 0; + + const fields_len = if (small.has_fields_len) blk: { + const fields_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk fields_len; + } else 0; + + const decls_len = if (small.has_decls_len) blk: { + const decls_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + + try stream.print("{s}, ", .{@tagName(small.name_strategy)}); + try self.writeFlag(stream, "nonexhaustive, ", small.nonexhaustive); + + if (decls_len == 0) { + try stream.writeAll("{}, "); + } else { + try stream.writeAll("{\n"); + self.indent += 2; + extra_index = try self.writeDecls(stream, decls_len, extra_index); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}, "); + } + + if (tag_type_ref != .none) { + try self.writeInstRef(stream, tag_type_ref); + try stream.writeAll(", "); + } + + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body.len; + + if (fields_len == 0) { + assert(body.len == 0); + try stream.writeAll("{}, {})"); + } else { + const prev_parent_decl_node = self.parent_decl_node; + if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); + self.indent += 2; + if (body.len == 0) { + try stream.writeAll("{}, {\n"); + } else { + try stream.writeAll("{\n"); + try self.writeBody(stream, body); + + try stream.writeByteNTimes(' ', self.indent - 2); + try stream.writeAll("}, {\n"); + } + + const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable; + const body_end = extra_index; + extra_index += bit_bags_count; + var bit_bag_index: usize = body_end; + var cur_bit_bag: u32 = undefined; + var field_i: u32 = 0; + while (field_i < fields_len) : (field_i += 1) { + if (field_i % 32 == 0) { + cur_bit_bag = self.code.extra[bit_bag_index]; + bit_bag_index += 1; + } + const has_tag_value = @truncate(u1, cur_bit_bag) != 0; + cur_bit_bag >>= 1; + + const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeByteNTimes(' ', self.indent); + try stream.print("{}", .{std.zig.fmtId(field_name)}); + + if (has_tag_value) { + const tag_value_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeAll(" = "); + try self.writeInstRef(stream, tag_value_ref); + } + try stream.writeAll(",\n"); + } + self.parent_decl_node = prev_parent_decl_node; + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("})"); + } + try self.writeSrcNode(stream, src_node); + } + + fn writeOpaqueDecl( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + name_strategy: Zir.Inst.NameStrategy, + ) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.OpaqueDecl, inst_data.payload_index); + const decls_len = extra.data.decls_len; + + try stream.print("{s}, ", .{@tagName(name_strategy)}); + + if (decls_len == 0) { + try stream.writeAll("}) "); + } else { + try stream.writeAll("\n"); + self.indent += 2; + _ = try self.writeDecls(stream, decls_len, extra.end); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}) "); + } + try self.writeSrc(stream, inst_data.src()); + } + + fn writeErrorSetDecl( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + name_strategy: Zir.Inst.NameStrategy, + ) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.ErrorSetDecl, inst_data.payload_index); + const fields = self.code.extra[extra.end..][0..extra.data.fields_len]; + + try stream.print("{s}, ", .{@tagName(name_strategy)}); + + try stream.writeAll("{\n"); + self.indent += 2; + for (fields) |str_index| { + const name = self.code.nullTerminatedString(str_index); + try stream.writeByteNTimes(' ', self.indent); + try stream.print("{},\n", .{std.zig.fmtId(name)}); + } + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}) "); + + try self.writeSrc(stream, inst_data.src()); + } + + fn writePlNodeSwitchBr( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + special_prong: Zir.SpecialProng, + ) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); + const special: struct { + body: []const Zir.Inst.Index, + end: usize, + } = switch (special_prong) { + .none => .{ .body = &.{}, .end = extra.end }, + .under, .@"else" => blk: { + const body_len = self.code.extra[extra.end]; + const extra_body_start = extra.end + 1; + break :blk .{ + .body = self.code.extra[extra_body_start..][0..body_len], + .end = extra_body_start + body_len, + }; + }, + }; + + try self.writeInstRef(stream, extra.data.operand); + + if (special.body.len != 0) { + const prong_name = switch (special_prong) { + .@"else" => "else", + .under => "_", + else => unreachable, + }; + try stream.print(", {s} => {{\n", .{prong_name}); + self.indent += 2; + try self.writeBody(stream, special.body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + + var extra_index: usize = special.end; + { + var scalar_i: usize = 0; + while (scalar_i < extra.data.cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + const body_len = self.code.extra[extra_index]; + extra_index += 1; + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + + try stream.writeAll(", "); + try self.writeInstRef(stream, item_ref); + try stream.writeAll(" => {\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + } + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writePlNodeSwitchBlockMulti( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + special_prong: Zir.SpecialProng, + ) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.SwitchBlockMulti, inst_data.payload_index); + const special: struct { + body: []const Zir.Inst.Index, + end: usize, + } = switch (special_prong) { + .none => .{ .body = &.{}, .end = extra.end }, + .under, .@"else" => blk: { + const body_len = self.code.extra[extra.end]; + const extra_body_start = extra.end + 1; + break :blk .{ + .body = self.code.extra[extra_body_start..][0..body_len], + .end = extra_body_start + body_len, + }; + }, + }; + + try self.writeInstRef(stream, extra.data.operand); + + if (special.body.len != 0) { + const prong_name = switch (special_prong) { + .@"else" => "else", + .under => "_", + else => unreachable, + }; + try stream.print(", {s} => {{\n", .{prong_name}); + self.indent += 2; + try self.writeBody(stream, special.body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + + var extra_index: usize = special.end; + { + var scalar_i: usize = 0; + while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + const body_len = self.code.extra[extra_index]; + extra_index += 1; + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + + try stream.writeAll(", "); + try self.writeInstRef(stream, item_ref); + try stream.writeAll(" => {\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + } + { + var multi_i: usize = 0; + while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) { + const items_len = self.code.extra[extra_index]; + extra_index += 1; + const ranges_len = self.code.extra[extra_index]; + extra_index += 1; + const body_len = self.code.extra[extra_index]; + extra_index += 1; + const items = self.code.refSlice(extra_index, items_len); + extra_index += items_len; + + for (items) |item_ref| { + try stream.writeAll(", "); + try self.writeInstRef(stream, item_ref); + } + + var range_i: usize = 0; + while (range_i < ranges_len) : (range_i += 1) { + const item_first = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + const item_last = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeAll(", "); + try self.writeInstRef(stream, item_first); + try stream.writeAll("..."); + try self.writeInstRef(stream, item_last); + } + + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + try stream.writeAll(" => {\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + } + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writePlNodeField(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Field, inst_data.payload_index).data; + const name = self.code.nullTerminatedString(extra.field_name_start); + try self.writeInstRef(stream, extra.lhs); + try stream.print(", \"{}\") ", .{std.zig.fmtEscapes(name)}); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeAs(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.As, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.dest_type); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.operand); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeNode( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const src_node = self.code.instructions.items(.data)[inst].node; + const src: LazySrcLoc = .{ .node_offset = src_node }; + try stream.writeAll(") "); + try self.writeSrc(stream, src); + } + + fn writeStrTok( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].str_tok; + const str = inst_data.get(self.code); + try stream.print("\"{}\") ", .{std.zig.fmtEscapes(str)}); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeFunc( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + inferred_error_set: bool, + ) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const extra = self.code.extraData(Zir.Inst.Func, inst_data.payload_index); + var extra_index = extra.end; + + const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + + const body = self.code.extra[extra_index..][0..extra.data.body_len]; + extra_index += body.len; + + var src_locs: Zir.Inst.Func.SrcLocs = undefined; + if (body.len != 0) { + src_locs = self.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; + } + return self.writeFuncCommon( + stream, + ret_ty_body, + inferred_error_set, + false, + false, + .none, + .none, + body, + src, + src_locs, + ); + } + + fn writeFuncExtended(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.ExtendedFunc, extended.operand); + const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; + const small = @bitCast(Zir.Inst.ExtendedFunc.Small, extended.small); + + var extra_index: usize = extra.end; + if (small.has_lib_name) { + const lib_name = self.code.nullTerminatedString(self.code.extra[extra_index]); + extra_index += 1; + try stream.print("lib_name=\"{}\", ", .{std.zig.fmtEscapes(lib_name)}); + } + try self.writeFlag(stream, "test, ", small.is_test); + const cc: Zir.Inst.Ref = if (!small.has_cc) .none else blk: { + const cc = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :blk cc; + }; + const align_inst: Zir.Inst.Ref = if (!small.has_align) .none else blk: { + const align_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :blk align_inst; + }; + + const ret_ty_body = self.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + + const body = self.code.extra[extra_index..][0..extra.data.body_len]; + extra_index += body.len; + + var src_locs: Zir.Inst.Func.SrcLocs = undefined; + if (body.len != 0) { + src_locs = self.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; + } + return self.writeFuncCommon( + stream, + ret_ty_body, + small.is_inferred_error, + small.is_var_args, + small.is_extern, + cc, + align_inst, + body, + src, + src_locs, + ); + } + + fn writeVarExtended(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.ExtendedVar, extended.operand); + const small = @bitCast(Zir.Inst.ExtendedVar.Small, extended.small); + + try self.writeInstRef(stream, extra.data.var_type); + + var extra_index: usize = extra.end; + if (small.has_lib_name) { + const lib_name = self.code.nullTerminatedString(self.code.extra[extra_index]); + extra_index += 1; + try stream.print(", lib_name=\"{}\"", .{std.zig.fmtEscapes(lib_name)}); + } + const align_inst: Zir.Inst.Ref = if (!small.has_align) .none else blk: { + const align_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :blk align_inst; + }; + const init_inst: Zir.Inst.Ref = if (!small.has_init) .none else blk: { + const init_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :blk init_inst; + }; + try self.writeFlag(stream, ", is_extern", small.is_extern); + try self.writeOptionalInstRef(stream, ", align=", align_inst); + try self.writeOptionalInstRef(stream, ", init=", init_inst); + try stream.writeAll("))"); + } + + fn writeBoolBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].bool_br; + const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index); + const body = self.code.extra[extra.end..][0..extra.data.body_len]; + try self.writeInstRef(stream, inst_data.lhs); + try stream.writeAll(", {\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("})"); + } + + fn writeIntType(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const int_type = self.code.instructions.items(.data)[inst].int_type; + const prefix: u8 = switch (int_type.signedness) { + .signed => 'i', + .unsigned => 'u', + }; + try stream.print("{c}{d}) ", .{ prefix, int_type.bit_count }); + try self.writeSrc(stream, int_type.src()); + } + + fn writeBreak(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].@"break"; + + try self.writeInstIndex(stream, inst_data.block_inst); + try stream.writeAll(", "); + try self.writeInstRef(stream, inst_data.operand); + try stream.writeAll(")"); + } + + fn writeUnreachable(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].@"unreachable"; + const safety_str = if (inst_data.safety) "safe" else "unsafe"; + try stream.print("{s}) ", .{safety_str}); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeFuncCommon( + self: *Writer, + stream: anytype, + ret_ty_body: []const Zir.Inst.Index, + inferred_error_set: bool, + var_args: bool, + is_extern: bool, + cc: Zir.Inst.Ref, + align_inst: Zir.Inst.Ref, + body: []const Zir.Inst.Index, + src: LazySrcLoc, + src_locs: Zir.Inst.Func.SrcLocs, + ) !void { + if (ret_ty_body.len == 0) { + try stream.writeAll("ret_ty=void"); + } else { + try stream.writeAll("ret_ty={\n"); + self.indent += 2; + try self.writeBody(stream, ret_ty_body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + + try self.writeOptionalInstRef(stream, ", cc=", cc); + try self.writeOptionalInstRef(stream, ", align=", align_inst); + try self.writeFlag(stream, ", vargs", var_args); + try self.writeFlag(stream, ", extern", is_extern); + try self.writeFlag(stream, ", inferror", inferred_error_set); + + if (body.len == 0) { + try stream.writeAll(", body={}) "); + } else { + try stream.writeAll(", body={\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}) "); + } + if (body.len != 0) { + try stream.print("(lbrace={d}:{d},rbrace={d}:{d}) ", .{ + src_locs.lbrace_line, @truncate(u16, src_locs.columns), + src_locs.rbrace_line, @truncate(u16, src_locs.columns >> 16), + }); + } + try self.writeSrc(stream, src); + } + + fn writeSwitchCapture(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].switch_capture; + try self.writeInstIndex(stream, inst_data.switch_inst); + try stream.print(", {d})", .{inst_data.prong_index}); + } + + fn writeDbgStmt(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].dbg_stmt; + try stream.print("{d}, {d})", .{ inst_data.line, inst_data.column }); + } + + fn writeInstRef(self: *Writer, stream: anytype, ref: Zir.Inst.Ref) !void { + var i: usize = @enumToInt(ref); + + if (i < Zir.Inst.Ref.typed_value_map.len) { + return stream.print("@{}", .{ref}); + } + i -= Zir.Inst.Ref.typed_value_map.len; + + return self.writeInstIndex(stream, @intCast(Zir.Inst.Index, i)); + } + + fn writeInstIndex(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + _ = self; + return stream.print("%{d}", .{inst}); + } + + fn writeOptionalInstRef( + self: *Writer, + stream: anytype, + prefix: []const u8, + inst: Zir.Inst.Ref, + ) !void { + if (inst == .none) return; + try stream.writeAll(prefix); + try self.writeInstRef(stream, inst); + } + + fn writeFlag( + self: *Writer, + stream: anytype, + name: []const u8, + flag: bool, + ) !void { + _ = self; + if (!flag) return; + try stream.writeAll(name); + } + + fn writeSrc(self: *Writer, stream: anytype, src: LazySrcLoc) !void { + const tree = self.file.tree; + const src_loc: Module.SrcLoc = .{ + .file_scope = self.file, + .parent_decl_node = self.parent_decl_node, + .lazy = src, + }; + // Caller must ensure AST tree is loaded. + const abs_byte_off = src_loc.byteOffset(self.gpa) catch unreachable; + const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off); + try stream.print("{s}:{d}:{d}", .{ + @tagName(src), delta_line.line + 1, delta_line.column + 1, + }); + } + + fn writeSrcNode(self: *Writer, stream: anytype, src_node: ?i32) !void { + const node_offset = src_node orelse return; + const src: LazySrcLoc = .{ .node_offset = node_offset }; + try stream.writeAll(" "); + return self.writeSrc(stream, src); + } + + fn writeBody(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void { + for (body) |inst| { + try stream.writeByteNTimes(' ', self.indent); + try stream.print("%{d} ", .{inst}); + try self.writeInstToStream(stream, inst); + try stream.writeByte('\n'); + } + } +}; From 4b2d7a9c67760aa9a81bfd364ac0d88cbb9737f1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 20 Sep 2021 15:44:09 -0700 Subject: [PATCH 076/160] stage2: implement comptime bitwise nand --- lib/std/math/big/int.zig | 3 +++ src/Sema.zig | 2 +- src/value.zig | 13 +++++++---- test/behavior/atomics.zig | 28 +++++++++++++++++++++++ test/behavior/atomics_stage1.zig | 38 ++++++-------------------------- 5 files changed, 48 insertions(+), 36 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 9efd4d5752..d223d3135f 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -566,6 +566,7 @@ pub const Mutable = struct { llor(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]); r.len = b.limbs.len; } + r.positive = a.positive or b.positive; } /// r = a & b @@ -580,6 +581,7 @@ pub const Mutable = struct { lland(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]); r.normalize(a.limbs.len); } + r.positive = a.positive and b.positive; } /// r = a ^ b @@ -594,6 +596,7 @@ pub const Mutable = struct { llxor(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]); r.normalize(b.limbs.len); } + r.positive = a.positive or b.positive; } /// rma may alias x or y. diff --git a/src/Sema.zig b/src/Sema.zig index 72dc25a58a..f53dbea90c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7871,7 +7871,7 @@ fn zirAtomicRmw(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE .Add => try stored_val.numberAddWrap(operand_val, operand_ty, sema.arena, target), .Sub => try stored_val.numberSubWrap(operand_val, operand_ty, sema.arena, target), .And => try stored_val.bitwiseAnd (operand_val, sema.arena), - .Nand => try stored_val.bitwiseNand (operand_val, operand_ty, sema.arena), + .Nand => try stored_val.bitwiseNand (operand_val, operand_ty, sema.arena, target), .Or => try stored_val.bitwiseOr (operand_val, sema.arena), .Xor => try stored_val.bitwiseXor (operand_val, sema.arena), .Max => try stored_val.numberMax (operand_val, sema.arena), diff --git a/src/value.zig b/src/value.zig index 177359d652..934aab7bc8 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1690,12 +1690,17 @@ pub const Value = extern union { } /// operands must be integers; handles undefined. - pub fn bitwiseNand(lhs: Value, rhs: Value, ty: Type, arena: *Allocator) !Value { + pub fn bitwiseNand(lhs: Value, rhs: Value, ty: Type, arena: *Allocator, target: Target) !Value { if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); - _ = ty; - _ = arena; - @panic("TODO comptime bitwise NAND"); + const anded = try bitwiseAnd(lhs, rhs, arena); + + const all_ones = if (ty.isSignedInt()) + try Value.Tag.int_i64.create(arena, -1) + else + try ty.maxInt(arena, target); + + return bitwiseXor(anded, all_ones, arena); } /// operands must be integers; handles undefined. diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index 311fb4b3b2..14e0933c52 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -167,3 +167,31 @@ fn testAtomicRmwFloat() !void { _ = @atomicRmw(f32, &x, .Sub, 2, .SeqCst); try expect(x == 4); } + +test "atomicrmw with ints" { + try testAtomicRmwInt(); + comptime try testAtomicRmwInt(); +} + +fn testAtomicRmwInt() !void { + var x: u8 = 1; + var res = @atomicRmw(u8, &x, .Xchg, 3, .SeqCst); + try expect(x == 3 and res == 1); + _ = @atomicRmw(u8, &x, .Add, 3, .SeqCst); + try expect(x == 6); + _ = @atomicRmw(u8, &x, .Sub, 1, .SeqCst); + try expect(x == 5); + _ = @atomicRmw(u8, &x, .And, 4, .SeqCst); + try expect(x == 4); + _ = @atomicRmw(u8, &x, .Nand, 4, .SeqCst); + try expect(x == 0xfb); + _ = @atomicRmw(u8, &x, .Or, 6, .SeqCst); + try expect(x == 0xff); + _ = @atomicRmw(u8, &x, .Xor, 2, .SeqCst); + try expect(x == 0xfd); + + _ = @atomicRmw(u8, &x, .Max, 1, .SeqCst); + try expect(x == 0xfd); + _ = @atomicRmw(u8, &x, .Min, 1, .SeqCst); + try expect(x == 1); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig index 424f33b403..f37d1dc8c1 100644 --- a/test/behavior/atomics_stage1.zig +++ b/test/behavior/atomics_stage1.zig @@ -3,39 +3,15 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const builtin = @import("builtin"); -test "atomicrmw with ints" { - try testAtomicRmwInt(); - comptime try testAtomicRmwInt(); -} - -fn testAtomicRmwInt() !void { - var x: u8 = 1; - var res = @atomicRmw(u8, &x, .Xchg, 3, .SeqCst); - try expect(x == 3 and res == 1); - _ = @atomicRmw(u8, &x, .Add, 3, .SeqCst); - try expect(x == 6); - _ = @atomicRmw(u8, &x, .Sub, 1, .SeqCst); - try expect(x == 5); - _ = @atomicRmw(u8, &x, .And, 4, .SeqCst); - try expect(x == 4); - _ = @atomicRmw(u8, &x, .Nand, 4, .SeqCst); - try expect(x == 0xfb); - _ = @atomicRmw(u8, &x, .Or, 6, .SeqCst); - try expect(x == 0xff); - _ = @atomicRmw(u8, &x, .Xor, 2, .SeqCst); - try expect(x == 0xfd); - - _ = @atomicRmw(u8, &x, .Max, 1, .SeqCst); - try expect(x == 0xfd); - _ = @atomicRmw(u8, &x, .Min, 1, .SeqCst); - try expect(x == 1); -} - test "atomics with different types" { try testAtomicsWithType(bool, true, false); - inline for (.{ u1, i4, u5, i15, u24 }) |T| { - try testAtomicsWithType(T, 0, 1); - } + + try testAtomicsWithType(u1, 0, 1); + try testAtomicsWithType(i4, 0, 1); + try testAtomicsWithType(u5, 0, 1); + try testAtomicsWithType(i15, 0, 1); + try testAtomicsWithType(u24, 0, 1); + try testAtomicsWithType(u0, 0, 0); try testAtomicsWithType(i0, 0, 0); } From abc30f79489b68f6dc0ee4b408c63a8e783215d1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 20 Sep 2021 16:48:42 -0700 Subject: [PATCH 077/160] stage2: improve handling of 0 bit types * Sema: zirAtomicLoad handles 0-bit types correctly * LLVM backend: when lowering function types, elide parameters with 0-bit types. * Type: abiSize handles u0/i0 correctly --- src/Sema.zig | 4 ++++ src/codegen/llvm.zig | 16 ++++++++++------ src/type.zig | 1 + test/behavior.zig | 1 - test/behavior/atomics.zig | 24 ++++++++++++++++++++++++ test/behavior/atomics_stage1.zig | 28 ---------------------------- 6 files changed, 39 insertions(+), 35 deletions(-) delete mode 100644 test/behavior/atomics_stage1.zig diff --git a/src/Sema.zig b/src/Sema.zig index f53dbea90c..990aa4ddf0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7803,6 +7803,10 @@ fn zirAtomicLoad(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile else => {}, } + if (try sema.typeHasOnePossibleValue(block, elem_ty_src, elem_ty)) |val| { + return sema.addConstant(elem_ty, val); + } + if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { if (try ptr_val.pointerDeref(sema.arena)) |elem_val| { return sema.addConstant(elem_ty, elem_val); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 2e835260af..2f703e2d68 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -541,17 +541,21 @@ pub const DeclGen = struct { defer self.gpa.free(fn_param_types); zig_fn_type.fnParamTypes(fn_param_types); - const llvm_param = try self.gpa.alloc(*const llvm.Type, fn_param_len); - defer self.gpa.free(llvm_param); + const llvm_param_buffer = try self.gpa.alloc(*const llvm.Type, fn_param_len); + defer self.gpa.free(llvm_param_buffer); - for (fn_param_types) |fn_param, i| { - llvm_param[i] = try self.llvmType(fn_param); + var llvm_params_len: c_uint = 0; + for (fn_param_types) |fn_param| { + if (fn_param.hasCodeGenBits()) { + llvm_param_buffer[llvm_params_len] = try self.llvmType(fn_param); + llvm_params_len += 1; + } } const fn_type = llvm.functionType( try self.llvmType(return_type), - llvm_param.ptr, - @intCast(c_uint, fn_param_len), + llvm_param_buffer.ptr, + llvm_params_len, .False, ); const llvm_fn = self.llvmModule().addFunction(decl.name, fn_type); diff --git a/src/type.zig b/src/type.zig index 122faefbc7..c2dc150347 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1822,6 +1822,7 @@ pub const Type = extern union { .int_signed, .int_unsigned => { const bits: u16 = self.cast(Payload.Bits).?.data; + if (bits == 0) return 0; return std.math.ceilPowerOfTwoPromote(u16, (bits + 7) / 8); }, diff --git a/test/behavior.zig b/test/behavior.zig index 10d666f6a4..ebf3fff83f 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -23,7 +23,6 @@ test { _ = @import("behavior/asm.zig"); _ = @import("behavior/async_fn.zig"); } - _ = @import("behavior/atomics_stage1.zig"); _ = @import("behavior/await_struct.zig"); _ = @import("behavior/bit_shifting.zig"); _ = @import("behavior/bitcast.zig"); diff --git a/test/behavior/atomics.zig b/test/behavior/atomics.zig index 14e0933c52..b22183bdbd 100644 --- a/test/behavior/atomics.zig +++ b/test/behavior/atomics.zig @@ -195,3 +195,27 @@ fn testAtomicRmwInt() !void { _ = @atomicRmw(u8, &x, .Min, 1, .SeqCst); try expect(x == 1); } + +test "atomics with different types" { + try testAtomicsWithType(bool, true, false); + + try testAtomicsWithType(u1, 0, 1); + try testAtomicsWithType(i4, 0, 1); + try testAtomicsWithType(u5, 0, 1); + try testAtomicsWithType(i15, 0, 1); + try testAtomicsWithType(u24, 0, 1); + + try testAtomicsWithType(u0, 0, 0); + try testAtomicsWithType(i0, 0, 0); +} + +fn testAtomicsWithType(comptime T: type, a: T, b: T) !void { + var x: T = b; + @atomicStore(T, &x, a, .SeqCst); + try expect(x == a); + try expect(@atomicLoad(T, &x, .SeqCst) == a); + try expect(@atomicRmw(T, &x, .Xchg, b, .SeqCst) == a); + try expect(@cmpxchgStrong(T, &x, b, a, .SeqCst, .SeqCst) == null); + if (@sizeOf(T) != 0) + try expect(@cmpxchgStrong(T, &x, b, a, .SeqCst, .SeqCst).? == a); +} diff --git a/test/behavior/atomics_stage1.zig b/test/behavior/atomics_stage1.zig deleted file mode 100644 index f37d1dc8c1..0000000000 --- a/test/behavior/atomics_stage1.zig +++ /dev/null @@ -1,28 +0,0 @@ -const std = @import("std"); -const expect = std.testing.expect; -const expectEqual = std.testing.expectEqual; -const builtin = @import("builtin"); - -test "atomics with different types" { - try testAtomicsWithType(bool, true, false); - - try testAtomicsWithType(u1, 0, 1); - try testAtomicsWithType(i4, 0, 1); - try testAtomicsWithType(u5, 0, 1); - try testAtomicsWithType(i15, 0, 1); - try testAtomicsWithType(u24, 0, 1); - - try testAtomicsWithType(u0, 0, 0); - try testAtomicsWithType(i0, 0, 0); -} - -fn testAtomicsWithType(comptime T: type, a: T, b: T) !void { - var x: T = b; - @atomicStore(T, &x, a, .SeqCst); - try expect(x == a); - try expect(@atomicLoad(T, &x, .SeqCst) == a); - try expect(@atomicRmw(T, &x, .Xchg, b, .SeqCst) == a); - try expect(@cmpxchgStrong(T, &x, b, a, .SeqCst, .SeqCst) == null); - if (@sizeOf(T) != 0) - try expect(@cmpxchgStrong(T, &x, b, a, .SeqCst, .SeqCst).? == a); -} From 380ca2685548ac916c825842033671ac8a97f577 Mon Sep 17 00:00:00 2001 From: "Mr. Paul" Date: Mon, 20 Sep 2021 15:32:34 +0700 Subject: [PATCH 078/160] docgen: re-enable syntax checking for code blocks In a previous commit (f4d3d29), syntax checking for code blocks with the `syntax` type was disabled due to a change in astgen now checking the existence of identifiers. The change in astgen caused some code samples in the language reference to cause compilation errors. This commit updates the code samples in the language reference and re-enables syntax checking. Some code samples have been changed to unchecked syntax blocks using `{#syntax_block#}` when suitable. --- doc/docgen.zig | 4 +- doc/langref.html.in | 248 ++++++++++++++++++++++++++------------------ 2 files changed, 148 insertions(+), 104 deletions(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index 79fd1519cf..c4450e80b6 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -1222,9 +1222,7 @@ fn genHtml( try printSourceBlock(allocator, tokenizer, out, syntax_block); - // TODO: remove code.just_check_syntax after updating code samples - // that have stopped working due to a change in the compiler. - if (!do_code_tests or code.just_check_syntax) { + if (!do_code_tests) { continue; } diff --git a/doc/langref.html.in b/doc/langref.html.in index 384ecfffe3..97503aed72 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -4771,6 +4771,8 @@ test "parse u64" { {#header_open|catch#}

    If you want to provide a default value, you can use the {#syntax#}catch{#endsyntax#} binary operator:

    {#code_begin|syntax#} +const parseU64 = @import("error_union_parsing_u64.zig").parseU64; + fn doAThing(str: []u8) void { const number = parseU64(str, 10) catch 13; _ = number; // ... @@ -4786,6 +4788,8 @@ fn doAThing(str: []u8) void {

    Let's say you wanted to return the error if you got one, otherwise continue with the function logic:

    {#code_begin|syntax#} +const parseU64 = @import("error_union_parsing_u64.zig").parseU64; + fn doAThing(str: []u8) !void { const number = parseU64(str, 10) catch |err| return err; _ = number; // ... @@ -4795,6 +4799,8 @@ fn doAThing(str: []u8) !void { There is a shortcut for this. The {#syntax#}try{#endsyntax#} expression:

    {#code_begin|syntax#} +const parseU64 = @import("error_union_parsing_u64.zig").parseU64; + fn doAThing(str: []u8) !void { const number = try parseU64(str, 10); _ = number; // ... @@ -4810,7 +4816,7 @@ fn doAThing(str: []u8) !void { Maybe you know with complete certainty that an expression will never be an error. In this case you can do this:

    - {#code_begin|syntax#}const number = parseU64("1234", 10) catch unreachable;{#code_end#} + {#syntax#}const number = parseU64("1234", 10) catch unreachable;{#endsyntax#}

    Here we know for sure that "1234" will parse successfully. So we put the {#syntax#}unreachable{#endsyntax#} value on the right hand side. {#syntax#}unreachable{#endsyntax#} generates @@ -4822,7 +4828,7 @@ fn doAThing(str: []u8) !void { Finally, you may want to take a different action for every situation. For that, we combine the {#link|if#} and {#link|switch#} expression:

    - {#code_begin|syntax#} + {#syntax_block|zig|handle_all_error_scenarios.zig#} fn doAThing(str: []u8) void { if (parseU64(str, 10)) |number| { doSomethingWithNumber(number); @@ -4834,7 +4840,7 @@ fn doAThing(str: []u8) void { error.InvalidChar => unreachable, } } - {#code_end#} + {#end_syntax_block#} {#header_open|errdefer#}

    The other component to error handling is defer statements. @@ -4845,7 +4851,7 @@ fn doAThing(str: []u8) void {

    Example:

    - {#code_begin|syntax#} + {#syntax_block|zig|errdefer_example.zig#} fn createFoo(param: i32) !Foo { const foo = try tryToAllocateFoo(); // now we have allocated foo. we need to free it if the function fails. @@ -4863,7 +4869,7 @@ fn createFoo(param: i32) !Foo { // but the defer will run! return foo; } - {#code_end#} + {#end_syntax_block#}

    The neat thing about this is that you get robust error handling without the verbosity and cognitive overhead of trying to make sure every exit path @@ -5132,12 +5138,12 @@ fn bang2() void { For the case when no errors are returned, the cost is a single memory write operation, only in the first non-failable function in the call graph that calls a failable function, i.e. when a function returning {#syntax#}void{#endsyntax#} calls a function returning {#syntax#}error{#endsyntax#}. This is to initialize this struct in the stack memory:

    - {#code_begin|syntax#} + {#syntax_block|zig|stack_trace_struct.zig#} pub const StackTrace = struct { index: usize, instruction_addresses: [N]usize, }; - {#code_end#} + {#end_syntax_block#}

    Here, N is the maximum function call depth as determined by call graph analysis. Recursion is ignored and counts for 2.

    @@ -5150,13 +5156,13 @@ pub const StackTrace = struct {

    When generating the code for a function that returns an error, just before the {#syntax#}return{#endsyntax#} statement (only for the {#syntax#}return{#endsyntax#} statements that return errors), Zig generates a call to this function:

    - {#code_begin|syntax#} + {#syntax_block|zig|zig_return_error_fn.zig#} // marked as "no-inline" in LLVM IR fn __zig_return_error(stack_trace: *StackTrace) void { stack_trace.instruction_addresses[stack_trace.index] = @returnAddress(); stack_trace.index = (stack_trace.index + 1) % N; } - {#code_end#} + {#end_syntax_block#}

    The cost is 2 math operations plus some memory reads and writes. The memory accessed is constrained and should remain cached for the duration of the error return bubbling.

    @@ -5206,16 +5212,16 @@ const optional_int: ?i32 = 5678; Task: call malloc, if the result is null, return null.

    C code

    -
    // malloc prototype included for reference
    +      {#syntax_block|c|call_malloc_in_c.c#}// malloc prototype included for reference
     void *malloc(size_t size);
     
     struct Foo *do_a_thing(void) {
         char *ptr = malloc(1234);
         if (!ptr) return NULL;
         // ...
    -}
    +}{#end_syntax_block#}

    Zig code

    - {#code_begin|syntax#} + {#syntax_block|zig|call_malloc_from_zig.zig#} // malloc prototype included for reference extern fn malloc(size: size_t) ?*u8; @@ -5223,7 +5229,7 @@ fn doAThing() ?*Foo { const ptr = malloc(1234) orelse return null; _ = ptr; // ... } - {#code_end#} + {#end_syntax_block#}

    Here, Zig is at least as convenient, if not more, than C. And, the type of "ptr" is {#syntax#}*u8{#endsyntax#} not {#syntax#}?*u8{#endsyntax#}. The {#syntax#}orelse{#endsyntax#} keyword @@ -5233,7 +5239,7 @@ fn doAThing() ?*Foo {

    The other form of checking against NULL you might see looks like this:

    -
    void do_a_thing(struct Foo *foo) {
    +      {#syntax_block|c|checking_null_in_c.c#}void do_a_thing(struct Foo *foo) {
         // do some stuff
     
         if (foo) {
    @@ -5241,11 +5247,14 @@ fn doAThing() ?*Foo {
         }
     
         // do some stuff
    -}
    +}{#end_syntax_block#}

    In Zig you can accomplish the same thing:

    - {#code_begin|syntax#} + {#code_begin|syntax|checking_null_in_zig#} +const Foo = struct{}; +fn doSomethingWithFoo(foo: *Foo) void { _ = foo; } + fn doAThing(optional_foo: ?*Foo) void { // do some stuff @@ -6111,7 +6120,7 @@ test "perform fn" { different code. In this example, the function {#syntax#}performFn{#endsyntax#} is generated three different times, for the different values of {#syntax#}prefix_char{#endsyntax#} provided:

    - {#code_begin|syntax#} + {#syntax_block|zig|performFn_1#} // From the line: // expect(performFn('t', 1) == 6); fn performFn(start_value: i32) i32 { @@ -6120,8 +6129,8 @@ fn performFn(start_value: i32) i32 { result = three(result); return result; } - {#code_end#} - {#code_begin|syntax#} + {#end_syntax_block#} + {#syntax_block|zig|performFn_2#} // From the line: // expect(performFn('o', 0) == 1); fn performFn(start_value: i32) i32 { @@ -6129,15 +6138,15 @@ fn performFn(start_value: i32) i32 { result = one(result); return result; } - {#code_end#} - {#code_begin|syntax#} + {#end_syntax_block#} + {#syntax_block|zig|performFn_3#} // From the line: // expect(performFn('w', 99) == 99); fn performFn(start_value: i32) i32 { var result: i32 = start_value; return result; } - {#code_end#} + {#end_syntax_block#}

    Note that this happens even in a debug build; in a release build these generated functions still pass through rigorous LLVM optimizations. The important thing to note, however, is not that this @@ -6367,11 +6376,11 @@ const Node = struct { it works fine.

    {#header_close#} - {#header_open|Case Study: printf in Zig#} + {#header_open|Case Study: print in Zig#}

    - Putting all of this together, let's see how {#syntax#}printf{#endsyntax#} works in Zig. + Putting all of this together, let's see how {#syntax#}print{#endsyntax#} works in Zig.

    - {#code_begin|exe|printf#} + {#code_begin|exe|print#} const print = @import("std").debug.print; const a_number: i32 = 1234; @@ -6386,67 +6395,84 @@ pub fn main() void { Let's crack open the implementation of this and see how it works:

    - {#code_begin|syntax#} -/// Calls print and then flushes the buffer. -pub fn printf(self: *Writer, comptime format: []const u8, args: anytype) anyerror!void { - const State = enum { - start, - open_brace, - close_brace, - }; + {#code_begin|syntax|poc_print_fn#} +const Writer = struct { + /// Calls print and then flushes the buffer. + pub fn print(self: *Writer, comptime format: []const u8, args: anytype) anyerror!void { + const State = enum { + start, + open_brace, + close_brace, + }; - comptime var start_index: usize = 0; - comptime var state = State.start; - comptime var next_arg: usize = 0; + comptime var start_index: usize = 0; + comptime var state = State.start; + comptime var next_arg: usize = 0; - inline for (format) |c, i| { - switch (state) { - State.start => switch (c) { - '{' => { - if (start_index < i) try self.write(format[start_index..i]); - state = State.open_brace; + inline for (format) |c, i| { + switch (state) { + State.start => switch (c) { + '{' => { + if (start_index < i) try self.write(format[start_index..i]); + state = State.open_brace; + }, + '}' => { + if (start_index < i) try self.write(format[start_index..i]); + state = State.close_brace; + }, + else => {}, }, - '}' => { - if (start_index < i) try self.write(format[start_index..i]); - state = State.close_brace; + State.open_brace => switch (c) { + '{' => { + state = State.start; + start_index = i; + }, + '}' => { + try self.printValue(args[next_arg]); + next_arg += 1; + state = State.start; + start_index = i + 1; + }, + 's' => { + continue; + }, + else => @compileError("Unknown format character: " ++ [1]u8{c}), }, - else => {}, - }, - State.open_brace => switch (c) { - '{' => { - state = State.start; - start_index = i; + State.close_brace => switch (c) { + '}' => { + state = State.start; + start_index = i; + }, + else => @compileError("Single '}' encountered in format string"), }, - '}' => { - try self.printValue(args[next_arg]); - next_arg += 1; - state = State.start; - start_index = i + 1; - }, - else => @compileError("Unknown format character: " ++ c), - }, - State.close_brace => switch (c) { - '}' => { - state = State.start; - start_index = i; - }, - else => @compileError("Single '}' encountered in format string"), - }, + } } - } - comptime { - if (args.len != next_arg) { - @compileError("Unused arguments"); + comptime { + if (args.len != next_arg) { + @compileError("Unused arguments"); + } + if (state != State.start) { + @compileError("Incomplete format string: " ++ format); + } } - if (state != State.Start) { - @compileError("Incomplete format string: " ++ format); + if (start_index < format.len) { + try self.write(format[start_index..format.len]); } + try self.flush(); } - if (start_index < format.len) { - try self.write(format[start_index..format.len]); + + fn write(self: *Writer, value: []const u8) !void { + _ = self; + _ = value; } - try self.flush(); -} + pub fn printValue(self: *Writer, value: anytype) !void { + _ = self; + _ = value; + } + fn flush(self: *Writer) !void { + _ = self; + } +}; {#code_end#}

    This is a proof of concept implementation; the actual function in the standard library has more @@ -6459,8 +6485,8 @@ pub fn printf(self: *Writer, comptime format: []const u8, args: anytype) anyerro When this function is analyzed from our example code above, Zig partially evaluates the function and emits a function that actually looks like this:

    - {#code_begin|syntax#} -pub fn printf(self: *Writer, arg0: i32, arg1: []const u8) !void { + {#syntax_block|zig|Emitted print Function#} +pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void { try self.write("here is a string: '"); try self.printValue(arg0); try self.write("' here is a number: "); @@ -6468,28 +6494,46 @@ pub fn printf(self: *Writer, arg0: i32, arg1: []const u8) !void { try self.write("\n"); try self.flush(); } - {#code_end#} + {#end_syntax_block#}

    {#syntax#}printValue{#endsyntax#} is a function that takes a parameter of any type, and does different things depending on the type:

    - {#code_begin|syntax#} -pub fn printValue(self: *Writer, value: anytype) !void { - switch (@typeInfo(@TypeOf(value))) { - .Int => { - return self.printInt(T, value); - }, - .Float => { - return self.printFloat(T, value); - }, - else => { - @compileError("Unable to print type '" ++ @typeName(T) ++ "'"); - }, + {#code_begin|syntax|poc_printValue_fn#} + const Writer = struct { + pub fn printValue(self: *Writer, value: anytype) !void { + switch (@typeInfo(@TypeOf(value))) { + .Int => { + return self.writeInt(value); + }, + .Float => { + return self.writeFloat(value); + }, + .Pointer => { + return self.write(value); + }, + else => { + @compileError("Unable to print type '" ++ @typeName(@TypeOf(value)) ++ "'"); + }, + } } -} + + fn write(self: *Writer, value: []const u8) !void { + _ = self; + _ = value; + } + fn writeInt(self: *Writer, value: anytype) !void { + _ = self; + _ = value; + } + fn writeFloat(self: *Writer, value: anytype) !void { + _ = self; + _ = value; + } +}; {#code_end#}

    - And now, what happens if we give too many arguments to {#syntax#}printf{#endsyntax#}? + And now, what happens if we give too many arguments to {#syntax#}print{#endsyntax#}?

    {#code_begin|test_err|Unused argument in 'here is a string: '{s}' here is a number: {}#} const print = @import("std").debug.print; @@ -6497,7 +6541,7 @@ const print = @import("std").debug.print; const a_number: i32 = 1234; const a_string = "foobar"; -test "printf too many arguments" { +test "print too many arguments" { print("here is a string: '{s}' here is a number: {}\n", .{ a_string, a_number, @@ -6512,7 +6556,7 @@ test "printf too many arguments" { Zig doesn't care whether the format argument is a string literal, only that it is a compile-time known value that can be coerced to a {#syntax#}[]const u8{#endsyntax#}:

    - {#code_begin|exe|printf#} + {#code_begin|exe|print#} const print = @import("std").debug.print; const a_number: i32 = 1234; @@ -7401,9 +7445,11 @@ fn add(a: i32, b: i32) i32 { {#syntax#}@call{#endsyntax#} allows more flexibility than normal function call syntax does. The {#syntax#}CallOptions{#endsyntax#} struct is reproduced here:

    - {#code_begin|syntax#} + {#syntax_block|zig|builtin.CallOptions struct#} pub const CallOptions = struct { modifier: Modifier = .auto, + + /// Only valid when `Modifier` is `Modifier.async_kw`. stack: ?[]align(std.Target.stack_align) u8 = null, pub const Modifier = enum { @@ -7440,7 +7486,7 @@ pub const CallOptions = struct { compile_time, }; }; - {#code_end#} + {#end_syntax_block#} {#header_close#} {#header_open|@cDefine#} @@ -7554,7 +7600,7 @@ fn cmpxchgStrongButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_v This function performs a weak atomic compare exchange operation. It's the equivalent of this code, except atomic:

    - {#code_begin|syntax#} + {#syntax_block|zig|cmpxchgWeakButNotAtomic#} fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T { const old_value = ptr.*; if (old_value == expected_value and usuallyTrueButSometimesFalse()) { @@ -7564,7 +7610,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val return old_value; } } - {#code_end#} + {#end_syntax_block#}

    If you are using cmpxchg in a loop, the sporadic failure will be no problem, and {#syntax#}cmpxchgWeak{#endsyntax#} is the better choice, because it can be implemented more efficiently in machine instructions. @@ -10159,7 +10205,7 @@ pub fn main() void { This expression is evaluated at compile-time and is used to control preprocessor directives and include multiple .h files:

    - {#code_begin|syntax#} + {#syntax_block|zig|@cImport Expression#} const builtin = @import("builtin"); const c = @cImport({ @@ -10173,7 +10219,7 @@ const c = @cImport({ } @cInclude("soundio.h"); }); - {#code_end#} + {#end_syntax_block#} {#see_also|@cImport|@cInclude|@cDefine|@cUndef|@import#} {#header_close#} From 2a728f6e5f0c5d12e110313342e714f9f23c4044 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Mon, 20 Sep 2021 17:10:32 -0700 Subject: [PATCH 079/160] tokenizer: Fix index-out-of-bounds on string_literal_backslash right before EOF --- lib/std/zig/tokenizer.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 3fdbb3ec7b..72d597f077 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -700,7 +700,7 @@ pub const Tokenizer = struct { }, .string_literal_backslash => switch (c) { - '\n' => { + 0, '\n' => { result.tag = .invalid; break; }, @@ -1919,6 +1919,10 @@ test "tokenizer - invalid builtin identifiers" { try testTokenize("@0()", &.{ .invalid, .integer_literal, .l_paren, .r_paren }); } +test "tokenizer - backslash before eof in string literal" { + try testTokenize("\"\\", &.{.invalid}); +} + fn testTokenize(source: [:0]const u8, expected_tokens: []const Token.Tag) !void { var tokenizer = Tokenizer.init(source); for (expected_tokens) |expected_token_id| { From 9a54ff72df5cc8631c467f3478117eb85a952ced Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 19 Sep 2021 11:51:32 +0300 Subject: [PATCH 080/160] stage2: implement cImport --- src/AstGen.zig | 6 ++++ src/Compilation.zig | 2 +- src/Module.zig | 3 ++ src/Sema.zig | 88 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index f1eabe4c0c..32b62fc3f5 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7269,6 +7269,7 @@ fn builtinCall( return rvalue(gz, rl, result, node); }, .c_define => { + if (!gz.c_import) return gz.astgen.failNode(node, "C define valid only inside C import block", .{}); const name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[0]); const value = try comptimeExpr(gz, scope, .none, params[1]); const result = try gz.addExtendedPayload(.c_define, Zir.Inst.BinNode{ @@ -7641,6 +7642,8 @@ fn simpleCBuiltin( operand_node: Ast.Node.Index, tag: Zir.Inst.Extended, ) InnerError!Zir.Inst.Ref { + const name: []const u8 = if (tag == .c_undef) "C undef" else "C include"; + if (!gz.c_import) return gz.astgen.failNode(node, "{s} valid only inside C import block", .{name}); const operand = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, operand_node); _ = try gz.addExtendedPayload(tag, Zir.Inst.UnNode{ .node = gz.nodeIndexToRelative(node), @@ -7698,6 +7701,7 @@ fn cImport( var block_scope = gz.makeSubBlock(scope); block_scope.force_comptime = true; + block_scope.c_import = true; defer block_scope.instructions.deinit(gpa); const block_inst = try gz.addBlock(.c_import, node); @@ -9025,6 +9029,7 @@ const GenZir = struct { base: Scope = Scope{ .tag = base_tag }, force_comptime: bool, in_defer: bool, + c_import: bool = false, /// How decls created in this scope should be named. anon_name_strategy: Zir.Inst.NameStrategy = .anon, /// The containing decl AST node. @@ -9070,6 +9075,7 @@ const GenZir = struct { return .{ .force_comptime = gz.force_comptime, .in_defer = gz.in_defer, + .c_import = gz.c_import, .decl_node_index = gz.decl_node_index, .decl_line = gz.decl_line, .parent = scope, diff --git a/src/Compilation.zig b/src/Compilation.zig index c1dfe91dc2..55cc9d0867 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2644,7 +2644,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { const dep_basename = std.fs.path.basename(out_dep_path); try man.addDepFilePost(zig_cache_tmp_dir, dep_basename); - try comp.stage1_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename); + if (build_options.is_stage1) try comp.stage1_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename); const digest = man.final(); const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); diff --git a/src/Module.zig b/src/Module.zig index 500c34bcb0..6a183db21f 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1325,6 +1325,8 @@ pub const Scope = struct { /// when null, it is determined by build mode, changed by @setRuntimeSafety want_safety: ?bool = null, + c_import_buf: ?*std.ArrayList(u8) = null, + const Param = struct { /// `noreturn` means `anytype`. ty: Type, @@ -1377,6 +1379,7 @@ pub const Scope = struct { .runtime_loop = parent.runtime_loop, .runtime_index = parent.runtime_index, .want_safety = parent.want_safety, + .c_import_buf = parent.c_import_buf, }; } diff --git a/src/Sema.zig b/src/Sema.zig index ff32ce49ca..ac11cf4dcc 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2138,10 +2138,72 @@ fn zirCImport(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) Com const tracy = trace(@src()); defer tracy.end(); - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); + const pl_node = sema.code.instructions.items(.data)[inst].pl_node; + const src = pl_node.src(); + const extra = sema.code.extraData(Zir.Inst.Block, pl_node.payload_index); + const body = sema.code.extra[extra.end..][0..extra.data.body_len]; - return sema.mod.fail(&parent_block.base, src, "TODO: implement Sema.zirCImport", .{}); + // we check this here to avoid undefined symbols + if (!@import("build_options").have_llvm) + return sema.mod.fail(&parent_block.base, src, "cannot do C import on Zig compiler not built with LLVM-extension", .{}); + + var c_import_buf = std.ArrayList(u8).init(sema.gpa); + defer c_import_buf.deinit(); + + var child_block: Scope.Block = .{ + .parent = parent_block, + .sema = sema, + .src_decl = parent_block.src_decl, + .instructions = .{}, + .inlining = parent_block.inlining, + .is_comptime = parent_block.is_comptime, + .c_import_buf = &c_import_buf, + }; + defer child_block.instructions.deinit(sema.gpa); + + _ = try sema.analyzeBody(&child_block, body); + + const c_import_res = sema.mod.comp.cImport(c_import_buf.items) catch |err| + return sema.mod.fail(&child_block.base, src, "C import failed: {s}", .{@errorName(err)}); + + if (c_import_res.errors.len != 0) { + const msg = try sema.mod.errMsg(&child_block.base, src, "C import failed", .{}); + errdefer msg.destroy(sema.gpa); + + if (!sema.mod.comp.bin_file.options.link_libc) + try sema.mod.errNote(&child_block.base, src, msg, "libc headers not available; compilation does not link against libc", .{}); + + for (c_import_res.errors) |_| { + // TODO integrate with LazySrcLoc + // try sema.mod.errNoteNonLazy(.{}, msg, "{s}", .{clang_err.msg_ptr[0..clang_err.msg_len]}); + // if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)", + // clang_err.line + 1, + // clang_err.column + 1, + } + @import("clang.zig").Stage2ErrorMsg.delete(c_import_res.errors.ptr, c_import_res.errors.len); + return sema.mod.failWithOwnedErrorMsg(&child_block.base, msg); + } + const c_import_pkg = @import("Package.zig").createWithDir( + sema.gpa, + sema.mod.comp.local_cache_directory, + null, + std.fs.path.basename(c_import_res.out_zig_path), + ) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => unreachable, // we pass null for root_src_dir_path + }; + const std_pkg = sema.mod.main_pkg.table.get("std").?; + const builtin_pkg = sema.mod.main_pkg.table.get("builtin").?; + try c_import_pkg.add(sema.gpa, "builtin", builtin_pkg); + try c_import_pkg.add(sema.gpa, "std", std_pkg); + + const result = sema.mod.importPkg(c_import_pkg) catch |err| + return sema.mod.fail(&child_block.base, src, "C import failed: {s}", .{@errorName(err)}); + + try sema.mod.semaFile(result.file); + const file_root_decl = result.file.root_decl.?; + try sema.mod.declareDeclDependency(sema.owner_decl, file_root_decl); + return sema.addType(file_root_decl.ty); } fn zirSuspendBlock(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8226,7 +8288,10 @@ fn zirCUndef( ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; const src: LazySrcLoc = .{ .node_offset = extra.node }; - return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirCUndef", .{}); + + const name = try sema.resolveConstString(block, src, extra.operand); + try block.c_import_buf.?.writer().print("#undefine {s}\n", .{name}); + return Air.Inst.Ref.void_value; } fn zirCInclude( @@ -8236,7 +8301,10 @@ fn zirCInclude( ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; const src: LazySrcLoc = .{ .node_offset = extra.node }; - return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirCInclude", .{}); + + const name = try sema.resolveConstString(block, src, extra.operand); + try block.c_import_buf.?.writer().print("#include <{s}>\n", .{name}); + return Air.Inst.Ref.void_value; } fn zirCDefine( @@ -8246,7 +8314,15 @@ fn zirCDefine( ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data; const src: LazySrcLoc = .{ .node_offset = extra.node }; - return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirCDefine", .{}); + + const name = try sema.resolveConstString(block, src, extra.lhs); + if (sema.typeOf(extra.rhs).zigTypeTag() != .Void) { + const value = try sema.resolveConstString(block, src, extra.rhs); + try block.c_import_buf.?.writer().print("#define {s} {s}\n", .{ name, value }); + } else { + try block.c_import_buf.?.writer().print("#define {s}\n", .{name}); + } + return Air.Inst.Ref.void_value; } fn zirWasmMemorySize( From d64d5cfc0a72a9989ed58548dd806a663f847ef5 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 19 Sep 2021 19:38:17 +0300 Subject: [PATCH 081/160] stage2: implement typeInfo for more types --- src/Sema.zig | 168 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 154 insertions(+), 14 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index ac11cf4dcc..2d50462f91 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6518,19 +6518,80 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr const target = sema.mod.getTarget(); switch (ty.zigTypeTag()) { + .Type => return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Type)), + .val = Value.initTag(.unreachable_value), + }), + ), + .Void => return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Void)), + .val = Value.initTag(.unreachable_value), + }), + ), + .Bool => return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Bool)), + .val = Value.initTag(.unreachable_value), + }), + ), + .NoReturn => return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.NoReturn)), + .val = Value.initTag(.unreachable_value), + }), + ), + .ComptimeFloat => return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.ComptimeFloat)), + .val = Value.initTag(.unreachable_value), + }), + ), + .ComptimeInt => return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.ComptimeInt)), + .val = Value.initTag(.unreachable_value), + }), + ), + .Undefined => return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Undefined)), + .val = Value.initTag(.unreachable_value), + }), + ), + .Null => return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Null)), + .val = Value.initTag(.unreachable_value), + }), + ), + .EnumLiteral => return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.EnumLiteral)), + .val = Value.initTag(.unreachable_value), + }), + ), .Fn => { + const info = ty.fnInfo(); const field_values = try sema.arena.alloc(Value, 6); // calling_convention: CallingConvention, - field_values[0] = try Value.Tag.enum_field_index.create( - sema.arena, - @enumToInt(ty.fnCallingConvention()), - ); + field_values[0] = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.cc)); // alignment: comptime_int, field_values[1] = try Value.Tag.int_u64.create(sema.arena, ty.abiAlignment(target)); // is_generic: bool, - field_values[2] = Value.initTag(.bool_false); // TODO + field_values[2] = if (info.is_generic) Value.initTag(.bool_true) else Value.initTag(.bool_false); // is_var_args: bool, - field_values[3] = Value.initTag(.bool_false); // TODO + field_values[3] = if (info.is_var_args) Value.initTag(.bool_true) else Value.initTag(.bool_false); // return_type: ?type, field_values[4] = try Value.Tag.ty.create(sema.arena, ty.fnReturnType()); // args: []const FnArg, @@ -6539,10 +6600,7 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ - .tag = try Value.Tag.enum_field_index.create( - sema.arena, - @enumToInt(@typeInfo(std.builtin.TypeInfo).Union.tag_type.?.Fn), - ), + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Fn)), .val = try Value.Tag.@"struct".create(sema.arena, field_values), }), ); @@ -6561,10 +6619,92 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ - .tag = try Value.Tag.enum_field_index.create( - sema.arena, - @enumToInt(@typeInfo(std.builtin.TypeInfo).Union.tag_type.?.Int), - ), + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Int)), + .val = try Value.Tag.@"struct".create(sema.arena, field_values), + }), + ); + }, + .Float => { + const field_values = try sema.arena.alloc(Value, 1); + // bits: comptime_int, + field_values[0] = try Value.Tag.int_u64.create(sema.arena, ty.bitSize(target)); + + return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Float)), + .val = try Value.Tag.@"struct".create(sema.arena, field_values), + }), + ); + }, + .Pointer => { + const info = ty.ptrInfo().data; + const field_values = try sema.arena.alloc(Value, 7); + // size: Size, + field_values[0] = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.size)); + // is_const: bool, + field_values[1] = if (!info.mutable) Value.initTag(.bool_true) else Value.initTag(.bool_false); + // is_volatile: bool, + field_values[2] = if (info.@"volatile") Value.initTag(.bool_true) else Value.initTag(.bool_false); + // alignment: comptime_int, + field_values[3] = try Value.Tag.int_u64.create(sema.arena, info.@"align"); + // child: type, + field_values[4] = try Value.Tag.ty.create(sema.arena, info.pointee_type); + // is_allowzero: bool, + field_values[5] = if (info.@"allowzero") Value.initTag(.bool_true) else Value.initTag(.bool_false); + // sentinel: anytype, + field_values[6] = if (info.sentinel) |some| try Value.Tag.opt_payload.create(sema.arena, some) else Value.initTag(.null_value); + + return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Pointer)), + .val = try Value.Tag.@"struct".create(sema.arena, field_values), + }), + ); + }, + .Array => { + const info = ty.arrayInfo(); + const field_values = try sema.arena.alloc(Value, 3); + // len: comptime_int, + field_values[0] = try Value.Tag.int_u64.create(sema.arena, info.len); + // child: type, + field_values[1] = try Value.Tag.ty.create(sema.arena, info.elem_type); + // sentinel: anytype, + field_values[2] = if (info.sentinel) |some| try Value.Tag.opt_payload.create(sema.arena, some) else Value.initTag(.null_value); + + return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Array)), + .val = try Value.Tag.@"struct".create(sema.arena, field_values), + }), + ); + }, + .Optional => { + const field_values = try sema.arena.alloc(Value, 1); + // child: type, + field_values[0] = try Value.Tag.ty.create(sema.arena, try ty.optionalChildAlloc(sema.arena)); + + return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Optional)), + .val = try Value.Tag.@"struct".create(sema.arena, field_values), + }), + ); + }, + .ErrorUnion => { + const field_values = try sema.arena.alloc(Value, 2); + // error_set: type, + field_values[0] = try Value.Tag.ty.create(sema.arena, ty.errorUnionSet()); + // payload: type, + field_values[1] = try Value.Tag.ty.create(sema.arena, ty.errorUnionPayload()); + + return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.ErrorUnion)), .val = try Value.Tag.@"struct".create(sema.arena, field_values), }), ); From 55e7c099caa7cf8dc253dac21765bb994e06b741 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 20 Sep 2021 21:05:42 +0300 Subject: [PATCH 082/160] stage2: various fixes to cImport, sizeOf and types to get tests passing --- src/AstGen.zig | 5 +- src/Compilation.zig | 2 +- src/Sema.zig | 41 +++- src/print_zir.zig | 18 +- src/type.zig | 2 +- test/behavior.zig | 6 +- test/behavior/sizeof_and_typeof.zig | 214 ------------------- test/behavior/sizeof_and_typeof_stage1.zig | 218 ++++++++++++++++++++ test/behavior/translate_c_macros.zig | 22 -- test/behavior/translate_c_macros_stage1.zig | 26 +++ 10 files changed, 304 insertions(+), 250 deletions(-) create mode 100644 test/behavior/sizeof_and_typeof_stage1.zig create mode 100644 test/behavior/translate_c_macros_stage1.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 32b62fc3f5..176203d37f 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7082,7 +7082,7 @@ fn builtinCall( .bit_cast => return bitCast( gz, scope, rl, node, params[0], params[1]), .TypeOf => return typeOf( gz, scope, rl, node, params), .union_init => return unionInit(gz, scope, rl, node, params), - .c_import => return cImport( gz, scope, rl, node, params[0]), + .c_import => return cImport( gz, scope, node, params[0]), .@"export" => { const node_tags = tree.nodes.items(.tag); @@ -7692,7 +7692,6 @@ fn shiftOp( fn cImport( gz: *GenZir, scope: *Scope, - rl: ResultLoc, node: Ast.Node.Index, body_node: Ast.Node.Index, ) InnerError!Zir.Inst.Ref { @@ -7712,7 +7711,7 @@ fn cImport( try block_scope.setBlockBody(block_inst); try gz.instructions.append(gpa, block_inst); - return rvalue(gz, rl, .void_value, node); + return indexToRef(block_inst); } fn overflowArithmetic( diff --git a/src/Compilation.zig b/src/Compilation.zig index 55cc9d0867..53e643acb8 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2644,7 +2644,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { const dep_basename = std.fs.path.basename(out_dep_path); try man.addDepFilePost(zig_cache_tmp_dir, dep_basename); - if (build_options.is_stage1) try comp.stage1_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename); + if (build_options.is_stage1 and comp.bin_file.options.use_stage1) try comp.stage1_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename); const digest = man.final(); const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); diff --git a/src/Sema.zig b/src/Sema.zig index 2d50462f91..1bb071f4f5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2183,11 +2183,10 @@ fn zirCImport(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) Com @import("clang.zig").Stage2ErrorMsg.delete(c_import_res.errors.ptr, c_import_res.errors.len); return sema.mod.failWithOwnedErrorMsg(&child_block.base, msg); } - const c_import_pkg = @import("Package.zig").createWithDir( + const c_import_pkg = @import("Package.zig").create( sema.gpa, - sema.mod.comp.local_cache_directory, null, - std.fs.path.basename(c_import_res.out_zig_path), + c_import_res.out_zig_path, ) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, else => unreachable, // we pass null for root_src_dir_path @@ -2200,6 +2199,9 @@ fn zirCImport(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) Com const result = sema.mod.importPkg(c_import_pkg) catch |err| return sema.mod.fail(&child_block.base, src, "C import failed: {s}", .{@errorName(err)}); + sema.mod.astGenFile(result.file) catch |err| + return sema.mod.fail(&child_block.base, src, "C import failed: {s}", .{@errorName(err)}); + try sema.mod.semaFile(result.file); const file_root_decl = result.file.root_decl.?; try sema.mod.declareDeclDependency(sema.owner_decl, file_root_decl); @@ -6467,10 +6469,41 @@ fn runtimeBoolCmp( fn zirSizeOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_ty = try sema.resolveType(block, operand_src, inst_data.operand); const target = sema.mod.getTarget(); - const abi_size = operand_ty.abiSize(target); + const abi_size = switch (operand_ty.zigTypeTag()) { + .Fn => unreachable, + .NoReturn, + .Undefined, + .Null, + .BoundFn, + .Opaque, + => return sema.mod.fail(&block.base, src, "no size available for type '{}'", .{operand_ty}), + .Type, + .EnumLiteral, + .ComptimeFloat, + .ComptimeInt, + .Void, + => 0, + + .Bool, + .Int, + .Float, + .Pointer, + .Array, + .Struct, + .Optional, + .ErrorUnion, + .ErrorSet, + .Enum, + .Union, + .Vector, + .Frame, + .AnyFrame, + => operand_ty.abiSize(target), + }; return sema.addIntUnsigned(Type.initTag(.comptime_int), abi_size); } diff --git a/src/print_zir.zig b/src/print_zir.zig index 94fa0307bd..35b3da4479 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -407,15 +407,27 @@ const Writer = struct { .mul_with_saturation, .shl_with_saturation, => try self.writeSaturatingArithmetic(stream, extended), + .struct_decl => try self.writeStructDecl(stream, extended), .union_decl => try self.writeUnionDecl(stream, extended), .enum_decl => try self.writeEnumDecl(stream, extended), + .c_undef, .c_include => { + const inst_data = self.code.extraData(Zir.Inst.UnNode, extended.operand).data; + try self.writeInstRef(stream, inst_data.operand); + try stream.writeAll(") "); + }, + + .c_define => { + const inst_data = self.code.extraData(Zir.Inst.BinNode, extended.operand).data; + try self.writeInstRef(stream, inst_data.lhs); + try stream.writeAll(", "); + try self.writeInstRef(stream, inst_data.rhs); + try stream.writeByte(')'); + }, + .alloc, .builtin_extern, - .c_undef, - .c_include, - .c_define, .wasm_memory_size, .wasm_memory_grow, => try stream.writeAll("TODO))"), diff --git a/src/type.zig b/src/type.zig index db193639a7..5d184ed2fc 100644 --- a/src/type.zig +++ b/src/type.zig @@ -602,8 +602,8 @@ pub const Type = extern union { } return false; }, + .Float => return a.tag() == b.tag(), .Opaque, - .Float, .BoundFn, .Frame, => std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ a, b }), diff --git a/test/behavior.zig b/test/behavior.zig index ebf3fff83f..ee3a789c39 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -11,6 +11,8 @@ test { _ = @import("behavior/array.zig"); _ = @import("behavior/usingnamespace.zig"); _ = @import("behavior/atomics.zig"); + _ = @import("behavior/sizeof_and_typeof.zig"); + _ = @import("behavior/translate_c_macros.zig"); if (builtin.zig_is_stage2) { // When all comptime_memory.zig tests pass, #9646 can be closed. @@ -128,7 +130,7 @@ test { _ = @import("behavior/saturating_arithmetic.zig"); _ = @import("behavior/shuffle.zig"); _ = @import("behavior/select.zig"); - _ = @import("behavior/sizeof_and_typeof.zig"); + _ = @import("behavior/sizeof_and_typeof_stage1.zig"); _ = @import("behavior/slice.zig"); _ = @import("behavior/slice_sentinel_comptime.zig"); _ = @import("behavior/struct.zig"); @@ -157,6 +159,6 @@ test { _ = @import("behavior/while.zig"); _ = @import("behavior/widening.zig"); _ = @import("behavior/src.zig"); - _ = @import("behavior/translate_c_macros.zig"); + _ = @import("behavior/translate_c_macros_stage1.zig"); } } diff --git a/test/behavior/sizeof_and_typeof.zig b/test/behavior/sizeof_and_typeof.zig index 60951a4c5a..09bd305c68 100644 --- a/test/behavior/sizeof_and_typeof.zig +++ b/test/behavior/sizeof_and_typeof.zig @@ -10,118 +10,6 @@ test "@sizeOf and @TypeOf" { const x: u16 = 13; const z: @TypeOf(x) = 19; -const A = struct { - a: u8, - b: u32, - c: u8, - d: u3, - e: u5, - f: u16, - g: u16, - h: u9, - i: u7, -}; - -const P = packed struct { - a: u8, - b: u32, - c: u8, - d: u3, - e: u5, - f: u16, - g: u16, - h: u9, - i: u7, -}; - -test "@offsetOf" { - // Packed structs have fixed memory layout - try expect(@offsetOf(P, "a") == 0); - try expect(@offsetOf(P, "b") == 1); - try expect(@offsetOf(P, "c") == 5); - try expect(@offsetOf(P, "d") == 6); - try expect(@offsetOf(P, "e") == 6); - try expect(@offsetOf(P, "f") == 7); - try expect(@offsetOf(P, "g") == 9); - try expect(@offsetOf(P, "h") == 11); - try expect(@offsetOf(P, "i") == 12); - - // Normal struct fields can be moved/padded - var a: A = undefined; - try expect(@ptrToInt(&a.a) - @ptrToInt(&a) == @offsetOf(A, "a")); - try expect(@ptrToInt(&a.b) - @ptrToInt(&a) == @offsetOf(A, "b")); - try expect(@ptrToInt(&a.c) - @ptrToInt(&a) == @offsetOf(A, "c")); - try expect(@ptrToInt(&a.d) - @ptrToInt(&a) == @offsetOf(A, "d")); - try expect(@ptrToInt(&a.e) - @ptrToInt(&a) == @offsetOf(A, "e")); - try expect(@ptrToInt(&a.f) - @ptrToInt(&a) == @offsetOf(A, "f")); - try expect(@ptrToInt(&a.g) - @ptrToInt(&a) == @offsetOf(A, "g")); - try expect(@ptrToInt(&a.h) - @ptrToInt(&a) == @offsetOf(A, "h")); - try expect(@ptrToInt(&a.i) - @ptrToInt(&a) == @offsetOf(A, "i")); -} - -test "@offsetOf packed struct, array length not power of 2 or multiple of native pointer width in bytes" { - const p3a_len = 3; - const P3 = packed struct { - a: [p3a_len]u8, - b: usize, - }; - try std.testing.expectEqual(0, @offsetOf(P3, "a")); - try std.testing.expectEqual(p3a_len, @offsetOf(P3, "b")); - - const p5a_len = 5; - const P5 = packed struct { - a: [p5a_len]u8, - b: usize, - }; - try std.testing.expectEqual(0, @offsetOf(P5, "a")); - try std.testing.expectEqual(p5a_len, @offsetOf(P5, "b")); - - const p6a_len = 6; - const P6 = packed struct { - a: [p6a_len]u8, - b: usize, - }; - try std.testing.expectEqual(0, @offsetOf(P6, "a")); - try std.testing.expectEqual(p6a_len, @offsetOf(P6, "b")); - - const p7a_len = 7; - const P7 = packed struct { - a: [p7a_len]u8, - b: usize, - }; - try std.testing.expectEqual(0, @offsetOf(P7, "a")); - try std.testing.expectEqual(p7a_len, @offsetOf(P7, "b")); - - const p9a_len = 9; - const P9 = packed struct { - a: [p9a_len]u8, - b: usize, - }; - try std.testing.expectEqual(0, @offsetOf(P9, "a")); - try std.testing.expectEqual(p9a_len, @offsetOf(P9, "b")); - - // 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 25 etc. are further cases -} - -test "@bitOffsetOf" { - // Packed structs have fixed memory layout - try expect(@bitOffsetOf(P, "a") == 0); - try expect(@bitOffsetOf(P, "b") == 8); - try expect(@bitOffsetOf(P, "c") == 40); - try expect(@bitOffsetOf(P, "d") == 48); - try expect(@bitOffsetOf(P, "e") == 51); - try expect(@bitOffsetOf(P, "f") == 56); - try expect(@bitOffsetOf(P, "g") == 72); - - try expect(@offsetOf(A, "a") * 8 == @bitOffsetOf(A, "a")); - try expect(@offsetOf(A, "b") * 8 == @bitOffsetOf(A, "b")); - try expect(@offsetOf(A, "c") * 8 == @bitOffsetOf(A, "c")); - try expect(@offsetOf(A, "d") * 8 == @bitOffsetOf(A, "d")); - try expect(@offsetOf(A, "e") * 8 == @bitOffsetOf(A, "e")); - try expect(@offsetOf(A, "f") * 8 == @bitOffsetOf(A, "f")); - try expect(@offsetOf(A, "g") * 8 == @bitOffsetOf(A, "g")); -} - test "@sizeOf on compile-time types" { try expect(@sizeOf(comptime_int) == 0); try expect(@sizeOf(comptime_float) == 0); @@ -129,34 +17,6 @@ test "@sizeOf on compile-time types" { try expect(@sizeOf(@TypeOf(type)) == 0); } -test "@sizeOf(T) == 0 doesn't force resolving struct size" { - const S = struct { - const Foo = struct { - y: if (@sizeOf(Foo) == 0) u64 else u32, - }; - const Bar = struct { - x: i32, - y: if (0 == @sizeOf(Bar)) u64 else u32, - }; - }; - - try expect(@sizeOf(S.Foo) == 4); - try expect(@sizeOf(S.Bar) == 8); -} - -test "@TypeOf() has no runtime side effects" { - const S = struct { - fn foo(comptime T: type, ptr: *T) T { - ptr.* += 1; - return ptr.*; - } - }; - var data: i32 = 0; - const T = @TypeOf(S.foo(i32, &data)); - comptime try expect(T == i32); - try expect(data == 0); -} - test "@TypeOf() with multiple arguments" { { var var_1: u32 = undefined; @@ -180,19 +40,6 @@ test "@TypeOf() with multiple arguments" { } } -test "branching logic inside @TypeOf" { - const S = struct { - var data: i32 = 0; - fn foo() anyerror!i32 { - data += 1; - return undefined; - } - }; - const T = @TypeOf(S.foo() catch undefined); - comptime try expect(T == i32); - try expect(S.data == 0); -} - fn fn1(alpha: bool) void { const n: usize = 7; _ = if (alpha) n else @sizeOf(usize); @@ -201,64 +48,3 @@ fn fn1(alpha: bool) void { test "lazy @sizeOf result is checked for definedness" { _ = fn1; } - -test "@bitSizeOf" { - try expect(@bitSizeOf(u2) == 2); - try expect(@bitSizeOf(u8) == @sizeOf(u8) * 8); - try expect(@bitSizeOf(struct { - a: u2, - }) == 8); - try expect(@bitSizeOf(packed struct { - a: u2, - }) == 2); -} - -test "@sizeOf comparison against zero" { - const S0 = struct { - f: *@This(), - }; - const U0 = union { - f: *@This(), - }; - const S1 = struct { - fn H(comptime T: type) type { - return struct { - x: T, - }; - } - f0: H(*@This()), - f1: H(**@This()), - f2: H(***@This()), - }; - const U1 = union { - fn H(comptime T: type) type { - return struct { - x: T, - }; - } - f0: H(*@This()), - f1: H(**@This()), - f2: H(***@This()), - }; - const S = struct { - fn doTheTest(comptime T: type, comptime result: bool) !void { - try expectEqual(result, @sizeOf(T) > 0); - } - }; - // Zero-sized type - try S.doTheTest(u0, false); - try S.doTheTest(*u0, false); - // Non byte-sized type - try S.doTheTest(u1, true); - try S.doTheTest(*u1, true); - // Regular type - try S.doTheTest(u8, true); - try S.doTheTest(*u8, true); - try S.doTheTest(f32, true); - try S.doTheTest(*f32, true); - // Container with ptr pointing to themselves - try S.doTheTest(S0, true); - try S.doTheTest(U0, true); - try S.doTheTest(S1, true); - try S.doTheTest(U1, true); -} diff --git a/test/behavior/sizeof_and_typeof_stage1.zig b/test/behavior/sizeof_and_typeof_stage1.zig new file mode 100644 index 0000000000..974e79015f --- /dev/null +++ b/test/behavior/sizeof_and_typeof_stage1.zig @@ -0,0 +1,218 @@ +const std = @import("std"); +const builtin = std.builtin; +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; + +const A = struct { + a: u8, + b: u32, + c: u8, + d: u3, + e: u5, + f: u16, + g: u16, + h: u9, + i: u7, +}; + +const P = packed struct { + a: u8, + b: u32, + c: u8, + d: u3, + e: u5, + f: u16, + g: u16, + h: u9, + i: u7, +}; + +test "@offsetOf" { + // Packed structs have fixed memory layout + try expect(@offsetOf(P, "a") == 0); + try expect(@offsetOf(P, "b") == 1); + try expect(@offsetOf(P, "c") == 5); + try expect(@offsetOf(P, "d") == 6); + try expect(@offsetOf(P, "e") == 6); + try expect(@offsetOf(P, "f") == 7); + try expect(@offsetOf(P, "g") == 9); + try expect(@offsetOf(P, "h") == 11); + try expect(@offsetOf(P, "i") == 12); + + // Normal struct fields can be moved/padded + var a: A = undefined; + try expect(@ptrToInt(&a.a) - @ptrToInt(&a) == @offsetOf(A, "a")); + try expect(@ptrToInt(&a.b) - @ptrToInt(&a) == @offsetOf(A, "b")); + try expect(@ptrToInt(&a.c) - @ptrToInt(&a) == @offsetOf(A, "c")); + try expect(@ptrToInt(&a.d) - @ptrToInt(&a) == @offsetOf(A, "d")); + try expect(@ptrToInt(&a.e) - @ptrToInt(&a) == @offsetOf(A, "e")); + try expect(@ptrToInt(&a.f) - @ptrToInt(&a) == @offsetOf(A, "f")); + try expect(@ptrToInt(&a.g) - @ptrToInt(&a) == @offsetOf(A, "g")); + try expect(@ptrToInt(&a.h) - @ptrToInt(&a) == @offsetOf(A, "h")); + try expect(@ptrToInt(&a.i) - @ptrToInt(&a) == @offsetOf(A, "i")); +} + +test "@offsetOf packed struct, array length not power of 2 or multiple of native pointer width in bytes" { + const p3a_len = 3; + const P3 = packed struct { + a: [p3a_len]u8, + b: usize, + }; + try std.testing.expectEqual(0, @offsetOf(P3, "a")); + try std.testing.expectEqual(p3a_len, @offsetOf(P3, "b")); + + const p5a_len = 5; + const P5 = packed struct { + a: [p5a_len]u8, + b: usize, + }; + try std.testing.expectEqual(0, @offsetOf(P5, "a")); + try std.testing.expectEqual(p5a_len, @offsetOf(P5, "b")); + + const p6a_len = 6; + const P6 = packed struct { + a: [p6a_len]u8, + b: usize, + }; + try std.testing.expectEqual(0, @offsetOf(P6, "a")); + try std.testing.expectEqual(p6a_len, @offsetOf(P6, "b")); + + const p7a_len = 7; + const P7 = packed struct { + a: [p7a_len]u8, + b: usize, + }; + try std.testing.expectEqual(0, @offsetOf(P7, "a")); + try std.testing.expectEqual(p7a_len, @offsetOf(P7, "b")); + + const p9a_len = 9; + const P9 = packed struct { + a: [p9a_len]u8, + b: usize, + }; + try std.testing.expectEqual(0, @offsetOf(P9, "a")); + try std.testing.expectEqual(p9a_len, @offsetOf(P9, "b")); + + // 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 25 etc. are further cases +} + +test "@bitOffsetOf" { + // Packed structs have fixed memory layout + try expect(@bitOffsetOf(P, "a") == 0); + try expect(@bitOffsetOf(P, "b") == 8); + try expect(@bitOffsetOf(P, "c") == 40); + try expect(@bitOffsetOf(P, "d") == 48); + try expect(@bitOffsetOf(P, "e") == 51); + try expect(@bitOffsetOf(P, "f") == 56); + try expect(@bitOffsetOf(P, "g") == 72); + + try expect(@offsetOf(A, "a") * 8 == @bitOffsetOf(A, "a")); + try expect(@offsetOf(A, "b") * 8 == @bitOffsetOf(A, "b")); + try expect(@offsetOf(A, "c") * 8 == @bitOffsetOf(A, "c")); + try expect(@offsetOf(A, "d") * 8 == @bitOffsetOf(A, "d")); + try expect(@offsetOf(A, "e") * 8 == @bitOffsetOf(A, "e")); + try expect(@offsetOf(A, "f") * 8 == @bitOffsetOf(A, "f")); + try expect(@offsetOf(A, "g") * 8 == @bitOffsetOf(A, "g")); +} + +test "@sizeOf(T) == 0 doesn't force resolving struct size" { + const S = struct { + const Foo = struct { + y: if (@sizeOf(Foo) == 0) u64 else u32, + }; + const Bar = struct { + x: i32, + y: if (0 == @sizeOf(Bar)) u64 else u32, + }; + }; + + try expect(@sizeOf(S.Foo) == 4); + try expect(@sizeOf(S.Bar) == 8); +} + +test "@TypeOf() has no runtime side effects" { + const S = struct { + fn foo(comptime T: type, ptr: *T) T { + ptr.* += 1; + return ptr.*; + } + }; + var data: i32 = 0; + const T = @TypeOf(S.foo(i32, &data)); + comptime try expect(T == i32); + try expect(data == 0); +} + +test "branching logic inside @TypeOf" { + const S = struct { + var data: i32 = 0; + fn foo() anyerror!i32 { + data += 1; + return undefined; + } + }; + const T = @TypeOf(S.foo() catch undefined); + comptime try expect(T == i32); + try expect(S.data == 0); +} + +test "@bitSizeOf" { + try expect(@bitSizeOf(u2) == 2); + try expect(@bitSizeOf(u8) == @sizeOf(u8) * 8); + try expect(@bitSizeOf(struct { + a: u2, + }) == 8); + try expect(@bitSizeOf(packed struct { + a: u2, + }) == 2); +} + +test "@sizeOf comparison against zero" { + const S0 = struct { + f: *@This(), + }; + const U0 = union { + f: *@This(), + }; + const S1 = struct { + fn H(comptime T: type) type { + return struct { + x: T, + }; + } + f0: H(*@This()), + f1: H(**@This()), + f2: H(***@This()), + }; + const U1 = union { + fn H(comptime T: type) type { + return struct { + x: T, + }; + } + f0: H(*@This()), + f1: H(**@This()), + f2: H(***@This()), + }; + const S = struct { + fn doTheTest(comptime T: type, comptime result: bool) !void { + try expectEqual(result, @sizeOf(T) > 0); + } + }; + // Zero-sized type + try S.doTheTest(u0, false); + try S.doTheTest(*u0, false); + // Non byte-sized type + try S.doTheTest(u1, true); + try S.doTheTest(*u1, true); + // Regular type + try S.doTheTest(u8, true); + try S.doTheTest(*u8, true); + try S.doTheTest(f32, true); + try S.doTheTest(*f32, true); + // Container with ptr pointing to themselves + try S.doTheTest(S0, true); + try S.doTheTest(U0, true); + try S.doTheTest(S1, true); + try S.doTheTest(U1, true); +} diff --git a/test/behavior/translate_c_macros.zig b/test/behavior/translate_c_macros.zig index 0daf4cec90..0baaf24283 100644 --- a/test/behavior/translate_c_macros.zig +++ b/test/behavior/translate_c_macros.zig @@ -3,28 +3,6 @@ const expectEqual = @import("std").testing.expectEqual; const h = @cImport(@cInclude("behavior/translate_c_macros.h")); -test "initializer list expression" { - try expectEqual(h.Color{ - .r = 200, - .g = 200, - .b = 200, - .a = 255, - }, h.LIGHTGRAY); -} - -test "sizeof in macros" { - try expectEqual(@as(c_int, @sizeOf(u32)), h.MY_SIZEOF(u32)); - try expectEqual(@as(c_int, @sizeOf(u32)), h.MY_SIZEOF2(u32)); -} - -test "reference to a struct type" { - try expectEqual(@sizeOf(h.struct_Foo), h.SIZE_OF_FOO); -} - -test "cast negative integer to pointer" { - try expectEqual(@intToPtr(?*c_void, @bitCast(usize, @as(isize, -1))), h.MAP_FAILED); -} - test "casting to void with a macro" { h.IGNORE_ME_1(42); h.IGNORE_ME_2(42); diff --git a/test/behavior/translate_c_macros_stage1.zig b/test/behavior/translate_c_macros_stage1.zig new file mode 100644 index 0000000000..8de06ae8ea --- /dev/null +++ b/test/behavior/translate_c_macros_stage1.zig @@ -0,0 +1,26 @@ +const expect = @import("std").testing.expect; +const expectEqual = @import("std").testing.expectEqual; + +const h = @cImport(@cInclude("behavior/translate_c_macros.h")); + +test "initializer list expression" { + try expectEqual(h.Color{ + .r = 200, + .g = 200, + .b = 200, + .a = 255, + }, h.LIGHTGRAY); +} + +test "sizeof in macros" { + try expectEqual(@as(c_int, @sizeOf(u32)), h.MY_SIZEOF(u32)); + try expectEqual(@as(c_int, @sizeOf(u32)), h.MY_SIZEOF2(u32)); +} + +test "reference to a struct type" { + try expectEqual(@sizeOf(h.struct_Foo), h.SIZE_OF_FOO); +} + +test "cast negative integer to pointer" { + try expectEqual(@intToPtr(?*c_void, @bitCast(usize, @as(isize, -1))), h.MAP_FAILED); +} From 0c74ce1156d6a967cd089cd4657575a3e22bb782 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 20 Sep 2021 20:56:30 -0700 Subject: [PATCH 083/160] Sema: fix double-free of `@cImport` error message --- src/Sema.zig | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 1bb071f4f5..786db21b4a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -83,6 +83,7 @@ const Decl = Module.Decl; const LazySrcLoc = Module.LazySrcLoc; const RangeSet = @import("RangeSet.zig"); const target_util = @import("target.zig"); +const Package = @import("Package.zig"); pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref); @@ -2167,23 +2168,26 @@ fn zirCImport(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) Com return sema.mod.fail(&child_block.base, src, "C import failed: {s}", .{@errorName(err)}); if (c_import_res.errors.len != 0) { - const msg = try sema.mod.errMsg(&child_block.base, src, "C import failed", .{}); - errdefer msg.destroy(sema.gpa); + const msg = msg: { + const msg = try sema.mod.errMsg(&child_block.base, src, "C import failed", .{}); + errdefer msg.destroy(sema.gpa); - if (!sema.mod.comp.bin_file.options.link_libc) - try sema.mod.errNote(&child_block.base, src, msg, "libc headers not available; compilation does not link against libc", .{}); + if (!sema.mod.comp.bin_file.options.link_libc) + try sema.mod.errNote(&child_block.base, src, msg, "libc headers not available; compilation does not link against libc", .{}); - for (c_import_res.errors) |_| { - // TODO integrate with LazySrcLoc - // try sema.mod.errNoteNonLazy(.{}, msg, "{s}", .{clang_err.msg_ptr[0..clang_err.msg_len]}); - // if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)", - // clang_err.line + 1, - // clang_err.column + 1, - } - @import("clang.zig").Stage2ErrorMsg.delete(c_import_res.errors.ptr, c_import_res.errors.len); + for (c_import_res.errors) |_| { + // TODO integrate with LazySrcLoc + // try sema.mod.errNoteNonLazy(.{}, msg, "{s}", .{clang_err.msg_ptr[0..clang_err.msg_len]}); + // if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)", + // clang_err.line + 1, + // clang_err.column + 1, + } + @import("clang.zig").Stage2ErrorMsg.delete(c_import_res.errors.ptr, c_import_res.errors.len); + break :msg msg; + }; return sema.mod.failWithOwnedErrorMsg(&child_block.base, msg); } - const c_import_pkg = @import("Package.zig").create( + const c_import_pkg = Package.create( sema.gpa, null, c_import_res.out_zig_path, From d722f0cc627c34f2c18681202a512d9aaa58fb18 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 21 Sep 2021 11:05:22 +0200 Subject: [PATCH 084/160] macho: do not write temp and noname symbols to symtab Remove currently obsolete AtomParser from Object. --- src/link/MachO.zig | 19 +++-- src/link/MachO/Atom.zig | 9 +-- src/link/MachO/Object.zig | 155 +------------------------------------- 3 files changed, 14 insertions(+), 169 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 324870a705..e803dc277a 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1869,7 +1869,7 @@ fn writeAtoms(self: *MachO) !void { pub fn createGotAtom(self: *MachO, key: GotIndirectionKey) !*Atom { const local_sym_index = @intCast(u32, self.locals.items.len); try self.locals.append(self.base.allocator, .{ - .n_strx = try self.makeString("l_zld_got_entry"), + .n_strx = 0, .n_type = macho.N_SECT, .n_sect = 0, .n_desc = 0, @@ -1907,7 +1907,7 @@ fn createDyldPrivateAtom(self: *MachO) !void { const local_sym_index = @intCast(u32, self.locals.items.len); const sym = try self.locals.addOne(self.base.allocator); sym.* = .{ - .n_strx = try self.makeString("l_zld_dyld_private"), + .n_strx = 0, .n_type = macho.N_SECT, .n_sect = 0, .n_desc = 0, @@ -1941,7 +1941,7 @@ fn createStubHelperPreambleAtom(self: *MachO) !void { const local_sym_index = @intCast(u32, self.locals.items.len); const sym = try self.locals.addOne(self.base.allocator); sym.* = .{ - .n_strx = try self.makeString("l_zld_stub_preamble"), + .n_strx = 0, .n_type = macho.N_SECT, .n_sect = 0, .n_desc = 0, @@ -2083,7 +2083,7 @@ pub fn createStubHelperAtom(self: *MachO) !*Atom { }; const local_sym_index = @intCast(u32, self.locals.items.len); try self.locals.append(self.base.allocator, .{ - .n_strx = try self.makeString("l_zld_stub_in_stub_helper"), + .n_strx = 0, .n_type = macho.N_SECT, .n_sect = 0, .n_desc = 0, @@ -2138,7 +2138,7 @@ pub fn createStubHelperAtom(self: *MachO) !*Atom { pub fn createLazyPointerAtom(self: *MachO, stub_sym_index: u32, lazy_binding_sym_index: u32) !*Atom { const local_sym_index = @intCast(u32, self.locals.items.len); try self.locals.append(self.base.allocator, .{ - .n_strx = try self.makeString("l_zld_lazy_ptr"), + .n_strx = 0, .n_type = macho.N_SECT, .n_sect = 0, .n_desc = 0, @@ -2179,7 +2179,7 @@ pub fn createStubAtom(self: *MachO, laptr_sym_index: u32) !*Atom { }; const local_sym_index = @intCast(u32, self.locals.items.len); try self.locals.append(self.base.allocator, .{ - .n_strx = try self.makeString("l_zld_stub"), + .n_strx = 0, .n_type = macho.N_SECT, .n_sect = 0, .n_desc = 0, @@ -4741,7 +4741,12 @@ fn writeSymbolTable(self: *MachO) !void { var locals = std.ArrayList(macho.nlist_64).init(self.base.allocator); defer locals.deinit(); - try locals.appendSlice(self.locals.items); + + for (self.locals.items) |sym| { + if (sym.n_strx == 0) continue; + if (symbolIsTemp(sym, self.getString(sym.n_strx))) continue; + try locals.append(sym); + } if (self.has_stabs) { for (self.objects.items) |object| { diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index 6dbe853451..bb6730fe0f 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -663,15 +663,8 @@ fn initRelocFromObject(rel: macho.relocation_info, context: RelocContext) !Reloc const sect = seg.sections.items[sect_id]; const match = (try context.macho_file.getMatchingSection(sect)) orelse unreachable; const local_sym_index = @intCast(u32, context.macho_file.locals.items.len); - const sym_name = try std.fmt.allocPrint(context.allocator, "l_{s}_{s}_{s}", .{ - context.object.name, - commands.segmentName(sect), - commands.sectionName(sect), - }); - defer context.allocator.free(sym_name); - try context.macho_file.locals.append(context.allocator, .{ - .n_strx = try context.macho_file.makeString(sym_name), + .n_strx = 0, .n_type = macho.N_SECT, .n_sect = @intCast(u8, context.macho_file.section_ordinals.getIndex(match).? + 1), .n_desc = 0, diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index d71c549d77..cfa994ecfb 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -375,152 +375,6 @@ fn filterDice(dices: []macho.data_in_code_entry, start_addr: u64, end_addr: u64) return dices[start..end]; } -const Context = struct { - allocator: *Allocator, - object: *Object, - macho_file: *MachO, - match: MachO.MatchingSection, -}; - -const AtomParser = struct { - section: macho.section_64, - code: []u8, - relocs: []macho.relocation_info, - nlists: []NlistWithIndex, - index: u32 = 0, - - fn peek(self: AtomParser) ?NlistWithIndex { - return if (self.index + 1 < self.nlists.len) self.nlists[self.index + 1] else null; - } - - fn lessThanBySeniority(context: Context, lhs: NlistWithIndex, rhs: NlistWithIndex) bool { - if (!MachO.symbolIsExt(rhs.nlist)) { - return MachO.symbolIsTemp(lhs.nlist, context.object.getString(lhs.nlist.n_strx)); - } else if (MachO.symbolIsPext(rhs.nlist) or MachO.symbolIsWeakDef(rhs.nlist)) { - return !MachO.symbolIsExt(lhs.nlist); - } else { - return false; - } - } - - pub fn next(self: *AtomParser, context: Context) !?*Atom { - if (self.index == self.nlists.len) return null; - - const tracy = trace(@src()); - defer tracy.end(); - - var aliases = std.ArrayList(NlistWithIndex).init(context.allocator); - defer aliases.deinit(); - - const next_nlist: ?NlistWithIndex = blk: while (true) { - const curr_nlist = self.nlists[self.index]; - try aliases.append(curr_nlist); - - if (self.peek()) |next_nlist| { - if (curr_nlist.nlist.n_value == next_nlist.nlist.n_value) { - self.index += 1; - continue; - } - break :blk next_nlist; - } - break :blk null; - } else null; - - for (aliases.items) |*nlist_with_index| { - nlist_with_index.index = context.object.symbol_mapping.get(nlist_with_index.index) orelse unreachable; - } - - if (aliases.items.len > 1) { - // Bubble-up senior symbol as the main link to the atom. - sort.sort( - NlistWithIndex, - aliases.items, - context, - AtomParser.lessThanBySeniority, - ); - } - - const senior_nlist = aliases.pop(); - const senior_sym = &context.macho_file.locals.items[senior_nlist.index]; - senior_sym.n_sect = @intCast(u8, context.macho_file.section_ordinals.getIndex(context.match).? + 1); - - const start_addr = senior_nlist.nlist.n_value - self.section.addr; - const end_addr = if (next_nlist) |n| n.nlist.n_value - self.section.addr else self.section.size; - - const code = self.code[start_addr..end_addr]; - const size = code.len; - - const max_align = self.section.@"align"; - const actual_align = if (senior_nlist.nlist.n_value > 0) - math.min(@ctz(u64, senior_nlist.nlist.n_value), max_align) - else - max_align; - - const stab: ?Atom.Stab = if (context.object.debug_info) |di| blk: { - // TODO there has to be a better to handle this. - for (di.inner.func_list.items) |func| { - if (func.pc_range) |range| { - if (senior_nlist.nlist.n_value >= range.start and senior_nlist.nlist.n_value < range.end) { - break :blk Atom.Stab{ - .function = range.end - range.start, - }; - } - } - } - // TODO - // if (self.macho_file.globals.contains(self.macho_file.getString(senior_sym.strx))) break :blk .global; - break :blk .static; - } else null; - - const atom = try context.macho_file.createEmptyAtom(senior_nlist.index, size, actual_align); - atom.stab = stab; - - const is_zerofill = blk: { - const section_type = commands.sectionType(self.section); - break :blk section_type == macho.S_ZEROFILL or section_type == macho.S_THREAD_LOCAL_ZEROFILL; - }; - if (!is_zerofill) { - mem.copy(u8, atom.code.items, code); - } - - try atom.aliases.ensureTotalCapacity(context.allocator, aliases.items.len); - for (aliases.items) |alias| { - atom.aliases.appendAssumeCapacity(alias.index); - const sym = &context.macho_file.locals.items[alias.index]; - sym.n_sect = @intCast(u8, context.macho_file.section_ordinals.getIndex(context.match).? + 1); - } - - try atom.parseRelocs(self.relocs, .{ - .base_addr = self.section.addr, - .base_offset = start_addr, - .allocator = context.allocator, - .object = context.object, - .macho_file = context.macho_file, - }); - - if (context.macho_file.has_dices) { - const dices = filterDice( - context.object.data_in_code_entries.items, - senior_nlist.nlist.n_value, - senior_nlist.nlist.n_value + size, - ); - try atom.dices.ensureTotalCapacity(context.allocator, dices.len); - - for (dices) |dice| { - atom.dices.appendAssumeCapacity(.{ - .offset = dice.offset - try math.cast(u32, senior_nlist.nlist.n_value), - .length = dice.length, - .kind = dice.kind, - }); - } - } - - self.index += 1; - - return atom; - } -}; - pub fn parseIntoAtoms(self: *Object, allocator: *Allocator, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); @@ -603,17 +457,10 @@ pub fn parseIntoAtoms(self: *Object, allocator: *Allocator, macho_file: *MachO) // Since there is no symbol to refer to this atom, we create // a temp one, unless we already did that when working out the relocations // of other atoms. - const sym_name = try std.fmt.allocPrint(allocator, "l_{s}_{s}_{s}", .{ - self.name, - segmentName(sect), - sectionName(sect), - }); - defer allocator.free(sym_name); - const atom_local_sym_index = self.sections_as_symbols.get(sect_id) orelse blk: { const atom_local_sym_index = @intCast(u32, macho_file.locals.items.len); try macho_file.locals.append(allocator, .{ - .n_strx = try macho_file.makeString(sym_name), + .n_strx = 0, .n_type = macho.N_SECT, .n_sect = @intCast(u8, macho_file.section_ordinals.getIndex(match).? + 1), .n_desc = 0, From affd8f8b59d9d803f98178ec32ab9e2f6b0b30d2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 21 Sep 2021 15:59:20 +0200 Subject: [PATCH 085/160] macho: fix incorrect segment/section growth calculation Otherwise, for last sections in segments it could happen we would not expand the segment when actually required thus exceeding the segment's size and causing data clobbering and dyld runtime errors. --- src/link/MachO.zig | 135 +- test/standalone.zig | 1 + test/standalone/issue_9812/build.zig | 16 + test/standalone/issue_9812/main.zig | 45 + .../issue_9812/vendor/kuba-zip/miniz.h | 6875 +++++++++++++++++ .../issue_9812/vendor/kuba-zip/zip.c | 1622 ++++ .../issue_9812/vendor/kuba-zip/zip.h | 433 ++ 7 files changed, 9065 insertions(+), 62 deletions(-) create mode 100644 test/standalone/issue_9812/build.zig create mode 100644 test/standalone/issue_9812/main.zig create mode 100644 test/standalone/issue_9812/vendor/kuba-zip/miniz.h create mode 100644 test/standalone/issue_9812/vendor/kuba-zip/zip.c create mode 100644 test/standalone/issue_9812/vendor/kuba-zip/zip.h diff --git a/src/link/MachO.zig b/src/link/MachO.zig index e803dc277a..93b3cc7e93 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -4085,6 +4085,70 @@ fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u64) u64 return mem.alignForwardGeneric(u64, final_off, alignment); } +fn growSegment(self: *MachO, seg_id: u16, new_size: u64) !void { + const seg = &self.load_commands.items[seg_id].Segment; + const new_seg_size = mem.alignForwardGeneric(u64, new_size, self.page_size); + assert(new_seg_size > seg.inner.filesize); + const offset_amt = new_seg_size - seg.inner.filesize; + log.debug("growing segment {s} from 0x{x} to 0x{x}", .{ seg.inner.segname, seg.inner.filesize, new_seg_size }); + seg.inner.filesize = new_seg_size; + seg.inner.vmsize = new_seg_size; + + log.debug(" (new segment file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{ + seg.inner.fileoff, + seg.inner.fileoff + seg.inner.filesize, + seg.inner.vmaddr, + seg.inner.vmaddr + seg.inner.vmsize, + }); + + // TODO We should probably nop the expanded by distance, or put 0s. + + // TODO copyRangeAll doesn't automatically extend the file on macOS. + const ledit_seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; + const new_filesize = offset_amt + ledit_seg.inner.fileoff + ledit_seg.inner.filesize; + try self.base.file.?.pwriteAll(&[_]u8{0}, new_filesize - 1); + + var next: usize = seg_id + 1; + while (next < self.linkedit_segment_cmd_index.? + 1) : (next += 1) { + const next_seg = &self.load_commands.items[next].Segment; + _ = try self.base.file.?.copyRangeAll( + next_seg.inner.fileoff, + self.base.file.?, + next_seg.inner.fileoff + offset_amt, + next_seg.inner.filesize, + ); + next_seg.inner.fileoff += offset_amt; + next_seg.inner.vmaddr += offset_amt; + + log.debug(" (new {s} segment file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{ + next_seg.inner.segname, + next_seg.inner.fileoff, + next_seg.inner.fileoff + next_seg.inner.filesize, + next_seg.inner.vmaddr, + next_seg.inner.vmaddr + next_seg.inner.vmsize, + }); + + for (next_seg.sections.items) |*moved_sect, moved_sect_id| { + moved_sect.offset += @intCast(u32, offset_amt); + moved_sect.addr += offset_amt; + + log.debug(" (new {s},{s} file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{ + commands.segmentName(moved_sect.*), + commands.sectionName(moved_sect.*), + moved_sect.offset, + moved_sect.offset + moved_sect.size, + moved_sect.addr, + moved_sect.addr + moved_sect.size, + }); + + try self.allocateLocalSymbols(.{ + .seg = @intCast(u16, next), + .sect = @intCast(u16, moved_sect_id), + }, @intCast(i64, offset_amt)); + } + } +} + fn growSection(self: *MachO, match: MatchingSection, new_size: u32) !void { const tracy = trace(@src()); defer tracy.end(); @@ -4098,7 +4162,14 @@ fn growSection(self: *MachO, match: MatchingSection, new_size: u32) !void { const needed_size = mem.alignForwardGeneric(u32, ideal_size, alignment); if (needed_size > max_size) blk: { - log.debug(" (need to grow!)", .{}); + log.debug(" (need to grow! needed 0x{x}, max 0x{x})", .{ needed_size, max_size }); + + if (match.sect == seg.sections.items.len - 1) { + // Last section, just grow segments + try self.growSegment(match.seg, seg.inner.filesize + needed_size - max_size); + break :blk; + } + // Need to move all sections below in file and address spaces. const offset_amt = offset: { const max_alignment = try self.getSectionMaxAlignment(match.seg, match.sect + 1); @@ -4114,70 +4185,10 @@ fn growSection(self: *MachO, match: MatchingSection, new_size: u32) !void { if (last_sect_off + offset_amt > seg_off) { // Need to grow segment first. - log.debug(" (need to grow segment first)", .{}); const spill_size = (last_sect_off + offset_amt) - seg_off; - const seg_offset_amt = mem.alignForwardGeneric(u64, spill_size, self.page_size); - seg.inner.filesize += seg_offset_amt; - seg.inner.vmsize += seg_offset_amt; - - log.debug(" (new {s} segment file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{ - seg.inner.segname, - seg.inner.fileoff, - seg.inner.fileoff + seg.inner.filesize, - seg.inner.vmaddr, - seg.inner.vmaddr + seg.inner.vmsize, - }); - - // TODO We should probably nop the expanded by distance, or put 0s. - - // TODO copyRangeAll doesn't automatically extend the file on macOS. - const ledit_seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment; - const new_filesize = seg_offset_amt + ledit_seg.inner.fileoff + ledit_seg.inner.filesize; - try self.base.file.?.pwriteAll(&[_]u8{0}, new_filesize - 1); - - var next: usize = match.seg + 1; - while (next < self.linkedit_segment_cmd_index.? + 1) : (next += 1) { - const next_seg = &self.load_commands.items[next].Segment; - _ = try self.base.file.?.copyRangeAll( - next_seg.inner.fileoff, - self.base.file.?, - next_seg.inner.fileoff + seg_offset_amt, - next_seg.inner.filesize, - ); - next_seg.inner.fileoff += seg_offset_amt; - next_seg.inner.vmaddr += seg_offset_amt; - - log.debug(" (new {s} segment file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{ - next_seg.inner.segname, - next_seg.inner.fileoff, - next_seg.inner.fileoff + next_seg.inner.filesize, - next_seg.inner.vmaddr, - next_seg.inner.vmaddr + next_seg.inner.vmsize, - }); - - for (next_seg.sections.items) |*moved_sect, moved_sect_id| { - moved_sect.offset += @intCast(u32, seg_offset_amt); - moved_sect.addr += seg_offset_amt; - - log.debug(" (new {s},{s} file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{ - commands.segmentName(moved_sect.*), - commands.sectionName(moved_sect.*), - moved_sect.offset, - moved_sect.offset + moved_sect.size, - moved_sect.addr, - moved_sect.addr + moved_sect.size, - }); - - try self.allocateLocalSymbols(.{ - .seg = @intCast(u16, next), - .sect = @intCast(u16, moved_sect_id), - }, @intCast(i64, seg_offset_amt)); - } - } + try self.growSegment(match.seg, seg.inner.filesize + spill_size); } - if (match.sect + 1 >= seg.sections.items.len) break :blk; - // We have enough space to expand within the segment, so move all sections by // the required amount and update their header offsets. const next_sect = seg.sections.items[match.sect + 1]; diff --git a/test/standalone.zig b/test/standalone.zig index 45158f1057..c5e12f1dcc 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -28,6 +28,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void { cases.addBuildFile("test/standalone/empty_env/build.zig", .{}); cases.addBuildFile("test/standalone/issue_7030/build.zig", .{}); cases.addBuildFile("test/standalone/install_raw_hex/build.zig", .{}); + cases.addBuildFile("test/standalone/issue_9812/build.zig", .{}); if (std.Target.current.os.tag != .wasi) { cases.addBuildFile("test/standalone/load_dynamic_library/build.zig", .{}); } diff --git a/test/standalone/issue_9812/build.zig b/test/standalone/issue_9812/build.zig new file mode 100644 index 0000000000..de13ada8ec --- /dev/null +++ b/test/standalone/issue_9812/build.zig @@ -0,0 +1,16 @@ +const std = @import("std"); + +pub fn build(b: *std.build.Builder) !void { + const mode = b.standardReleaseOptions(); + const zip_add = b.addTest("main.zig"); + zip_add.setBuildMode(mode); + zip_add.addCSourceFile("vendor/kuba-zip/zip.c", &[_][]const u8{ + "-std=c99", + "-fno-sanitize=undefined", + }); + zip_add.addIncludeDir("vendor/kuba-zip"); + zip_add.linkLibC(); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(&zip_add.step); +} diff --git a/test/standalone/issue_9812/main.zig b/test/standalone/issue_9812/main.zig new file mode 100644 index 0000000000..70899c9326 --- /dev/null +++ b/test/standalone/issue_9812/main.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const testing = std.testing; + +const c = @cImport({ + @cInclude("zip.h"); +}); + +const Error = error{ + FailedToWriteEntry, + FileNotFound, + FailedToCreateEntry, + Overflow, + OutOfMemory, + InvalidCmdLine, +}; + +test "" { + const allocator = std.heap.c_allocator; + + const args = try std.process.argsAlloc(allocator); + if (args.len != 4) { + return; + } + + const zip_file = args[1]; + const src_file_name = args[2]; + const dst_file_name = args[3]; + + errdefer |e| switch (@as(Error, e)) { + error.FailedToWriteEntry => std.log.err("could not find {s}", .{src_file_name}), + error.FileNotFound => std.log.err("could not open {s}", .{zip_file}), + error.FailedToCreateEntry => std.log.err("could not create {s}", .{dst_file_name}), + else => {}, + }; + + const zip = c.zip_open(zip_file, c.ZIP_DEFAULT_COMPRESSION_LEVEL, 'a') orelse return error.FileNotFound; + defer c.zip_close(zip); + + if (c.zip_entry_open(zip, dst_file_name) < 0) + return error.FailedToCreateEntry; + defer _ = c.zip_entry_close(zip); + + if (c.zip_entry_fwrite(zip, src_file_name) < 0) + return error.FailedToWriteEntry; +} diff --git a/test/standalone/issue_9812/vendor/kuba-zip/miniz.h b/test/standalone/issue_9812/vendor/kuba-zip/miniz.h new file mode 100644 index 0000000000..3bbe2d937b --- /dev/null +++ b/test/standalone/issue_9812/vendor/kuba-zip/miniz.h @@ -0,0 +1,6875 @@ +/* + miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP + reading/writing/appending, PNG writing See "unlicense" statement at the end + of this file. Rich Geldreich , last updated Oct. 13, + 2013 Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: + http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the + archive related functions just define MINIZ_NO_ARCHIVE_APIS, or to get rid of + all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Change History + 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major + release with Zip64 support (almost there!): + - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug + (thanks kahmyong.moon@hp.com) which could cause locate files to not find + files. This bug would only have occured in earlier versions if you explicitly + used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or + mz_zip_add_mem_to_archive_file_in_place() (which used this flag). If you + can't switch to v1.15 but want to fix this bug, just remove the uses of this + flag from both helper funcs (and of course don't use the flag). + - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when + pUser_read_buf is not NULL and compressed size is > uncompressed size + - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract + compressed data from directory entries, to account for weird zipfiles which + contain zero-size compressed data on dir entries. Hopefully this fix won't + cause any issues on weird zip archives, because it assumes the low 16-bits of + zip external attributes are DOS attributes (which I believe they always are + in practice). + - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the + internal attributes, just the filename and external attributes + - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed + - Added cmake support for Linux builds which builds all the examples, + tested with clang v3.3 and gcc v4.6. + - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti + - Merged MZ_FORCEINLINE fix from hdeanclark + - Fix include before config #ifdef, thanks emil.brink + - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping + (super useful for OpenGL apps), and explicit control over the compression + level (so you can set it to 1 for real-time compression). + - Merged in some compiler fixes from paulharris's github repro. + - Retested this build under Windows (VS 2010, including static analysis), + tcc 0.9.26, gcc v4.6 and clang v3.3. + - Added example6.c, which dumps an image of the mandelbrot set to a PNG + file. + - Modified example2 to help test the + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. + - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix + possible src file fclose() leak if alignment bytes+local header file write + faiiled + - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the + wrong central dir header offset, appears harmless in this release, but it + became a problem in the zip64 branch 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 + compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix + mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. + - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and + re-ran a randomized regression test on ~500k files. + - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. + - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze + (static analysis) option and fixed all warnings (except for the silly "Use of + the comma-operator in a tested expression.." analysis warning, which I + purposely use to work around a MSVC compiler warning). + - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and + tested Linux executables. The codeblocks workspace is compatible with + Linux+Win32/x64. + - Added miniz_tester solution/project, which is a useful little app + derived from LZHAM's tester app that I use as part of the regression test. + - Ran miniz.c and tinfl.c through another series of regression testing on + ~500,000 files and archives. + - Modified example5.c so it purposely disables a bunch of high-level + functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the + MINIZ_NO_STDIO bug report.) + - Fix ftell() usage in examples so they exit with an error on files which + are too large (a limitation of the examples, not miniz itself). 4/12/12 v1.12 + - More comments, added low-level example5.c, fixed a couple minor + level_and_flags issues in the archive API's. level_and_flags can now be set + to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson + for the feedback/bug report. 5/28/11 v1.11 - Added statement from + unlicense.org 5/27/11 v1.10 - Substantial compressor optimizations: + - Level 1 is now ~4x faster than before. The L1 compressor's throughput + now varies between 70-110MB/sec. on a + - Core i7 (actual throughput varies depending on the type of data, and x64 + vs. x86). + - Improved baseline L2-L9 compression perf. Also, greatly improved + compression perf. issues on some file types. + - Refactored the compression code for better readability and + maintainability. + - Added level 10 compression level (L10 has slightly better ratio than + level 9, but could have a potentially large drop in throughput on some + files). 5/15/11 v1.09 - Initial stable release. + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, + and dynamic blocks, lazy or greedy parsing, match length filtering, RLE-only, + and Huffman-only streams. It performs and compresses approximately as well as + zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is + implemented as a single function coroutine: see tinfl_decompress(). It + supports decompression into a 32KB (or larger power of 2) wrapping buffer, or + into a memory block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory + allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough + functionality present for it to be a drop-in zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly + routines. Supports raw deflate streams or standard zlib streams with adler-32 + checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or + zlib static dictionaries. I've tried to closely emulate zlib's various + flavors of stream flushing and return status codes, but there are no + guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, + originally written by Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in + mind, with just enough abstraction to get the job done with minimal fuss. + There are simple API's to retrieve file information, read files from existing + archives, create new archives, append new files to existing archives, or + clone archive data from one archive to another. It supports archives located + in memory or the heap, on disk (using stdio.h), or you can specify custom + file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a + disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const + char *pArchive_name, size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an + archive, the entire central directory is located and read as-is into memory, + and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a + loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, + const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one + example) can be used to identify multiple versions of the same file in an + archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using + mz_zip_reader_get_num_files()) and retrieve detailed info on each file by + calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer + immediately writes compressed file data to disk and builds an exact image of + the central directory in memory. The central directory image is written all + at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file + data to any power of 2 alignment, which can be useful when the archive will + be read from optical media. Also, the writer supports placing arbitrary data + blobs at the very beginning of ZIP archives. Archives written using either + feature are still readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is + to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, + const char *pArchive_name, const void *pBuf, size_t buf_size, const void + *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be + appended to. Note the appending is done in-place and is not an atomic + operation, so if something goes wrong during the operation it's possible the + archive could be left without a central directory (although the local file + headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, + cloning only those bits you want to preserve into a new archive using using + the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and + rename the newly written archive, and you're done. This is safe but requires + a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using + mz_zip_writer_init_from_reader(), append new files as needed, then finalize + the archive which will write an updated central directory to the original + archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() + does.) There's a possibility that the archive's central directory could be + lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle + unencrypted, stored or deflated files. Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, + either cut and paste the below header, or create miniz.h, #define + MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your + target platform: #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define + MINIZ_LITTLE_ENDIAN 1 #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before + including miniz.c to ensure miniz uses the 64-bit variants: fopen64(), + stat64(), etc. Otherwise you won't be able to process large files (i.e. + 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be +// CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on +// stdio for file I/O. +//#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able +// to get the current time, or get/set file times, and the C run-time funcs that +// get/set times won't be called. The current downside is the times written to +// your archives will be from 1979. +//#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive +// API's. +//#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression +// API's. +//#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent +// conflicts against stock zlib. +//#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom +// user alloc/free/realloc callbacks to the zlib and archive API's, and a few +// stand-alone helper API's which don't provide custom user functions (such as +// tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +//#define MINIZ_NO_MALLOC + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) +// TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc +// on Linux +#define MINIZ_NO_TIME +#endif + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) +#include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \ + defined(__i386) || defined(__i486__) || defined(__i486) || \ + defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ +#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) +#if MINIZ_X86_OR_X64_CPU +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient + * integer loads and stores from unaligned addresses. */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#define MINIZ_UNALIGNED_USE_MEMCPY +#else +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || \ + defined(_LP64) || defined(__LP64__) || defined(__ia64__) || \ + defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are +// reasonably fast (and don't involve compiler generated calls to helper +// functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __APPLE__ +#define ftello64 ftello +#define fseeko64 fseeko +#define fopen64 fopen +#define freopen64 freopen + +// Darwin OSX +#define MZ_PLATFORM 19 +#endif + +#ifndef MZ_PLATFORM +#if defined(_WIN64) || defined(_WIN32) || defined(__WIN32__) +#define MZ_PLATFORM 0 +#else +// UNIX +#define MZ_PLATFORM 3 +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some +// parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() +// unless you've modified the MZ_MALLOC macro) to release a block allocated from +// the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with +// ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with +// ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { + MZ_DEFAULT_STRATEGY = 0, + MZ_FILTERED = 1, + MZ_HUFFMAN_ONLY = 2, + MZ_RLE = 3, + MZ_FIXED = 4 +}; + +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or + * modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purposely differ from zlib's: +// items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, + size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The +// other values are for advanced use (refer to the zlib docs). +enum { + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum { + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best +// possible compression (not zlib compatible, and may be very slow), +// MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum { + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s { + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func + zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been +// optimized purely for performance, not ratio. (This special func. is +// currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and +// MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with +// zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no +// header or footer) mem_level must be between [1, 9] (it's checked but +// ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, + int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as +// calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input +// and producing as much output as possible. Parameters: +// pStream is the stream to read from and write to. You must initialize/update +// the next_in, avail_in, next_out, and avail_out members. flush may be +// MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not +// available, and/or there's more output to be written but the output buffer +// is full). MZ_STREAM_END if all input has been consumed and all output bytes +// have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or +// output buffers are empty. (Fill up the input buffer or free up some output +// space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of +// data that could be generated by deflate(), assuming flush is set to only +// MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on +// failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of +// data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that +// controls the window size and whether or not the stream has been wrapped with +// a zlib header/footer: window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse +// zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the +// input as needed, and writing as much to the output as possible. Parameters: +// pStream is the stream to read from and write to. You must initialize/update +// the next_in, avail_in, next_out, and avail_out members. flush may be +// MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. On the first call, if flush is +// MZ_FINISH it's assumed the input and output buffers are both sized large +// enough to decompress the entire stream in a single call (this is slightly +// faster). MZ_FINISH implies that there are no more source bytes available +// beside what's already in the input buffer, and that the output buffer is +// large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or +// there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes +// have been written. For zlib streams, the adler-32 of the decompressed data +// has also been verified. MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is +// empty but the inflater needs more input to continue, or if the output +// buffer is not large enough. Call mz_inflate() again with more input data, +// or with more room in the output buffer (except when using single call +// decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on +// failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the +// error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used +// as a drop-in replacement for the subset of zlib that miniz.c supports. Define +// MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib +// in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void *voidpf; +typedef uLong uLongf; +typedef void *voidp; +typedef void *const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional +// expression is constant" message. +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum { + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct { + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, + void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, + const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +typedef struct { + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, + mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, + size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, + mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, + mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, + mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is +// 0 this function returns the number of bytes needed to fully store the +// filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, + char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, + const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, + mz_uint file_index, void *pBuf, + size_t buf_size, mz_uint flags, + void *pUser_read_buf, + size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc( + mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, + mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, + void *pBuf, size_t buf_size, + mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, + const char *pFilename, void *pBuf, + size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, + size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, + const char *pFilename, size_t *pSize, + mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, + mz_uint file_index, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, + const char *pFilename, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and +// modified times. This function only extracts files, not archive directory +// records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, + const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, + const char *pArchive_filename, + const char *pDst_filename, + mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive +// file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +// Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, + size_t size_to_reserve_at_beginning, + size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient +// in-place file appends to occur on an existing archive. For archives opened +// using mz_zip_reader_init_file, pFilename must be the archive's filename so it +// can be reopened for writing. If the file can't be reopened, +// mz_zip_reader_end() will be called. For archives opened using +// mz_zip_reader_init_mem, the memory block must be growable using the realloc +// callback (which defaults to realloc unless you've overridden it). Finally, +// for archives opened using mz_zip_reader_init, the mz_zip_archive's user +// provided m_pWrite function cannot be NULL. Note: In-place archive +// modification is not recommended unless you know what you're doing, because if +// execution stops or something goes wrong before the archive is finalized the +// file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, + const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record +// the current local time into the archive. To add a directory entry, call this +// method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, +// MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or +// just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, + const void *pBuf, size_t buf_size, + mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, + const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, + mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, + mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records +// the disk file's modified time into the archive. level_and_flags - compression +// level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd +// with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, + const char *pSrc_filename, const void *pComment, + mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint32 ext_attributes); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no +// recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, + mz_zip_archive *pSource_zip, + mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by +// the end of central directory record. After an archive is finalized, the only +// valid call on the mz_zip_archive struct is mz_zip_writer_end(). An archive +// must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, + size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if +// mz_zip_writer_init_file() was used. Note for the archive to be valid, it must +// have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) +// appends a memory blob to a ZIP archive. level_and_flags - compression level +// (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero +// or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place( + const char *pZip_filename, const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, + const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and +// ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the +// input is a raw deflate stream. TINFL_FLAG_HAS_MORE_INPUT: If set, there are +// more input bytes available beyond the end of the supplied input buffer. If +// clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large +// enough to hold the entire decompressed stream. If clear, the output buffer is +// at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the +// decompressed bytes. +enum { + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block +// allocated via malloc(). On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data +// to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger +// than src_buf_len on uncompressible data. The caller must call mz_free() on +// the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block +// in memory. Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the +// number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an +// internal 32KB buffer, and a user provided callback function will be called to +// flush the buffer. Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, + tinfl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; +typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum { + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) \ + do { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function +// actually needed for decompression. All the other functions are just +// high-level helpers for improved usability. This is a universal API, i.e. it +// can be used as a building block to build any desired higher level +// decompression API. In the limit case, it can be called once per every byte +// input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, + const mz_uint8 *pIn_buf_next, + size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, + mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, + const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum { + TINFL_MAX_HUFF_TABLES = 3, + TINFL_MAX_HUFF_SYMBOLS_0 = 288, + TINFL_MAX_HUFF_SYMBOLS_1 = 32, + TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct { + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], + m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) +#else +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag { + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, + m_check_adler32, m_dist, m_counter, m_num_extra, + m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], + m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly +// slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 0 + +// tdefl_init() compression flags logically OR'd together (low 12 bits contain +// the max. number of probes per dictionary search): TDEFL_DEFAULT_MAX_PROBES: +// The compressor defaults to 128 dictionary probes per dictionary search. +// 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ +// (slowest/best compression). +enum { + TDEFL_HUFFMAN_ONLY = 0, + TDEFL_DEFAULT_MAX_PROBES = 128, + TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before +// the deflate data, and the Adler-32 of the source data at the end. Otherwise, +// you'll get raw deflate data. TDEFL_COMPUTE_ADLER32: Always compute the +// adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more +// efficient lazy parsing. TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to +// decrease the compressor's initialization time to the minimum, but the output +// may vary from run to run given the same input (depending on the contents of +// memory). TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a +// distance of 1) TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per +// dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum { + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block +// allocated via malloc(). On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against +// the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger +// than src_buf_len on uncompressible data. The caller must free() the returned +// block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in +// memory. Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be +// 1, 2, 3, or 4. The image pitch in bytes per scanline will be w*num_chans. +// The leftmost pixel on the top scanline is stored first in memory. level may +// range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, +// MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL If flip is +// true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be +// larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, + int h, int num_chans, + size_t *pLen_out, + mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, + int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write +// compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, + void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The +// above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags); + +enum { + TDEFL_MAX_HUFF_TABLES = 3, + TDEFL_MAX_HUFF_SYMBOLS_0 = 288, + TDEFL_MAX_HUFF_SYMBOLS_1 = 32, + TDEFL_MAX_HUFF_SYMBOLS_2 = 19, + TDEFL_LZ_DICT_SIZE = 32768, + TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, + TDEFL_MIN_MATCH_LEN = 3, + TDEFL_MAX_MATCH_LEN = 258 +}; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed +// output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { + TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 12, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#else +enum { + TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 15, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#endif + +// The low-level tdefl functions below may be used directly if the above helper +// functions aren't flexible enough. The low-level functions don't make any heap +// allocations, unlike the above helper functions. +typedef enum { + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum { + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct { + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, + m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, + m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, + m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not +// dynamically allocate memory. pBut_buf_func: If NULL, output data will be +// supplied to the specified callback. In this case, the user should call the +// tdefl_compress_buffer() API for compression. If pBut_buf_func is NULL the +// user should always call the tdefl_compress() API. flags: See the above enums +// (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer +// as possible, and writing as much compressed data to the specified output +// buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, + size_t *pIn_buf_size, void *pOut_buf, + size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a +// non-NULL tdefl_put_buf_func_ptr. tdefl_compress_buffer() always consumes the +// entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, + size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't +// defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS +// Create tdefl_compress() flags given zlib-style compression parameters. +// level may range from [0,10] (where 10 is absolute max compression, but may be +// much slower on some files) window_bits may be -15 (raw deflate) or 15 (zlib) +// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, +// MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, + int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + +// ------------------- End of Header: Implementation follows. (If you only want +// the header, define MINIZ_HEADER_FILE_ONLY.) + +#ifndef MINIZ_HEADER_FILE_ONLY + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#include +#include + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC +#define MZ_MALLOC(x) NULL +#define MZ_FREE(x) (void)x, ((void)0) +#define MZ_REALLOC(p, x) NULL +#else +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else +#define MZ_READ_LE16(p) \ + ((mz_uint32)(((const mz_uint8 *)(p))[0]) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) \ + ((mz_uint32)(((const mz_uint8 *)(p))[0]) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | \ + ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#define MZ_READ_LE64(p) \ + (((mz_uint64)MZ_READ_LE32(p)) | \ + (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) \ + << 32U)) + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API's + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) { + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C +// implementation that balances processor cache usage against speed": +// http://www.geocities.com/malbrain/ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) { + static const mz_uint32 s_crc32[16] = { + 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, + 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c}; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; +} + +void mz_free(void *p) { MZ_FREE(p); } + +#ifndef MINIZ_NO_ZLIB_APIS + +static void *def_alloc_func(void *opaque, size_t items, size_t size) { + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +static void def_free_func(void *opaque, void *address) { + (void)opaque, (void)address; + MZ_FREE(address); +} +static void *def_realloc_func(void *opaque, void *address, size_t items, + size_t size) { + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +const char *mz_version(void) { return MZ_VERSION; } + +int mz_deflateInit(mz_streamp pStream, int level) { + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, + MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, + int mem_level, int strategy) { + tdefl_compressor *pComp; + mz_uint comp_flags = + TDEFL_COMPUTE_ADLER32 | + tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || + ((window_bits != MZ_DEFAULT_WINDOW_BITS) && + (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = def_alloc_func; + if (!pStream->zfree) + pStream->zfree = def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, + sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) { + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || + (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, + ((tdefl_compressor *)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) { + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || + (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor *)pStream->state)->m_prev_return_status == + TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor *)pStream->state, + pStream->next_in, &in_bytes, pStream->next_out, + &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) { + mz_status = MZ_STREAM_ERROR; + break; + } else if (defl_status == TDEFL_STATUS_DONE) { + mz_status = MZ_STREAM_END; + break; + } else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) { + if ((flush) || (pStream->total_in != orig_total_in) || + (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) { + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) { + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty + // tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, + 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len, int level) { + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len) { + return mz_compress2(pDest, pDest_len, pSource, source_len, + MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) { + return mz_deflateBound(NULL, source_len); +} + +typedef struct { + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) { + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && + (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = def_alloc_func; + if (!pStream->zfree) + pStream->zfree = def_free_func; + + pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, + sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) { + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) { + inflate_state *pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state *)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) { + // MZ_FINISH on the first call implies that the input and output buffers are + // large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, + pStream->next_out, pStream->next_out, &out_bytes, + decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && + (!pState->m_dict_avail)) + ? MZ_STREAM_END + : MZ_OK; + } + + for (;;) { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress( + &pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, + pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some + // uncompressed data left in the output dictionary - + // oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress + // without supplying more input or by setting flush + // to MZ_FINISH. + else if (flush == MZ_FINISH) { + // The output buffer MUST be large to hold the remaining uncompressed data + // when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's + // at least 1 more byte on the way. If there's no more room left in the + // output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || + (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) + ? MZ_STREAM_END + : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) { + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, + const unsigned char *pSource, mz_ulong source_len) { + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR + : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) { + static struct { + int m_err; + const char *m_pDesc; + } s_error_descs[] = {{MZ_OK, ""}, + {MZ_STREAM_END, "stream end"}, + {MZ_NEED_DICT, "need dictionary"}, + {MZ_ERRNO, "file error"}, + {MZ_STREAM_ERROR, "stream error"}, + {MZ_DATA_ERROR, "data error"}, + {MZ_MEM_ERROR, "out of memory"}, + {MZ_BUF_ERROR, "buf error"}, + {MZ_VERSION_ERROR, "version error"}, + {MZ_PARAM_ERROR, "parameter error"}}; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Low-level Decompression (completely independent from all +// compression API's) + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN \ + switch (r->m_state) { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do { \ + for (;;) { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END +#define TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt +// to read beyond the input buf, then something is wrong with the input because +// the inflator never reads ahead more than it needs to. Currently +// TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define TINFL_GET_BYTE(state_index, c) \ + do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for (;;) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do { \ + if (num_bits < (mz_uint)(n)) { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do { \ + if (num_bits < (mz_uint)(n)) { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END + +// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes +// remaining in the input buffer falls below 2. It reads just enough bytes from +// the input stream that are needed to decode the next Huffman code (and +// absolutely no more). It works by trying to fully decode a Huffman code by +// using whatever bits are currently present in the bit buffer. If this fails, +// it reads another byte, and tries again until it succeeds or until the bit +// buffer contains >=15 bits (deflate's max. Huffman code size). +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex +// than you would initially expect because the zlib API expects the decompressor +// to never read beyond the final byte of the deflate stream. (In other words, +// when this macro wants to read another byte from the input, it REALLY needs +// another byte in order to fully decode the next Huffman code.) Handling this +// properly is particularly important on raw deflate (non-zlib) streams, which +// aren't followed by a byte aligned adler-32. The slow path is only executed at +// the very end of the input buffer. +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ + do { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | \ + (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= \ + 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, + const mz_uint8 *pIn_buf_next, + size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, + mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, + const mz_uint32 decomp_flags) { + static const int s_length_base[31] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + static const int s_length_extra[31] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, + 4, 4, 5, 5, 5, 5, 0, 0, 0}; + static const int s_dist_base[32] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, + 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, + 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0}; + static const int s_dist_extra[32] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, + 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + static const mz_uint8 s_length_dezigzag[19] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + static const int s_min_table_sizes[3] = {257, 1, 4}; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = + pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = + pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = + (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) + ? (size_t)-1 + : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, + dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer + // is large enough to hold the entire output file (in which case it doesn't + // matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || + (pOut_buf_next < pOut_buf_start)) { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || + (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || + ((out_buf_size_mask + 1) < + (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != + (mz_uint)(0xFFFF ^ + (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } else { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), + (size_t)(pIn_buf_end - pIn_buf_cur)), + counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } else if (r->m_type == 3) { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } else { + if (r->m_type == 1) { + mz_uint8 *p = r->m_tables[0].m_code_size; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } else { + for (counter = 0; counter < 3; counter++) { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) { + int tree_next, tree_cur; + tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], + total_syms[16]; + pTable = &r->m_tables[r->m_type]; + MZ_CLEAR_OBJ(total_syms); + MZ_CLEAR_OBJ(pTable->m_look_up); + MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; + sym_index < r->m_table_sizes[r->m_type]; ++sym_index) { + mz_uint rev_code = 0, l, cur_code, + code_size = pTable->m_code_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) { + pTable->m_look_up[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == + (tree_cur = pTable->m_look_up[rev_code & + (TINFL_FAST_LOOKUP_SIZE - 1)])) { + pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = + (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { + pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } else + tree_cur = pTable->m_tree[-tree_cur - 1]; + } + rev_code >>= 1; + tree_cur -= (rev_code & 1); + pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) { + for (counter = 0; + counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + if (dist < 16) { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, + (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, + r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_tables[1].m_code_size, + r->m_len_codes + r->m_table_sizes[0], + r->m_table_sizes[1]); + } + } + for (;;) { + mz_uint8 *pSrc; + for (;;) { + if (((pIn_buf_end - pIn_buf_cur) < 4) || + ((pOut_buf_end - pOut_buf_cur) < 2)) { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } else { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { + bit_buf |= + (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) { + bit_buf |= + (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = + r->m_tables[0] + .m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= + 0) + code_len = sym2 >> 9; + else { + code_len = TINFL_FAST_LOOKUP_BITS; + do { + sym2 = r->m_tables[0] + .m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { + bit_buf |= + (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = + r->m_tables[0] + .m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= + 0) + code_len = sym2 >> 9; + else { + code_len = TINFL_FAST_LOOKUP_BITS; + do { + sym2 = r->m_tables[0] + .m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && + (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) { + while (counter--) { + while (pOut_buf_cur >= pOut_buf_end) { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = + pOut_buf_start[(dist_from_out_buf_start++ - dist) & + out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) { + if (counter) { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) { + TINFL_SKIP_BITS(32, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; + } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf; + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & + (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && + (status >= 0)) { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, + s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && + (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && + (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags) { + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) { + size_t src_buf_size = src_buf_len - src_buf_ofs, + dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress( + &decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, + (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, + &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags) { + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + status = + tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, + (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED + : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, + tinfl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags) { + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (;;) { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, + dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = + tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, + &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && + (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +// ------------------- Low-level Compression (independent from all decompression +// API's) + +// Purposely making these tables static for faster init and thread safety. +static const mz_uint16 s_tdefl_len_sym[256] = { + 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, + 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, + 272, 272, 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, + 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, + 276, 276, 276, 276, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, + 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278, 278, 278, 278, 279, 279, 279, 279, 279, 279, 279, 279, 279, + 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, + 280, 280, 280, 280, 280, 280, 280, 280, 281, 281, 281, 281, 281, 281, 281, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 282, 282, 282, 282, 282, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 283, 283, 283, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 284, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, + 285}; + +static const mz_uint8 s_tdefl_len_extra[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0}; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, + 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17}; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = { + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = { + 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, + 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29}; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = { + 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13}; + +// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted +// values. +typedef struct { + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; +static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, + tdefl_sym_freq *pSyms0, + tdefl_sym_freq *pSyms1) { + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; + tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; + MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { + mz_uint freq = pSyms0[i].m_key; + hist[freq & 0xFF]++; + hist[256 + ((freq >> 8) & 0xFF)]++; + } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) + total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) { + const mz_uint32 *pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { + offsets[i] = cur_ofs; + cur_ofs += pHist[i]; + } + for (i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = + pCur_syms[i]; + { + tdefl_sym_freq *t = pCur_syms; + pCur_syms = pNew_syms; + pNew_syms = t; + } + } + return pCur_syms; +} + +// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, +// alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) { + int root, leaf, next, avbl, used, dpth; + if (n == 0) + return; + else if (n == 1) { + A[0].m_key = 1; + return; + } + A[0].m_key += A[1].m_key; + root = 0; + leaf = 2; + for (next = 1; next < n - 1; next++) { + if (leaf >= n || A[root].m_key < A[leaf].m_key) { + A[next].m_key = A[root].m_key; + A[root++].m_key = (mz_uint16)next; + } else + A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { + A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); + A[root++].m_key = (mz_uint16)next; + } else + A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) + A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; + used = dpth = 0; + root = n - 2; + next = n - 1; + while (avbl > 0) { + while (root >= 0 && (int)A[root].m_key == dpth) { + used++; + root--; + } + while (avbl > used) { + A[next--].m_key = (mz_uint16)(dpth); + avbl--; + } + avbl = 2 * used; + dpth++; + used = 0; + } +} + +// Limits canonical Huffman code table's max code size. +enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, + int code_list_len, + int max_code_size) { + int i; + mz_uint32 total = 0; + if (code_list_len <= 1) + return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) + pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) + total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) + if (pNum_codes[i]) { + pNum_codes[i]--; + pNum_codes[i + 1] += 2; + break; + } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, + int table_len, int code_size_limit, + int static_table) { + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; + mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; + MZ_CLEAR_OBJ(num_codes); + if (static_table) { + for (i = 0; i < table_len; i++) + num_codes[d->m_huff_code_sizes[table_num][i]]++; + } else { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], + *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) + if (pSym_count[i]) { + syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; + syms0[num_used_syms++].m_sym_index = (mz_uint16)i; + } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); + tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, + code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) + d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; + for (j = 0, i = 2; i <= code_size_limit; i++) + next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) { + mz_uint rev_code = 0, code, code_size; + if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) + continue; + code = next_code[code_size]++; + for (l = code_size; l > 0; l--, code >>= 1) + rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) \ + do { \ + mz_uint bits = b; \ + mz_uint len = l; \ + MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); \ + d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ + } \ + MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() \ + { \ + if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)( \ + d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) \ + packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 16; \ + packed_code_sizes[num_packed_code_sizes++] = \ + (mz_uint8)(rle_repeat_count - 3); \ + } \ + rle_repeat_count = 0; \ + } \ + } + +#define TDEFL_RLE_ZERO_CODE_SIZE() \ + { \ + if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = \ + (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ + while (rle_z_count--) \ + packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 17; \ + packed_code_sizes[num_packed_code_sizes++] = \ + (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 18; \ + packed_code_sizes[num_packed_code_sizes++] = \ + (mz_uint8)(rle_z_count - 11); \ + } \ + rle_z_count = 0; \ + } \ + } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) { + int num_lit_codes, num_dist_codes, num_bit_lengths; + mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, + rle_repeat_count, packed_code_sizes_index; + mz_uint8 + code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], + packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], + prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) + if (d->m_huff_code_sizes[0][num_lit_codes - 1]) + break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) + if (d->m_huff_code_sizes[1][num_dist_codes - 1]) + break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], + sizeof(mz_uint8) * num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], + sizeof(mz_uint8) * num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; + num_packed_code_sizes = 0; + rle_z_count = 0; + rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, + sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + } else { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = + (mz_uint16)(d->m_huff_count[2][code_size] + 1); + packed_code_sizes[num_packed_code_sizes++] = code_size; + } else if (++rle_repeat_count == 6) { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { + TDEFL_RLE_PREV_CODE_SIZE(); + } else { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) + if (d->m_huff_code_sizes + [2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) + break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); + TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) + TDEFL_PUT_BITS( + d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; + packed_code_sizes_index < num_packed_code_sizes;) { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; + MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) + TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], + "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) { + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF}; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && \ + MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) \ + { \ + bit_buffer |= (((mz_uint64)(b)) << bits_in); \ + bits_in += (l); \ + } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; + flags >>= 1) { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], + match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], + d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], + s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], + d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], + num_extra_bits); + } else { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], + d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], + d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], + d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64 *)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) { + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; + flags >>= 1) { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], + match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], + d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], + s_tdefl_len_extra[match_len]); + + if (match_dist < 512) { + sym = s_tdefl_small_dist_sym[match_dist]; + num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } else { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; + num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } else { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && + // MINIZ_HAS_64BIT_REGISTERS + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) { + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) { + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = + ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && + (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = + ((d->m_pPut_buf_func == NULL) && + ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) + ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) + : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) { + TDEFL_PUT_BITS(0x78, 8); + TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = + tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || + (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output + // buffer and send a raw block instead. + if (((use_raw_block) || + ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= + d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) { + TDEFL_PUT_BITS( + d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], + 8); + } + } + // Check for the extremely unlikely (if not impossible) case of the compressed + // block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) { + if (flush == TDEFL_FINISH) { + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } else { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, + sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, + sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) { + if (d->m_pPut_buf_func) { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } else if (pOutput_buf_start == d->m_output_buf) { + int bytes_to_copy = (int)MZ_MIN( + (size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, + bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } else { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#define TDEFL_READ_UNALIGNED_WORD(p) ((p)[0] | (p)[1] << 8) +static MZ_FORCEINLINE void +tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, + mz_uint max_match_len, mz_uint *pMatch_dist, + mz_uint *pMatch_len) { + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, + match_len = *pMatch_len, probe_pos = pos, next_probe_pos, + probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), + s01 = *s; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) { + for (;;) { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || \ + ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16 *)(d->m_dict + probe_pos); + if (*q != s01) + continue; + p = s; + probe_len = 32; + do { + } while ((*(++p) == *(++q)) && (*(++p) == *(++q)) && (*(++p) == *(++q)) && + (*(++p) == *(++q)) && (--probe_len > 0)); + if (!probe_len) { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); + break; + } else if ((probe_len = ((mz_uint)(p - s) * 2) + + (mz_uint)(*(const mz_uint8 *)p == + *(const mz_uint8 *)q)) > match_len) { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == + max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void +tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, + mz_uint max_match_len, mz_uint *pMatch_dist, + mz_uint *pMatch_len) { + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, + match_len = *pMatch_len, probe_pos = pos, next_probe_pos, + probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) { + for (;;) { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || \ + ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && \ + (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) { + // Faster, minimally featured LZRW1-style match+parse loop with better + // register utilization. Intended for applications where raw throughput is + // valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, + lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, + total_lz_bytes = d->m_total_lz_bytes, + num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = + (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN( + d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, + MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; + + while (lookahead_size >= 4) { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = + (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & + TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= + dict_size) && + ((mz_uint32)( + *(d->m_dict + (probe_pos & TDEFL_LZ_DICT_SIZE_MASK)) | + (*(d->m_dict + ((probe_pos & TDEFL_LZ_DICT_SIZE_MASK) + 1)) + << 8) | + (*(d->m_dict + ((probe_pos & TDEFL_LZ_DICT_SIZE_MASK) + 2)) + << 16)) == first_trigram)) { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = + (const mz_uint16 *)(d->m_dict + + (probe_pos & TDEFL_LZ_DICT_SIZE_MASK)); + mz_uint32 probe_len = 32; + do { + } while ((*(++p) == *(++q)) && (*(++p) == *(++q)) && + (*(++p) == *(++q)) && (*(++p) == *(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || + ((cur_match_len == TDEFL_MIN_MATCH_LEN) && + (cur_match_dist >= 8U * 1024U))) { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } else { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && + (cur_match_dist >= 1) && + (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - + TDEFL_MIN_MATCH_LEN]]++; + } + } else { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, + mz_uint8 lit) { + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void +tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) { + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && + (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) { + const mz_uint8 *pSrc = d->m_pSrc; + size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to + // TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & + TDEFL_LZ_DICT_SIZE_MASK, + ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] + << TDEFL_LZ_HASH_SHIFT) ^ + d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN( + src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } else { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & + TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] + << (TDEFL_LZ_HASH_SHIFT * 2)) ^ + (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] + << TDEFL_LZ_HASH_SHIFT) ^ + c) & + (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = + MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + // Simple lazy/greedy parsing state machine. + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = + d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } else { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, + d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && + (cur_match_dist >= 8U * 1024U)) || + (cur_pos == cur_match_dist) || + ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) { + if (cur_match_len > d->m_saved_match_len) { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } else { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } else { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } else if (!cur_match_dist) + tdefl_record_literal(d, + d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || + (cur_match_len >= 128)) { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } else { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output + // buffer. + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && + (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= + d->m_total_lz_bytes) || + (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) { + if (d->m_pIn_buf_size) { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, + d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, + d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE + : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, + size_t *pIn_buf_size, void *pOut_buf, + size_t *pOut_buf_size, tdefl_flush flush) { + if (!d) { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == + ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || + (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || + (pIn_buf_size && *pIn_buf_size && !pIn_buf) || + (pOut_buf_size && *pOut_buf_size && !pOut_buf)) { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | + TDEFL_RLE_MATCHES)) == 0)) { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } else +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && + (pIn_buf)) + d->m_adler32 = + (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, + d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && + (!d->m_output_flush_remaining)) { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { + MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_OBJ(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, + size_t in_buf_size, tdefl_flush flush) { + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags) { + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = + d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = + d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; + d->m_pOutput_buf_end = d->m_output_buf; + d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; + d->m_adler32 = 1; + d->m_pIn_buf = NULL; + d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; + d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; + d->m_pSrc = NULL; + d->m_src_buf_left = 0; + d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, + sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, + sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) { + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) { return d->m_adler32; } + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, + tdefl_put_buf_func_ptr pPut_buf_func, + void *pPut_buf_user, int flags) { + tdefl_compressor *pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == + TDEFL_STATUS_OKAY); + succeeded = + succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == + TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; +} + +typedef struct { + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, + void *pUser) { + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) { + size_t new_capacity = p->m_capacity; + mz_uint8 *pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, + size_t *pOut_len, int flags) { + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output( + pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, + const void *pSrc_buf, size_t src_buf_len, + int flags) { + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8 *)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output( + pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return 0; + return out_buf.m_size; +} + +#ifndef MINIZ_NO_ZLIB_APIS +static const mz_uint s_tdefl_num_probes[11] = {0, 1, 6, 32, 16, 32, + 128, 256, 512, 768, 1500}; + +// level may actually range from [0,10] (10 is a "hidden" max level, where we +// want a bit more compression and it's fine if throughput to fall off a cliff +// on some files). +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, + int strategy) { + mz_uint comp_flags = + s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | + ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} +#endif // MINIZ_NO_ZLIB_APIS + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) // nonstandard extension used : non-constant + // aggregate initializer (also supported by GNU + // C and C99, so no big deal) +#endif + +// Simple PNG writer function by Alex Evans, 2011. Released into the public +// domain: https://gist.github.com/908299, more context at +// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. +// This is actually a modification of Alex's original code so PNG files +// generated by this function pass pngcheck. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, + int h, int num_chans, + size_t *pLen_out, + mz_uint level, mz_bool flip) { + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was + // defined. + static const mz_uint s_tdefl_png_num_probes[11] = { + 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500}; + tdefl_compressor *pComp = + (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) { + MZ_FREE(pComp); + return NULL; + } + // write dummy header + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, + s_tdefl_png_num_probes[MZ_MIN(10, level)] | + TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, + (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, + bpl, TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != + TDEFL_STATUS_DONE) { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + // write real header + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; + mz_uint8 pnghdr[41] = {0x89, + 0x50, + 0x4e, + 0x47, + 0x0d, + 0x0a, + 0x1a, + 0x0a, + 0x00, + 0x00, + 0x00, + 0x0d, + 0x49, + 0x48, + 0x44, + 0x52, + 0, + 0, + (mz_uint8)(w >> 8), + (mz_uint8)w, + 0, + 0, + (mz_uint8)(h >> 8), + (mz_uint8)h, + 8, + chans[num_chans], + 0, + 0, + 0, + 0, + 0, + 0, + 0, + (mz_uint8)(*pLen_out >> 24), + (mz_uint8)(*pLen_out >> 16), + (mz_uint8)(*pLen_out >> 8), + (mz_uint8)*pLen_out, + 0x49, + 0x44, + 0x41, + 0x54}; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter( + "\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, + *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, + int num_chans, size_t *pLen_out) { + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we + // can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's + // where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, + pLen_out, 6, MZ_FALSE); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// ------------------- .ZIP archive reading + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include +#include + +#if defined(_MSC_VER) || defined(__MINGW32__) + +#include + +static wchar_t *str2wstr(const char *str) { + int len = strlen(str) + 1; + wchar_t *wstr = (wchar_t*)malloc(len * sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, str, len * sizeof(char), wstr, len); + return wstr; +} + +static FILE *mz_fopen(const char *pFilename, const char *pMode) { + wchar_t *wFilename = str2wstr(pFilename); + wchar_t *wMode = str2wstr(pMode); + FILE *pFile = _wfopen(wFilename, wMode); + + free(wFilename); + free(wMode); + + return pFile; +} + +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) { + wchar_t *wPath = str2wstr(pPath); + wchar_t *wMode = str2wstr(pMode); + FILE *pFile = _wfreopen(wPath, wMode, pStream); + + free(wPath); + free(wMode); + + return pFile; +} + +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +// ZIG_ANDROID_MOD: Zig's mingw64 doesn't have _ftelli64/_fseeki64 +#if defined(__MINGW64__) +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#else +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#endif +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__GNUC__) && _LARGEFILE64_SOURCE +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#else +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FILE FILE +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#else +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#endif +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif // #ifdef _MSC_VER +#endif // #ifdef MINIZ_NO_STDIO + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler +// alignment and platform endian issues, miniz.c doesn't use structs for any of +// this stuff. +enum { + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct { + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag { + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. + */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 + * will also be slammed to true too, even if we didn't find a zip64 end of + * central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write + * helpers. */ + MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) \ + (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) \ + ((element_type *)((array_ptr)->m_p))[index] + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, + mz_zip_array *pArray) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t min_new_capacity, + mz_uint growing) { + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, + pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t new_capacity, + mz_uint growing) { + if (new_capacity > pArray->m_capacity) { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t new_size, + mz_uint growing) { + if (new_size > pArray->m_capacity) { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, + mz_zip_array *pArray, + size_t n) { + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, + mz_zip_array *pArray, + const void *pElements, + size_t n) { + if (0 == n) + return MZ_TRUE; + if (!pElements) + return MZ_FALSE; + + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, + pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) { + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static void mz_zip_time_t_to_dos_time(time_t time, mz_uint16 *pDOS_time, + mz_uint16 *pDOS_date) { +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif /* #ifdef _MSC_VER */ + + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifndef MINIZ_NO_STDIO +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, + time_t *pTime) { + struct MZ_FILE_STAT_STRUCT file_stat; + + /* On Linux with x86 glibc, this call will fail on large files (I think >= + * 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + + *pTime = file_stat.st_mtime; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ + +static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, + time_t modified_time) { + struct utimbuf t; + + memset(&t, 0, sizeof(t)); + t.actime = access_time; + t.modtime = modified_time; + + return !utime(pFilename, &t); +} +#endif /* #ifndef MINIZ_NO_STDIO */ +#endif /* #ifndef MINIZ_NO_TIME */ + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, + mz_zip_error err_num) { + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, + mz_uint32 flags) { + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, + sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, + sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, + sizeof(mz_uint32)); + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool +mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, + const mz_zip_array *pCentral_dir_offsets, + mz_uint l_index, mz_uint r_index) { + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, + l_index)), + *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), + r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central +// directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), +// but it could allocate memory.) +static void +mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT( + &pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) { + int child, root = start; + for (;;) { + if ((child = (root << 1) + 1) >= size) + break; + child += + (((child + 1) < size) && + (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + start--; + } + + end = size - 1; + while (end > 0) { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) { + if ((child = (root << 1) + 1) >= end) + break; + child += + (((child + 1) < end) && + mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, + pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, + mz_uint32 record_sig, + mz_uint32 record_size, + mz_int64 *pOfs) { + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = + MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) { + int i, + n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" + * (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= + (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, + mz_uint flags) { + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, + cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = + ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = + (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first + * 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig( + pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) { + if (pZip->m_pRead(pZip->m_pIO_opaque, + cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, + pZip64_locator, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) { + zip64_end_of_central_dir_ofs = MZ_READ_LE64( + pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > + (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, + pZip64_end_of_central_dir, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = + MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) { + mz_uint32 zip64_total_num_of_disks = + MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < + (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = + (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough + * for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && + ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) { + mz_uint i, n; + /* Read the entire central directory into a heap block, and allocate another + * heap block to hold the unsorted central dir file record offsets, and + * possibly another to hold the sorted indices. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, + MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, + pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) { + if (!mz_zip_array_resize(pZip, + &pZip->m_pState->m_sorted_central_dir_offsets, + pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, + pZip->m_pState->m_central_dir.m_p, + cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some + * basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { + mz_uint total_header_size, disk_index, bit_flags, filename_size, + ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || + (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, + i) = + (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, + mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == + MZ_UINT32_MAX)) { + /* Attempt to find zip64 extended information field in the entry's extra + * data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) { + const mz_uint8 *pExtra_data; + void *buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > + n) { + buf = MZ_MALLOC(ext_data_size); + if (buf == NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, + cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + filename_size, + buf, ext_data_size) != ext_data_size) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8 *)buf; + } else { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > + extra_size_remaining) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { + /* Ok, the archive didn't have any zip64 headers but it uses a + * zip64 extended information field so mark it as zip64 anyway + * (this can occur with infozip's zip util when it reads + * compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = + extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext + * data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && + (decomp_size != comp_size)) || + (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || + ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > + n) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, + mz_uint32 flags) { + if ((!pZip) || (!pZip->m_pRead)) + return MZ_FALSE; + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, + void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) + ? 0 + : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, + size_t size, mz_uint32 flags) { + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, + void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || + (((cur_ofs != (mz_int64)file_ofs)) && + (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint32 flags) { + mz_uint64 file_size; + MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return MZ_FALSE; + if (MZ_FSEEK64(pFile, 0, SEEK_END)) { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + file_size = MZ_FTELL64(pFile); + if (!mz_zip_reader_init_internal(pZip, flags)) { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) { + return pZip ? pZip->m_total_files : 0; +} + +static MZ_FORCEINLINE const mz_uint8 * +mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) { + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, + file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, + mz_uint file_index) { + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, + mz_uint file_index) { + mz_uint filename_len, external_attr; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, + // which wasn't correct. Most/all zip writers (hopefully) set DOS + // file/directory attributes in the low 16-bits, so check for the DOS + // directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; + + return MZ_FALSE; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, + mz_zip_archive_file_stat *pStat) { + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = + mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), + MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, + p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), + n); + pStat->m_comment[n] = '\0'; + + return MZ_TRUE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, + char *pFilename, mz_uint filename_buf_size) { + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { + if (filename_buf_size) + pFilename[0] = '\0'; + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, + const char *pB, + mz_uint len, + mz_uint flags) { + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int +mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, + const mz_zip_array *pCentral_dir_offsets, + mz_uint l_index, const char *pR, mz_uint r_len) { + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT( + pCentral_dir_array, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, + l_index)), + *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, + const char *pFilename) { + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT( + &pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) { + int m = (l + h) >> 1, file_index = pIndices[m], + comp = + mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, + file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, + const char *pComment, mz_uint flags) { + mz_uint file_index; + size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && + (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); + if (name_len > 0xFFFF) + return -1; + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > 0xFFFF) + return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, + file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = + (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), + file_comment_len = + MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || + (!mz_zip_reader_string_equal(pComment, pFile_comment, + file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) { + int ofs = filename_len - 1; + do { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || + (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && + (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + return -1; +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, + mz_uint file_index, void *pBuf, + size_t buf_size, mz_uint flags, + void *pUser_read_buf, + size_t user_read_buf_size) { + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, + out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 + local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return MZ_FALSE; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips + // with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have + // compressed deflate data which inflates to 0 bytes, but these entries claim + // to uncompress to 512 bytes in the headers). I'm torn how to handle this + // case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && + (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size + : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, + (size_t)needed_size) != needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || + (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, + (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input + // buffer. + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } else if (pUser_read_buf) { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } else { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + return MZ_FALSE; + + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do { + size_t in_buf_size, + out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress( + &inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, + (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | + (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || + (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, + (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc( + mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, + mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, + flags, pUser_read_buf, + user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, + void *pBuf, size_t buf_size, + mz_uint flags) { + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, + flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, + const char *pFilename, void *pBuf, + size_t buf_size, mz_uint flags) { + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, + buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, + size_t *pSize, mz_uint flags) { + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + return NULL; + if (NULL == + (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, + flags)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, + const char *pFilename, size_t *pSize, + mz_uint flags) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, + mz_uint file_index, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags) { + int status = TINFL_STATUS_DONE; + mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, + out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; + void *pWrite_buf = NULL; + mz_uint32 + local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips + // with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have + // compressed deflate data which inflates to 0 bytes, but these entries claim + // to uncompress to 512 bytes in the headers). I'm torn how to handle this + // case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && + (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + // Decompress the file either directly from memory or from a file input + // buffer. + if (pZip->m_pState->m_pMem) { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } else { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) { + if (((sizeof(size_t) == sizeof(mz_uint32))) && + (file_stat.m_comp_size > 0xFFFFFFFF)) + return MZ_FALSE; + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, + (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = + (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, + (size_t)file_stat.m_comp_size); + // cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + // comp_remaining = 0; + } else { + while (comp_remaining) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32( + file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } else { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + TINFL_LZ_DICT_SIZE))) + status = TINFL_STATUS_FAILED; + else { + do { + mz_uint8 *pWrite_buf_cur = + (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, + out_buf_size = + TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, + (size_t)read_buf_avail) != read_buf_avail) { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress( + &inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, + (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, + comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != + out_buf_size) { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = + (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || + (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && + (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || + (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, + const char *pFilename, + mz_file_write_func pCallback, + void *pOpaque, mz_uint flags) { + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, + flags); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, + const void *pBuf, size_t n) { + (void)ofs; + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, + const char *pDst_filename, + mz_uint flags) { + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback( + pZip, file_index, mz_zip_file_write_callback, pFile, flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; +#ifndef MINIZ_NO_TIME + if (status) { + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); + } +#endif + + return status; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) { + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || + (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, + const char *pArchive_filename, + const char *pDst_filename, + mz_uint flags) { + int file_index = + mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} +#endif + +// ------------------- .ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) { + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || + (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (pZip->m_file_offset_alignment) { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, + sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, + sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, + sizeof(mz_uint32)); + return MZ_TRUE; +} + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, + const void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); + + if ((!n) || + ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) + return 0; + + if (new_size > pState->m_mem_capacity) { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + while (new_capacity < new_size) + new_capacity *= 2; + if (NULL == (pNew_block = pZip->m_pRealloc( + pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, + size_t size_to_reserve_at_beginning, + size_t initial_allocation_size) { + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, + size_to_reserve_at_beginning))) { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, initial_allocation_size))) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, + const void *pBuf, size_t n) { + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || + (((cur_ofs != (mz_int64)file_ofs)) && + (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, + mz_uint64 size_to_reserve_at_beginning) { + MZ_FILE *pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) { + mz_uint64 cur_ofs = 0; + char buf[4096]; + MZ_CLEAR_OBJ(buf); + do { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, + const char *pFilename) { + mz_zip_internal_state *pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max + // size + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if (pState->m_pFile) { +#ifdef MINIZ_NO_STDIO + pFilename; + return MZ_FALSE; +#else + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == + (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is + // NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } +#endif // #ifdef MINIZ_NO_STDIO + } else if (pState->m_pMem) { + // Archive lives in a memory block. Assume it's from the heap that we can + // resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the + // user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; + + // Start writing new files at the archive's current central directory + // location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, + const void *pBuf, size_t buf_size, + mz_uint level_and_flags) { + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, + level_and_flags, 0, 0); +} + +typedef struct { + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, + void *pUser) { + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, + pState->m_cur_archive_file_ofs, pBuf, + len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_local_dir_header( + mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, + mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, + mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, + mz_uint16 dos_time, mz_uint16 dos_date) { + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header( + mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, + mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, + mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, + mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) { + (void)pZip; + mz_uint16 version_made_by = 10 * MZ_VER_MAJOR + MZ_VER_MINOR; + version_made_by |= (MZ_PLATFORM << 8); + + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_MADE_BY_OFS, version_made_by); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir( + mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, + const void *pExtra, mz_uint16 extra_size, const void *pComment, + mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, + mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, + mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, + mz_uint32 ext_attributes) { + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || + (((mz_uint64)pState->m_central_dir.m_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_central_dir_header( + pZip, central_dir_header, filename_size, extra_size, comment_size, + uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, + dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, + filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, + extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, + comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, + ¢ral_dir_ofs, 1))) { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, + MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) { + // Basic ZIP archive filename validity checks: Valid filenames cannot start + // with a forward slash, cannot contain a drive letter, and cannot use + // DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; +} + +static mz_uint +mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) { + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & + (pZip->m_file_offset_alignment - 1); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, + mz_uint64 cur_file_ofs, mz_uint32 n) { + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, + const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, + mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, + mz_uint32 uncomp_crc32) { + mz_uint32 ext_attributes = 0; + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = + ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || + (!pArchive_name) || ((comment_size) && (!pComment)) || + (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + local_dir_header_ofs = cur_archive_file_ofs = pZip->m_archive_size; + pState = pZip->m_pState; + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + { + time_t cur_time; + time(&cur_time); + mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif // #ifndef MINIZ_NO_TIME + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } + + // Try to do any allocations before writing to the archive, so if an + // allocation fails the file remains unmodified. (A good idea if we're doing + // an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + archive_name_size + comment_size)) || + (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; + + if ((!store_data_uncompressed) && (buf_size)) { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return MZ_FALSE; + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, + num_alignment_padding_bytes + + sizeof(local_dir_header))) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == + 0); + } + cur_archive_file_ofs += + num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, + archive_name_size) != archive_name_size) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) { + uncomp_crc32 = + (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + if (store_data_uncompressed) { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, + buf_size) != buf_size) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } else if (buf_size) { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, + tdefl_create_comp_flags_from_zip_params( + level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != + TDEFL_STATUS_DONE)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header( + pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, + comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, + sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir( + pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, + dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, + const char *pSrc_filename, const void *pComment, + mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint32 ext_attributes) { + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0; +#ifndef MINIZ_NO_TIME + time_t file_modified_time; +#endif + + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs, uncomp_size = 0, + comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || + ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + local_dir_header_ofs = cur_archive_file_ofs = pZip->m_archive_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + memset(&file_modified_time, 0, sizeof(file_modified_time)); + if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) + return MZ_FALSE; + mz_zip_time_t_to_dos_time(file_modified_time, &dos_time, &dos_date); +#endif + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + if (uncomp_size > 0xFFFFFFFF) { + // No zip64 support yet + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, + num_alignment_padding_bytes + + sizeof(local_dir_header))) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == + 0); + } + cur_archive_file_ofs += + num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, + archive_name_size) != archive_name_size) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = + pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + if (!level) { + while (uncomp_remaining) { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || + (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, + n) != n)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = + (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } else { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc( + pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, + tdefl_create_comp_flags_from_zip_params( + level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + for (;;) { + size_t in_buf_size = + (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; + + uncomp_crc32 = (mz_uint32)mz_crc32( + uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, + uncomp_remaining ? TDEFL_NO_FLUSH + : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) { + result = MZ_TRUE; + break; + } else if (status != TDEFL_STATUS_OKAY) + break; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + MZ_FCLOSE(pSrc_file); + pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header( + pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, + comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, + sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir( + pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, + dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, + mz_zip_archive *pSource_zip, + mz_uint file_index) { + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 + local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; + const mz_uint8 *pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == + (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || + ((pZip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > + 0xFFFFFFFF)) + return MZ_FALSE; + + cur_src_file_ofs = + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, + pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, + num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == + 0); + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = + n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + + if (NULL == + (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, + (size_t)MZ_MAX(sizeof(mz_uint32) * 4, + MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, + comp_bytes_remaining))))) + return MZ_FALSE; + + while (comp_bytes_remaining) { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, + n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; + } + + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, + sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + // cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; + + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, + local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; + + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back( + pZip, &pState->m_central_dir, + pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, + MZ_FALSE); + return MZ_FALSE; + } + + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, + MZ_FALSE); + return MZ_FALSE; + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) { + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + + pState = pZip->m_pState; + + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || + ((pZip->m_archive_size + pState->m_central_dir.m_size + + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, + pState->m_central_dir.m_p, + (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, + pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, + sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_archive_size += sizeof(hdr); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, + size_t *pSize) { + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) { + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || + ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && + (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place( + const char *pZip_filename, const char *pArchive_name, const void *pBuf, + size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags) { + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || + ((comment_size) && (!pComment)) || + ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } else { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, + level_and_flags | + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } + } + status = + mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, + pComment, comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid + // central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, + const char *pArchive_name, + size_t *pSize, mz_uint flags) { + int file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + return NULL; + + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, + flags | + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; + + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, + flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + + mz_zip_reader_end(&zip_archive); + return p; +} + +#endif // #ifndef MINIZ_NO_STDIO + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_FILE_ONLY + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ diff --git a/test/standalone/issue_9812/vendor/kuba-zip/zip.c b/test/standalone/issue_9812/vendor/kuba-zip/zip.c new file mode 100644 index 0000000000..3e648995c1 --- /dev/null +++ b/test/standalone/issue_9812/vendor/kuba-zip/zip.c @@ -0,0 +1,1622 @@ +/* + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ +#define __STDC_WANT_LIB_EXT1__ 1 + +#include +#include +#include + +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +/* Win32, DOS, MSVC, MSVS */ +#include + +#define MKDIR(DIRNAME) _mkdir(DIRNAME) +#define STRCLONE(STR) ((STR) ? _strdup(STR) : NULL) +#define HAS_DEVICE(P) \ + ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) && \ + (P)[1] == ':') +#define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE(P) ? 2 : 0) + +#else + +#include // needed for symlink() + +#define MKDIR(DIRNAME) mkdir(DIRNAME, 0755) +#define STRCLONE(STR) ((STR) ? strdup(STR) : NULL) + +#endif + +#ifdef __MINGW32__ +#include +#include +#endif + +#include "miniz.h" +#include "zip.h" + +#ifdef _MSC_VER +#include + +#define ftruncate(fd, sz) (-(_chsize_s((fd), (sz)) != 0)) +#define fileno _fileno +#endif + +#ifndef HAS_DEVICE +#define HAS_DEVICE(P) 0 +#endif + +#ifndef FILESYSTEM_PREFIX_LEN +#define FILESYSTEM_PREFIX_LEN(P) 0 +#endif + +#ifndef ISSLASH +#define ISSLASH(C) ((C) == '/' || (C) == '\\') +#endif + +#define CLEANUP(ptr) \ + do { \ + if (ptr) { \ + free((void *)ptr); \ + ptr = NULL; \ + } \ + } while (0) + +struct zip_entry_t { + int index; + char *name; + mz_uint64 uncomp_size; + mz_uint64 comp_size; + mz_uint32 uncomp_crc32; + mz_uint64 offset; + mz_uint8 header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint64 header_offset; + mz_uint16 method; + mz_zip_writer_add_state state; + tdefl_compressor comp; + mz_uint32 external_attr; + time_t m_time; +}; + +struct zip_t { + mz_zip_archive archive; + mz_uint level; + struct zip_entry_t entry; +}; + +enum zip_modify_t { + MZ_KEEP = 0, + MZ_DELETE = 1, + MZ_MOVE = 2, +}; + +struct zip_entry_mark_t { + int file_index; + enum zip_modify_t type; + mz_uint64 m_local_header_ofs; + mz_uint64 lf_length; +}; + +static const char *const zip_errlist[30] = { + NULL, + "not initialized\0", + "invalid entry name\0", + "entry not found\0", + "invalid zip mode\0", + "invalid compression level\0", + "no zip 64 support\0", + "memset error\0", + "cannot write data to entry\0", + "cannot initialize tdefl compressor\0", + "invalid index\0", + "header not found\0", + "cannot flush tdefl buffer\0", + "cannot write entry header\0", + "cannot create entry header\0", + "cannot write to central dir\0", + "cannot open file\0", + "invalid entry type\0", + "extracting data using no memory allocation\0", + "file not found\0", + "no permission\0", + "out of memory\0", + "invalid zip archive name\0", + "make dir error\0", + "symlink error\0", + "close archive error\0", + "capacity size too small\0", + "fseek error\0", + "fread error\0", + "fwrite error\0", +}; + +const char *zip_strerror(int errnum) { + errnum = -errnum; + if (errnum <= 0 || errnum >= 30) { + return NULL; + } + + return zip_errlist[errnum]; +} + +static const char *zip_basename(const char *name) { + char const *p; + char const *base = name += FILESYSTEM_PREFIX_LEN(name); + int all_slashes = 1; + + for (p = name; *p; p++) { + if (ISSLASH(*p)) + base = p + 1; + else + all_slashes = 0; + } + + /* If NAME is all slashes, arrange to return `/'. */ + if (*base == '\0' && ISSLASH(*name) && all_slashes) + --base; + + return base; +} + +static int zip_mkpath(char *path) { + char *p; + char npath[MAX_PATH + 1]; + int len = 0; + int has_device = HAS_DEVICE(path); + + memset(npath, 0, MAX_PATH + 1); + if (has_device) { + // only on windows + npath[0] = path[0]; + npath[1] = path[1]; + len = 2; + } + for (p = path + len; *p && len < MAX_PATH; p++) { + if (ISSLASH(*p) && ((!has_device && len > 0) || (has_device && len > 2))) { +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +#else + if ('\\' == *p) { + *p = '/'; + } +#endif + + if (MKDIR(npath) == -1) { + if (errno != EEXIST) { + return ZIP_EMKDIR; + } + } + } + npath[len++] = *p; + } + + return 0; +} + +static char *zip_strrpl(const char *str, size_t n, char oldchar, char newchar) { + char c; + size_t i; + char *rpl = (char *)calloc((1 + n), sizeof(char)); + char *begin = rpl; + if (!rpl) { + return NULL; + } + + for (i = 0; (i < n) && (c = *str++); ++i) { + if (c == oldchar) { + c = newchar; + } + *rpl++ = c; + } + + return begin; +} + +static char *zip_name_normalize(char *name, char *const nname, size_t len) { + size_t offn = 0; + size_t offnn = 0, ncpy = 0; + + if (name == NULL || nname == NULL || len <= 0) { + return NULL; + } + // skip trailing '/' + while (ISSLASH(*name)) + name++; + + for (; offn < len; offn++) { + if (ISSLASH(name[offn])) { + if (ncpy > 0 && strcmp(&nname[offnn], ".\0") && + strcmp(&nname[offnn], "..\0")) { + offnn += ncpy; + nname[offnn++] = name[offn]; // append '/' + } + ncpy = 0; + } else { + nname[offnn + ncpy] = name[offn]; + ncpy++; + } + } + + // at the end, extra check what we've already copied + if (ncpy == 0 || !strcmp(&nname[offnn], ".\0") || + !strcmp(&nname[offnn], "..\0")) { + nname[offnn] = 0; + } + return nname; +} + +static mz_bool zip_name_match(const char *name1, const char *name2) { + int len2 = strlen(name2); + char *nname2 = zip_strrpl(name2, len2, '\\', '/'); + if (!nname2) { + return MZ_FALSE; + } + + mz_bool res = (strcmp(name1, nname2) == 0) ? MZ_TRUE : MZ_FALSE; + CLEANUP(nname2); + return res; +} + +static int zip_archive_truncate(mz_zip_archive *pzip) { + mz_zip_internal_state *pState = pzip->m_pState; + mz_uint64 file_size = pzip->m_archive_size; + if ((pzip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) { + return 0; + } + if (pzip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED) { + if (pState->m_pFile) { + int fd = fileno(pState->m_pFile); + return ftruncate(fd, file_size); + } + } + return 0; +} + +static int zip_archive_extract(mz_zip_archive *zip_archive, const char *dir, + int (*on_extract)(const char *filename, + void *arg), + void *arg) { + int err = 0; + mz_uint i, n; + char path[MAX_PATH + 1]; + char symlink_to[MAX_PATH + 1]; + mz_zip_archive_file_stat info; + size_t dirlen = 0; + mz_uint32 xattr = 0; + + memset(path, 0, sizeof(path)); + memset(symlink_to, 0, sizeof(symlink_to)); + + dirlen = strlen(dir); + if (dirlen + 1 > MAX_PATH) { + return ZIP_EINVENTNAME; + } + + memset((void *)&info, 0, sizeof(mz_zip_archive_file_stat)); + +#if defined(_MSC_VER) + strcpy_s(path, MAX_PATH, dir); +#else + strcpy(path, dir); +#endif + + if (!ISSLASH(path[dirlen - 1])) { +#if defined(_WIN32) || defined(__WIN32__) + path[dirlen] = '\\'; +#else + path[dirlen] = '/'; +#endif + ++dirlen; + } + + // Get and print information about each file in the archive. + n = mz_zip_reader_get_num_files(zip_archive); + for (i = 0; i < n; ++i) { + if (!mz_zip_reader_file_stat(zip_archive, i, &info)) { + // Cannot get information about zip archive; + err = ZIP_ENOENT; + goto out; + } + + if (!zip_name_normalize(info.m_filename, info.m_filename, + strlen(info.m_filename))) { + // Cannot normalize file name; + err = ZIP_EINVENTNAME; + goto out; + } +#if defined(_MSC_VER) + strncpy_s(&path[dirlen], MAX_PATH - dirlen, info.m_filename, + MAX_PATH - dirlen); +#else + strncpy(&path[dirlen], info.m_filename, MAX_PATH - dirlen); +#endif + err = zip_mkpath(path); + if (err < 0) { + // Cannot make a path + goto out; + } + + if ((((info.m_version_made_by >> 8) == 3) || + ((info.m_version_made_by >> 8) == + 19)) // if zip is produced on Unix or macOS (3 and 19 from + // section 4.4.2.2 of zip standard) + && info.m_external_attr & + (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 + // is directory) +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +#else + if (info.m_uncomp_size > MAX_PATH || + !mz_zip_reader_extract_to_mem_no_alloc(zip_archive, i, symlink_to, + MAX_PATH, 0, NULL, 0)) { + err = ZIP_EMEMNOALLOC; + goto out; + } + symlink_to[info.m_uncomp_size] = '\0'; + if (symlink(symlink_to, path) != 0) { + err = ZIP_ESYMLINK; + goto out; + } +#endif + } else { + if (!mz_zip_reader_is_file_a_directory(zip_archive, i)) { + if (!mz_zip_reader_extract_to_file(zip_archive, i, path, 0)) { + // Cannot extract zip archive to file + err = ZIP_ENOFILE; + goto out; + } + } + +#if defined(_MSC_VER) + (void)xattr; // unused +#else + xattr = (info.m_external_attr >> 16) & 0xFFFF; + if (xattr > 0) { + if (chmod(path, (mode_t)xattr) < 0) { + err = ZIP_ENOPERM; + goto out; + } + } +#endif + } + + if (on_extract) { + if (on_extract(path, arg) < 0) { + goto out; + } + } + } + +out: + // Close the archive, freeing any resources it was using + if (!mz_zip_reader_end(zip_archive)) { + // Cannot end zip reader + err = ZIP_ECLSZIP; + } + return err; +} + +static inline void zip_archive_finalize(mz_zip_archive *pzip) { + mz_zip_writer_finalize_archive(pzip); + zip_archive_truncate(pzip); +} + +static int zip_entry_mark(struct zip_t *zip, + struct zip_entry_mark_t *entry_mark, int n, + char *const entries[], const size_t len) { + int err = 0; + if (!zip || !entry_mark || !entries) { + return ZIP_ENOINIT; + } + + mz_zip_archive_file_stat file_stat; + mz_uint64 d_pos = ~0; + for (int i = 0; i < n; ++i) { + + if ((err = zip_entry_openbyindex(zip, i))) { + return err; + } + + mz_bool name_matches = MZ_FALSE; + for (int j = 0; j < (const int)len; ++j) { + if (zip_name_match(zip->entry.name, entries[j])) { + name_matches = MZ_TRUE; + break; + } + } + if (name_matches) { + entry_mark[i].type = MZ_DELETE; + } else { + entry_mark[i].type = MZ_KEEP; + } + + if (!mz_zip_reader_file_stat(&zip->archive, i, &file_stat)) { + return ZIP_ENOENT; + } + + zip_entry_close(zip); + + entry_mark[i].m_local_header_ofs = file_stat.m_local_header_ofs; + entry_mark[i].file_index = -1; + entry_mark[i].lf_length = 0; + if ((entry_mark[i].type) == MZ_DELETE && + (d_pos > entry_mark[i].m_local_header_ofs)) { + d_pos = entry_mark[i].m_local_header_ofs; + } + } + for (int i = 0; i < n; ++i) { + if ((entry_mark[i].m_local_header_ofs > d_pos) && + (entry_mark[i].type != MZ_DELETE)) { + entry_mark[i].type = MZ_MOVE; + } + } + return err; +} + +static int zip_index_next(mz_uint64 *local_header_ofs_array, int cur_index) { + int new_index = 0; + for (int i = cur_index - 1; i >= 0; --i) { + if (local_header_ofs_array[cur_index] > local_header_ofs_array[i]) { + new_index = i + 1; + return new_index; + } + } + return new_index; +} + +static int zip_sort(mz_uint64 *local_header_ofs_array, int cur_index) { + int nxt_index = zip_index_next(local_header_ofs_array, cur_index); + + if (nxt_index != cur_index) { + mz_uint64 temp = local_header_ofs_array[cur_index]; + for (int i = cur_index; i > nxt_index; i--) { + local_header_ofs_array[i] = local_header_ofs_array[i - 1]; + } + local_header_ofs_array[nxt_index] = temp; + } + return nxt_index; +} + +static int zip_index_update(struct zip_entry_mark_t *entry_mark, int last_index, + int nxt_index) { + for (int j = 0; j < last_index; j++) { + if (entry_mark[j].file_index >= nxt_index) { + entry_mark[j].file_index += 1; + } + } + entry_mark[nxt_index].file_index = last_index; + return 0; +} + +static int zip_entry_finalize(struct zip_t *zip, + struct zip_entry_mark_t *entry_mark, + const int n) { + + mz_uint64 *local_header_ofs_array = (mz_uint64 *)calloc(n, sizeof(mz_uint64)); + if (!local_header_ofs_array) { + return ZIP_EOOMEM; + } + + for (int i = 0; i < n; ++i) { + local_header_ofs_array[i] = entry_mark[i].m_local_header_ofs; + int index = zip_sort(local_header_ofs_array, i); + + if (index != i) { + zip_index_update(entry_mark, i, index); + } + entry_mark[i].file_index = index; + } + + mz_uint64 *length = (mz_uint64 *)calloc(n, sizeof(mz_uint64)); + if (!length) { + CLEANUP(local_header_ofs_array); + return ZIP_EOOMEM; + } + for (int i = 0; i < n - 1; i++) { + length[i] = local_header_ofs_array[i + 1] - local_header_ofs_array[i]; + } + length[n - 1] = zip->archive.m_archive_size - local_header_ofs_array[n - 1]; + + for (int i = 0; i < n; i++) { + entry_mark[i].lf_length = length[entry_mark[i].file_index]; + } + + CLEANUP(length); + CLEANUP(local_header_ofs_array); + return 0; +} + +static int zip_entry_set(struct zip_t *zip, struct zip_entry_mark_t *entry_mark, + int n, char *const entries[], const size_t len) { + int err = 0; + + if ((err = zip_entry_mark(zip, entry_mark, n, entries, len)) < 0) { + return err; + } + if ((err = zip_entry_finalize(zip, entry_mark, n)) < 0) { + return err; + } + return 0; +} + +static mz_int64 zip_file_move(MZ_FILE *m_pFile, const mz_uint64 to, + const mz_uint64 from, const mz_uint64 length, + mz_uint8 *move_buf, + const mz_int64 capacity_size) { + if ((mz_int64)length > capacity_size) { + return ZIP_ECAPSIZE; + } + if (MZ_FSEEK64(m_pFile, from, SEEK_SET)) { + MZ_FCLOSE(m_pFile); + return ZIP_EFSEEK; + } + + if (fread(move_buf, 1, length, m_pFile) != length) { + MZ_FCLOSE(m_pFile); + return ZIP_EFREAD; + } + if (MZ_FSEEK64(m_pFile, to, SEEK_SET)) { + MZ_FCLOSE(m_pFile); + return ZIP_EFSEEK; + } + if (fwrite(move_buf, 1, length, m_pFile) != length) { + MZ_FCLOSE(m_pFile); + return ZIP_EFWRITE; + } + return (mz_int64)length; +} + +static mz_int64 zip_files_move(MZ_FILE *m_pFile, mz_uint64 writen_num, + mz_uint64 read_num, mz_uint64 length) { + int n = 0; + const mz_int64 page_size = 1 << 12; // 4K + mz_uint8 *move_buf = (mz_uint8 *)calloc(1, page_size); + if (move_buf == NULL) { + return ZIP_EOOMEM; + } + + mz_int64 moved_length = 0; + mz_int64 move_count = 0; + while ((mz_int64)length > 0) { + move_count = ((mz_int64)length >= page_size) ? page_size : (mz_int64)length; + n = zip_file_move(m_pFile, writen_num, read_num, move_count, move_buf, + page_size); + if (n < 0) { + moved_length = n; + goto cleanup; + } + + if (n != move_count) { + goto cleanup; + } + + writen_num += move_count; + read_num += move_count; + length -= move_count; + moved_length += move_count; + } + +cleanup: + CLEANUP(move_buf); + return moved_length; +} + +static int zip_central_dir_move(mz_zip_internal_state *pState, int begin, + int end, int entry_num) { + if (begin == entry_num) { + return 0; + } + + mz_uint64 l_size = 0; + mz_uint64 r_size = 0; + mz_uint64 d_size = 0; + mz_uint8 *next = NULL; + mz_uint8 *deleted = &MZ_ZIP_ARRAY_ELEMENT( + &pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pState->m_central_dir_offsets, mz_uint32, begin)); + l_size = (mz_uint32)(deleted - (mz_uint8 *)(pState->m_central_dir.m_p)); + if (end == entry_num) { + r_size = 0; + } else { + next = &MZ_ZIP_ARRAY_ELEMENT( + &pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pState->m_central_dir_offsets, mz_uint32, end)); + r_size = pState->m_central_dir.m_size - + (mz_uint32)(next - (mz_uint8 *)(pState->m_central_dir.m_p)); + d_size = next - deleted; + } + + if (l_size == 0) { + memmove(pState->m_central_dir.m_p, next, r_size); + pState->m_central_dir.m_p = MZ_REALLOC(pState->m_central_dir.m_p, r_size); + for (int i = end; i < entry_num; i++) { + MZ_ZIP_ARRAY_ELEMENT(&pState->m_central_dir_offsets, mz_uint32, i) -= + d_size; + } + } + + if (l_size * r_size != 0) { + memmove(deleted, next, r_size); + for (int i = end; i < entry_num; i++) { + MZ_ZIP_ARRAY_ELEMENT(&pState->m_central_dir_offsets, mz_uint32, i) -= + d_size; + } + } + + pState->m_central_dir.m_size = l_size + r_size; + return 0; +} + +static int zip_central_dir_delete(mz_zip_internal_state *pState, + int *deleted_entry_index_array, + int entry_num) { + int i = 0; + int begin = 0; + int end = 0; + int d_num = 0; + while (i < entry_num) { + while ((!deleted_entry_index_array[i]) && (i < entry_num)) { + i++; + } + begin = i; + + while ((deleted_entry_index_array[i]) && (i < entry_num)) { + i++; + } + end = i; + zip_central_dir_move(pState, begin, end, entry_num); + } + + i = 0; + while (i < entry_num) { + while ((!deleted_entry_index_array[i]) && (i < entry_num)) { + i++; + } + begin = i; + if (begin == entry_num) { + break; + } + while ((deleted_entry_index_array[i]) && (i < entry_num)) { + i++; + } + end = i; + int k = 0; + for (int j = end; j < entry_num; j++) { + MZ_ZIP_ARRAY_ELEMENT(&pState->m_central_dir_offsets, mz_uint32, + begin + k) = + (mz_uint32)MZ_ZIP_ARRAY_ELEMENT(&pState->m_central_dir_offsets, + mz_uint32, j); + k++; + } + d_num += end - begin; + } + + pState->m_central_dir_offsets.m_size = + sizeof(mz_uint32) * (entry_num - d_num); + return 0; +} + +static int zip_entries_delete_mark(struct zip_t *zip, + struct zip_entry_mark_t *entry_mark, + int entry_num) { + mz_uint64 writen_num = 0; + mz_uint64 read_num = 0; + mz_uint64 deleted_length = 0; + mz_uint64 move_length = 0; + int i = 0; + int deleted_entry_num = 0; + int n = 0; + + mz_bool *deleted_entry_flag_array = + (mz_bool *)calloc(entry_num, sizeof(mz_bool)); + if (deleted_entry_flag_array == NULL) { + return ZIP_EOOMEM; + } + + mz_zip_internal_state *pState = zip->archive.m_pState; + zip->archive.m_zip_mode = MZ_ZIP_MODE_WRITING; + + if (MZ_FSEEK64(pState->m_pFile, 0, SEEK_SET)) { + CLEANUP(deleted_entry_flag_array); + return ZIP_ENOENT; + } + + while (i < entry_num) { + while ((entry_mark[i].type == MZ_KEEP) && (i < entry_num)) { + writen_num += entry_mark[i].lf_length; + read_num = writen_num; + i++; + } + + while ((entry_mark[i].type == MZ_DELETE) && (i < entry_num)) { + deleted_entry_flag_array[i] = MZ_TRUE; + read_num += entry_mark[i].lf_length; + deleted_length += entry_mark[i].lf_length; + i++; + deleted_entry_num++; + } + + while ((entry_mark[i].type == MZ_MOVE) && (i < entry_num)) { + move_length += entry_mark[i].lf_length; + mz_uint8 *p = &MZ_ZIP_ARRAY_ELEMENT( + &pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pState->m_central_dir_offsets, mz_uint32, i)); + if (!p) { + CLEANUP(deleted_entry_flag_array); + return ZIP_ENOENT; + } + mz_uint32 offset = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + offset -= (mz_uint32)deleted_length; + MZ_WRITE_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS, offset); + i++; + } + + n = zip_files_move(pState->m_pFile, writen_num, read_num, move_length); + if (n != (mz_int64)move_length) { + CLEANUP(deleted_entry_flag_array); + return n; + } + writen_num += move_length; + read_num += move_length; + } + + zip->archive.m_archive_size -= deleted_length; + zip->archive.m_total_files = entry_num - deleted_entry_num; + + zip_central_dir_delete(pState, deleted_entry_flag_array, entry_num); + CLEANUP(deleted_entry_flag_array); + + return deleted_entry_num; +} + +struct zip_t *zip_open(const char *zipname, int level, char mode) { + struct zip_t *zip = NULL; + + if (!zipname || strlen(zipname) < 1) { + // zip_t archive name is empty or NULL + goto cleanup; + } + + if (level < 0) + level = MZ_DEFAULT_LEVEL; + if ((level & 0xF) > MZ_UBER_COMPRESSION) { + // Wrong compression level + goto cleanup; + } + + zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t)); + if (!zip) + goto cleanup; + + zip->level = (mz_uint)level; + switch (mode) { + case 'w': + // Create a new archive. + if (!mz_zip_writer_init_file(&(zip->archive), zipname, 0)) { + // Cannot initialize zip_archive writer + goto cleanup; + } + break; + + case 'r': + case 'a': + case 'd': + if (!mz_zip_reader_init_file( + &(zip->archive), zipname, + zip->level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) { + // An archive file does not exist or cannot initialize + // zip_archive reader + goto cleanup; + } + if ((mode == 'a' || mode == 'd') && + !mz_zip_writer_init_from_reader(&(zip->archive), zipname)) { + mz_zip_reader_end(&(zip->archive)); + goto cleanup; + } + break; + + default: + goto cleanup; + } + + return zip; + +cleanup: + CLEANUP(zip); + return NULL; +} + +void zip_close(struct zip_t *zip) { + if (zip) { + // Always finalize, even if adding failed for some reason, so we have a + // valid central directory. + mz_zip_writer_finalize_archive(&(zip->archive)); + zip_archive_truncate(&(zip->archive)); + mz_zip_writer_end(&(zip->archive)); + mz_zip_reader_end(&(zip->archive)); + + CLEANUP(zip); + } +} + +int zip_is64(struct zip_t *zip) { + if (!zip || !zip->archive.m_pState) { + // zip_t handler or zip state is not initialized + return ZIP_ENOINIT; + } + + return (int)zip->archive.m_pState->m_zip64; +} + +int zip_entry_open(struct zip_t *zip, const char *entryname) { + size_t entrylen = 0; + mz_zip_archive *pzip = NULL; + mz_uint num_alignment_padding_bytes, level; + mz_zip_archive_file_stat stats; + int err = 0; + + if (!zip) { + return ZIP_ENOINIT; + } + + if (!entryname) { + return ZIP_EINVENTNAME; + } + + entrylen = strlen(entryname); + if (entrylen == 0) { + return ZIP_EINVENTNAME; + } + + /* + .ZIP File Format Specification Version: 6.3.3 + + 4.4.17.1 The name of the file, with optional relative path. + The path stored MUST not contain a drive or + device letter, or a leading slash. All slashes + MUST be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. + */ + if (zip->entry.name) { + CLEANUP(zip->entry.name); + } + zip->entry.name = zip_strrpl(entryname, entrylen, '\\', '/'); + if (!zip->entry.name) { + // Cannot parse zip entry name + return ZIP_EINVENTNAME; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { + zip->entry.index = + mz_zip_reader_locate_file(pzip, zip->entry.name, NULL, 0); + if (zip->entry.index < 0) { + err = ZIP_ENOENT; + goto cleanup; + } + + if (!mz_zip_reader_file_stat(pzip, (mz_uint)zip->entry.index, &stats)) { + err = ZIP_ENOENT; + goto cleanup; + } + + zip->entry.comp_size = stats.m_comp_size; + zip->entry.uncomp_size = stats.m_uncomp_size; + zip->entry.uncomp_crc32 = stats.m_crc32; + zip->entry.offset = stats.m_central_dir_ofs; + zip->entry.header_offset = stats.m_local_header_ofs; + zip->entry.method = stats.m_method; + zip->entry.external_attr = stats.m_external_attr; +#ifndef MINIZ_NO_TIME + zip->entry.m_time = stats.m_time; +#endif + + return 0; + } + + zip->entry.index = (int)zip->archive.m_total_files; + zip->entry.comp_size = 0; + zip->entry.uncomp_size = 0; + zip->entry.uncomp_crc32 = MZ_CRC32_INIT; + zip->entry.offset = zip->archive.m_archive_size; + zip->entry.header_offset = zip->archive.m_archive_size; + memset(zip->entry.header, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8)); + zip->entry.method = 0; + + // UNIX or APPLE +#if MZ_PLATFORM == 3 || MZ_PLATFORM == 19 + // regular file with rw-r--r-- persmissions + zip->entry.external_attr = (mz_uint32)(0100644) << 16; +#else + zip->entry.external_attr = 0; +#endif + + num_alignment_padding_bytes = + mz_zip_writer_compute_padding_needed_for_file_alignment(pzip); + + if (!pzip->m_pState || (pzip->m_zip_mode != MZ_ZIP_MODE_WRITING)) { + // Invalid zip mode + err = ZIP_EINVMODE; + goto cleanup; + } + if (zip->level & MZ_ZIP_FLAG_COMPRESSED_DATA) { + // Invalid zip compression level + err = ZIP_EINVLVL; + goto cleanup; + } + // no zip64 support yet + if ((pzip->m_total_files == 0xFFFF) || + ((pzip->m_archive_size + num_alignment_padding_bytes + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + entrylen) > 0xFFFFFFFF)) { + // No zip64 support yet + err = ZIP_ENOSUP64; + goto cleanup; + } + if (!mz_zip_writer_write_zeros(pzip, zip->entry.offset, + num_alignment_padding_bytes + + sizeof(zip->entry.header))) { + // Cannot memset zip entry header + err = ZIP_EMEMSET; + goto cleanup; + } + + zip->entry.header_offset += num_alignment_padding_bytes; + if (pzip->m_file_offset_alignment) { + MZ_ASSERT( + (zip->entry.header_offset & (pzip->m_file_offset_alignment - 1)) == 0); + } + zip->entry.offset += num_alignment_padding_bytes + sizeof(zip->entry.header); + + if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, zip->entry.name, + entrylen) != entrylen) { + // Cannot write data to zip entry + err = ZIP_EWRTENT; + goto cleanup; + } + + zip->entry.offset += entrylen; + level = zip->level & 0xF; + if (level) { + zip->entry.state.m_pZip = pzip; + zip->entry.state.m_cur_archive_file_ofs = zip->entry.offset; + zip->entry.state.m_comp_size = 0; + + if (tdefl_init(&(zip->entry.comp), mz_zip_writer_add_put_buf_callback, + &(zip->entry.state), + (int)tdefl_create_comp_flags_from_zip_params( + (int)level, -15, MZ_DEFAULT_STRATEGY)) != + TDEFL_STATUS_OKAY) { + // Cannot initialize the zip compressor + err = ZIP_ETDEFLINIT; + goto cleanup; + } + } + + zip->entry.m_time = time(NULL); + + return 0; + +cleanup: + CLEANUP(zip->entry.name); + return err; +} + +int zip_entry_openbyindex(struct zip_t *zip, int index) { + mz_zip_archive *pZip = NULL; + mz_zip_archive_file_stat stats; + mz_uint namelen; + const mz_uint8 *pHeader; + const char *pFilename; + + if (!zip) { + // zip_t handler is not initialized + return ZIP_ENOINIT; + } + + pZip = &(zip->archive); + if (pZip->m_zip_mode != MZ_ZIP_MODE_READING) { + // open by index requires readonly mode + return ZIP_EINVMODE; + } + + if (index < 0 || (mz_uint)index >= pZip->m_total_files) { + // index out of range + return ZIP_EINVIDX; + } + + if (!(pHeader = &MZ_ZIP_ARRAY_ELEMENT( + &pZip->m_pState->m_central_dir, mz_uint8, + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, + mz_uint32, index)))) { + // cannot find header in central directory + return ZIP_ENOHDR; + } + + namelen = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + + /* + .ZIP File Format Specification Version: 6.3.3 + + 4.4.17.1 The name of the file, with optional relative path. + The path stored MUST not contain a drive or + device letter, or a leading slash. All slashes + MUST be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. + */ + if (zip->entry.name) { + CLEANUP(zip->entry.name); + } + zip->entry.name = zip_strrpl(pFilename, namelen, '\\', '/'); + if (!zip->entry.name) { + // local entry name is NULL + return ZIP_EINVENTNAME; + } + + if (!mz_zip_reader_file_stat(pZip, (mz_uint)index, &stats)) { + return ZIP_ENOENT; + } + + zip->entry.index = index; + zip->entry.comp_size = stats.m_comp_size; + zip->entry.uncomp_size = stats.m_uncomp_size; + zip->entry.uncomp_crc32 = stats.m_crc32; + zip->entry.offset = stats.m_central_dir_ofs; + zip->entry.header_offset = stats.m_local_header_ofs; + zip->entry.method = stats.m_method; + zip->entry.external_attr = stats.m_external_attr; +#ifndef MINIZ_NO_TIME + zip->entry.m_time = stats.m_time; +#endif + + return 0; +} + +int zip_entry_close(struct zip_t *zip) { + mz_zip_archive *pzip = NULL; + mz_uint level; + tdefl_status done; + mz_uint16 entrylen; + mz_uint16 dos_time = 0, dos_date = 0; + int err = 0; + + if (!zip) { + // zip_t handler is not initialized + err = ZIP_ENOINIT; + goto cleanup; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode == MZ_ZIP_MODE_READING) { + goto cleanup; + } + + level = zip->level & 0xF; + if (level) { + done = tdefl_compress_buffer(&(zip->entry.comp), "", 0, TDEFL_FINISH); + if (done != TDEFL_STATUS_DONE && done != TDEFL_STATUS_OKAY) { + // Cannot flush compressed buffer + err = ZIP_ETDEFLBUF; + goto cleanup; + } + zip->entry.comp_size = zip->entry.state.m_comp_size; + zip->entry.offset = zip->entry.state.m_cur_archive_file_ofs; + zip->entry.method = MZ_DEFLATED; + } + + entrylen = (mz_uint16)strlen(zip->entry.name); + if ((zip->entry.comp_size > 0xFFFFFFFF) || (zip->entry.offset > 0xFFFFFFFF)) { + // No zip64 support, yet + err = ZIP_ENOSUP64; + goto cleanup; + } + +#ifndef MINIZ_NO_TIME + mz_zip_time_t_to_dos_time(zip->entry.m_time, &dos_time, &dos_date); +#endif + + if (!mz_zip_writer_create_local_dir_header( + pzip, zip->entry.header, entrylen, 0, zip->entry.uncomp_size, + zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0, + dos_time, dos_date)) { + // Cannot create zip entry header + err = ZIP_ECRTHDR; + goto cleanup; + } + + if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.header_offset, + zip->entry.header, + sizeof(zip->entry.header)) != sizeof(zip->entry.header)) { + // Cannot write zip entry header + err = ZIP_EWRTHDR; + goto cleanup; + } + + if (!mz_zip_writer_add_to_central_dir( + pzip, zip->entry.name, entrylen, NULL, 0, "", 0, + zip->entry.uncomp_size, zip->entry.comp_size, zip->entry.uncomp_crc32, + zip->entry.method, 0, dos_time, dos_date, zip->entry.header_offset, + zip->entry.external_attr)) { + // Cannot write to zip central dir + err = ZIP_EWRTDIR; + goto cleanup; + } + + pzip->m_total_files++; + pzip->m_archive_size = zip->entry.offset; + +cleanup: + if (zip) { + zip->entry.m_time = 0; + CLEANUP(zip->entry.name); + } + return err; +} + +const char *zip_entry_name(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return NULL; + } + + return zip->entry.name; +} + +int zip_entry_index(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return ZIP_ENOINIT; + } + + return zip->entry.index; +} + +int zip_entry_isdir(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return ZIP_ENOINIT; + } + + if (zip->entry.index < 0) { + // zip entry is not opened + return ZIP_EINVIDX; + } + + return (int)mz_zip_reader_is_file_a_directory(&zip->archive, + (mz_uint)zip->entry.index); +} + +unsigned long long zip_entry_size(struct zip_t *zip) { + return zip ? zip->entry.uncomp_size : 0; +} + +unsigned int zip_entry_crc32(struct zip_t *zip) { + return zip ? zip->entry.uncomp_crc32 : 0; +} + +int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize) { + mz_uint level; + mz_zip_archive *pzip = NULL; + tdefl_status status; + + if (!zip) { + // zip_t handler is not initialized + return ZIP_ENOINIT; + } + + pzip = &(zip->archive); + if (buf && bufsize > 0) { + zip->entry.uncomp_size += bufsize; + zip->entry.uncomp_crc32 = (mz_uint32)mz_crc32( + zip->entry.uncomp_crc32, (const mz_uint8 *)buf, bufsize); + + level = zip->level & 0xF; + if (!level) { + if ((pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, buf, + bufsize) != bufsize)) { + // Cannot write buffer + return ZIP_EWRTENT; + } + zip->entry.offset += bufsize; + zip->entry.comp_size += bufsize; + } else { + status = tdefl_compress_buffer(&(zip->entry.comp), buf, bufsize, + TDEFL_NO_FLUSH); + if (status != TDEFL_STATUS_DONE && status != TDEFL_STATUS_OKAY) { + // Cannot compress buffer + return ZIP_ETDEFLBUF; + } + } + } + + return 0; +} + +int zip_entry_fwrite(struct zip_t *zip, const char *filename) { + int err = 0; + size_t n = 0; + FILE *stream = NULL; + mz_uint8 buf[MZ_ZIP_MAX_IO_BUF_SIZE]; + struct MZ_FILE_STAT_STRUCT file_stat; + + if (!zip) { + // zip_t handler is not initialized + return ZIP_ENOINIT; + } + + memset(buf, 0, MZ_ZIP_MAX_IO_BUF_SIZE); + memset((void *)&file_stat, 0, sizeof(struct MZ_FILE_STAT_STRUCT)); + if (MZ_FILE_STAT(filename, &file_stat) != 0) { + // problem getting information - check errno + return ZIP_ENOENT; + } + + if ((file_stat.st_mode & 0200) == 0) { + // MS-DOS read-only attribute + zip->entry.external_attr |= 0x01; + } + zip->entry.external_attr |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16); + zip->entry.m_time = file_stat.st_mtime; + +#if defined(_MSC_VER) + if (fopen_s(&stream, filename, "rb")) +#else + if (!(stream = fopen(filename, "rb"))) +#endif + { + // Cannot open filename + return ZIP_EOPNFILE; + } + + while ((n = fread(buf, sizeof(mz_uint8), MZ_ZIP_MAX_IO_BUF_SIZE, stream)) > + 0) { + if (zip_entry_write(zip, buf, n) < 0) { + err = ZIP_EWRTENT; + break; + } + } + fclose(stream); + + return err; +} + +ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + size_t size = 0; + + if (!zip) { + // zip_t handler is not initialized + return (ssize_t)ZIP_ENOINIT; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return (ssize_t)ZIP_ENOENT; + } + + idx = (mz_uint)zip->entry.index; + if (mz_zip_reader_is_file_a_directory(pzip, idx)) { + // the entry is a directory + return (ssize_t)ZIP_EINVENTTYPE; + } + + *buf = mz_zip_reader_extract_to_heap(pzip, idx, &size, 0); + if (*buf && bufsize) { + *bufsize = size; + } + return (ssize_t)size; +} + +ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) { + mz_zip_archive *pzip = NULL; + + if (!zip) { + // zip_t handler is not initialized + return (ssize_t)ZIP_ENOINIT; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return (ssize_t)ZIP_ENOENT; + } + + if (!mz_zip_reader_extract_to_mem_no_alloc(pzip, (mz_uint)zip->entry.index, + buf, bufsize, 0, NULL, 0)) { + return (ssize_t)ZIP_EMEMNOALLOC; + } + + return (ssize_t)zip->entry.uncomp_size; +} + +int zip_entry_fread(struct zip_t *zip, const char *filename) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + mz_uint32 xattr = 0; + mz_zip_archive_file_stat info; + + if (!zip) { + // zip_t handler is not initialized + return ZIP_ENOINIT; + } + + memset((void *)&info, 0, sizeof(mz_zip_archive_file_stat)); + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return ZIP_ENOENT; + } + + idx = (mz_uint)zip->entry.index; + if (mz_zip_reader_is_file_a_directory(pzip, idx)) { + // the entry is a directory + return ZIP_EINVENTTYPE; + } + + if (!mz_zip_reader_extract_to_file(pzip, idx, filename, 0)) { + return ZIP_ENOFILE; + } + +#if defined(_MSC_VER) + (void)xattr; // unused +#else + if (!mz_zip_reader_file_stat(pzip, idx, &info)) { + // Cannot get information about zip archive; + return ZIP_ENOFILE; + } + + xattr = (info.m_external_attr >> 16) & 0xFFFF; + if (xattr > 0) { + if (chmod(filename, (mode_t)xattr) < 0) { + return ZIP_ENOPERM; + } + } +#endif + + return 0; +} + +int zip_entry_extract(struct zip_t *zip, + size_t (*on_extract)(void *arg, unsigned long long offset, + const void *buf, size_t bufsize), + void *arg) { + mz_zip_archive *pzip = NULL; + mz_uint idx; + + if (!zip) { + // zip_t handler is not initialized + return ZIP_ENOINIT; + } + + pzip = &(zip->archive); + if (pzip->m_zip_mode != MZ_ZIP_MODE_READING || zip->entry.index < 0) { + // the entry is not found or we do not have read access + return ZIP_ENOENT; + } + + idx = (mz_uint)zip->entry.index; + return (mz_zip_reader_extract_to_callback(pzip, idx, on_extract, arg, 0)) + ? 0 + : ZIP_EINVIDX; +} + +int zip_entries_total(struct zip_t *zip) { + if (!zip) { + // zip_t handler is not initialized + return ZIP_ENOINIT; + } + + return (int)zip->archive.m_total_files; +} + +int zip_entries_delete(struct zip_t *zip, char *const entries[], + const size_t len) { + int n = 0; + int err = 0; + struct zip_entry_mark_t *entry_mark = NULL; + + if (zip == NULL || (entries == NULL && len != 0)) { + return ZIP_ENOINIT; + } + + if (entries == NULL && len == 0) { + return 0; + } + + n = zip_entries_total(zip); + + entry_mark = + (struct zip_entry_mark_t *)calloc(n, sizeof(struct zip_entry_mark_t)); + if (!entry_mark) { + return ZIP_EOOMEM; + } + + zip->archive.m_zip_mode = MZ_ZIP_MODE_READING; + + err = zip_entry_set(zip, entry_mark, n, entries, len); + if (err < 0) { + CLEANUP(entry_mark); + return err; + } + + err = zip_entries_delete_mark(zip, entry_mark, n); + CLEANUP(entry_mark); + return err; +} + +int zip_stream_extract(const char *stream, size_t size, const char *dir, + int (*on_extract)(const char *filename, void *arg), + void *arg) { + mz_zip_archive zip_archive; + if (!stream || !dir) { + // Cannot parse zip archive stream + return ZIP_ENOINIT; + } + if (!memset(&zip_archive, 0, sizeof(mz_zip_archive))) { + // Cannot memset zip archive + return ZIP_EMEMSET; + } + if (!mz_zip_reader_init_mem(&zip_archive, stream, size, 0)) { + // Cannot initialize zip_archive reader + return ZIP_ENOINIT; + } + + return zip_archive_extract(&zip_archive, dir, on_extract, arg); +} + +struct zip_t *zip_stream_open(const char *stream, size_t size, int level, + char mode) { + struct zip_t *zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t)); + if (!zip) { + return NULL; + } + + if (level < 0) { + level = MZ_DEFAULT_LEVEL; + } + if ((level & 0xF) > MZ_UBER_COMPRESSION) { + // Wrong compression level + goto cleanup; + } + zip->level = (mz_uint)level; + + if ((stream != NULL) && (size > 0) && (mode == 'r')) { + if (!mz_zip_reader_init_mem(&(zip->archive), stream, size, 0)) { + goto cleanup; + } + } else if ((stream == NULL) && (size == 0) && (mode == 'w')) { + // Create a new archive. + if (!mz_zip_writer_init_heap(&(zip->archive), 0, 1024)) { + // Cannot initialize zip_archive writer + goto cleanup; + } + } else { + goto cleanup; + } + return zip; + +cleanup: + CLEANUP(zip); + return NULL; +} + +ssize_t zip_stream_copy(struct zip_t *zip, void **buf, size_t *bufsize) { + if (!zip) { + return (ssize_t)ZIP_ENOINIT; + } + + zip_archive_finalize(&(zip->archive)); + + if (bufsize != NULL) { + *bufsize = (size_t)zip->archive.m_archive_size; + } + *buf = calloc(sizeof(unsigned char), zip->archive.m_archive_size); + memcpy(*buf, zip->archive.m_pState->m_pMem, zip->archive.m_archive_size); + + return (ssize_t)zip->archive.m_archive_size; +} + +void zip_stream_close(struct zip_t *zip) { + if (zip) { + mz_zip_writer_end(&(zip->archive)); + mz_zip_reader_end(&(zip->archive)); + CLEANUP(zip); + } +} + +int zip_create(const char *zipname, const char *filenames[], size_t len) { + int err = 0; + size_t i; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + mz_uint32 ext_attributes = 0; + + if (!zipname || strlen(zipname) < 1) { + // zip_t archive name is empty or NULL + return ZIP_EINVZIPNAME; + } + + // Create a new archive. + if (!memset(&(zip_archive), 0, sizeof(zip_archive))) { + // Cannot memset zip archive + return ZIP_EMEMSET; + } + + if (!mz_zip_writer_init_file(&zip_archive, zipname, 0)) { + // Cannot initialize zip_archive writer + return ZIP_ENOINIT; + } + + if (!memset((void *)&file_stat, 0, sizeof(struct MZ_FILE_STAT_STRUCT))) { + return ZIP_EMEMSET; + } + + for (i = 0; i < len; ++i) { + const char *name = filenames[i]; + if (!name) { + err = ZIP_EINVENTNAME; + break; + } + + if (MZ_FILE_STAT(name, &file_stat) != 0) { + // problem getting information - check errno + err = ZIP_ENOFILE; + break; + } + + if ((file_stat.st_mode & 0200) == 0) { + // MS-DOS read-only attribute + ext_attributes |= 0x01; + } + ext_attributes |= (mz_uint32)((file_stat.st_mode & 0xFFFF) << 16); + + if (!mz_zip_writer_add_file(&zip_archive, zip_basename(name), name, "", 0, + ZIP_DEFAULT_COMPRESSION_LEVEL, + ext_attributes)) { + // Cannot add file to zip_archive + err = ZIP_ENOFILE; + break; + } + } + + mz_zip_writer_finalize_archive(&zip_archive); + mz_zip_writer_end(&zip_archive); + return err; +} + +int zip_extract(const char *zipname, const char *dir, + int (*on_extract)(const char *filename, void *arg), void *arg) { + mz_zip_archive zip_archive; + + if (!zipname || !dir) { + // Cannot parse zip archive name + return ZIP_EINVZIPNAME; + } + + if (!memset(&zip_archive, 0, sizeof(mz_zip_archive))) { + // Cannot memset zip archive + return ZIP_EMEMSET; + } + + // Now try to open the archive. + if (!mz_zip_reader_init_file(&zip_archive, zipname, 0)) { + // Cannot initialize zip_archive reader + return ZIP_ENOINIT; + } + + return zip_archive_extract(&zip_archive, dir, on_extract, arg); +} diff --git a/test/standalone/issue_9812/vendor/kuba-zip/zip.h b/test/standalone/issue_9812/vendor/kuba-zip/zip.h new file mode 100644 index 0000000000..54b412d8bd --- /dev/null +++ b/test/standalone/issue_9812/vendor/kuba-zip/zip.h @@ -0,0 +1,433 @@ +/* + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once +#ifndef ZIP_H +#define ZIP_H + +#include +#include + +#ifndef ZIP_SHARED +# define ZIP_EXPORT +#else +# ifdef _WIN32 +# ifdef ZIP_BUILD_SHARED +# define ZIP_EXPORT __declspec(dllexport) +# else +# define ZIP_EXPORT __declspec(dllimport) +# endif +# else +# define ZIP_EXPORT __attribute__ ((visibility ("default"))) +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(_POSIX_C_SOURCE) && defined(_MSC_VER) +// 64-bit Windows is the only mainstream platform +// where sizeof(long) != sizeof(void*) +#ifdef _WIN64 +typedef long long ssize_t; /* byte count or error */ +#else +typedef long ssize_t; /* byte count or error */ +#endif +#endif + +#ifndef MAX_PATH +#define MAX_PATH 32767 /* # chars in a path name including NULL */ +#endif + +/** + * @mainpage + * + * Documenation for @ref zip. + */ + +/** + * @addtogroup zip + * @{ + */ + +/** + * Default zip compression level. + */ +#define ZIP_DEFAULT_COMPRESSION_LEVEL 6 + +/** + * Error codes + */ +#define ZIP_ENOINIT -1 // not initialized +#define ZIP_EINVENTNAME -2 // invalid entry name +#define ZIP_ENOENT -3 // entry not found +#define ZIP_EINVMODE -4 // invalid zip mode +#define ZIP_EINVLVL -5 // invalid compression level +#define ZIP_ENOSUP64 -6 // no zip 64 support +#define ZIP_EMEMSET -7 // memset error +#define ZIP_EWRTENT -8 // cannot write data to entry +#define ZIP_ETDEFLINIT -9 // cannot initialize tdefl compressor +#define ZIP_EINVIDX -10 // invalid index +#define ZIP_ENOHDR -11 // header not found +#define ZIP_ETDEFLBUF -12 // cannot flush tdefl buffer +#define ZIP_ECRTHDR -13 // cannot create entry header +#define ZIP_EWRTHDR -14 // cannot write entry header +#define ZIP_EWRTDIR -15 // cannot write to central dir +#define ZIP_EOPNFILE -16 // cannot open file +#define ZIP_EINVENTTYPE -17 // invalid entry type +#define ZIP_EMEMNOALLOC -18 // extracting data using no memory allocation +#define ZIP_ENOFILE -19 // file not found +#define ZIP_ENOPERM -20 // no permission +#define ZIP_EOOMEM -21 // out of memory +#define ZIP_EINVZIPNAME -22 // invalid zip archive name +#define ZIP_EMKDIR -23 // make dir error +#define ZIP_ESYMLINK -24 // symlink error +#define ZIP_ECLSZIP -25 // close archive error +#define ZIP_ECAPSIZE -26 // capacity size too small +#define ZIP_EFSEEK -27 // fseek error +#define ZIP_EFREAD -28 // fread error +#define ZIP_EFWRITE -29 // fwrite error + +/** + * Looks up the error message string coresponding to an error number. + * @param errnum error number + * @return error message string coresponding to errnum or NULL if error is not + * found. + */ +extern ZIP_EXPORT const char *zip_strerror(int errnum); + +/** + * @struct zip_t + * + * This data structure is used throughout the library to represent zip archive - + * forward declaration. + */ +struct zip_t; + +/** + * Opens zip archive with compression level using the given mode. + * + * @param zipname zip archive file name. + * @param level compression level (0-9 are the standard zlib-style levels). + * @param mode file access mode. + * - 'r': opens a file for reading/extracting (the file must exists). + * - 'w': creates an empty file for writing. + * - 'a': appends to an existing archive. + * + * @return the zip archive handler or NULL on error + */ +extern ZIP_EXPORT struct zip_t *zip_open(const char *zipname, int level, + char mode); + +/** + * Closes the zip archive, releases resources - always finalize. + * + * @param zip zip archive handler. + */ +extern ZIP_EXPORT void zip_close(struct zip_t *zip); + +/** + * Determines if the archive has a zip64 end of central directory headers. + * + * @param zip zip archive handler. + * + * @return the return code - 1 (true), 0 (false), negative number (< 0) on + * error. + */ +extern ZIP_EXPORT int zip_is64(struct zip_t *zip); + +/** + * Opens an entry by name in the zip archive. + * + * For zip archive opened in 'w' or 'a' mode the function will append + * a new entry. In readonly mode the function tries to locate the entry + * in global dictionary. + * + * @param zip zip archive handler. + * @param entryname an entry name in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_entry_open(struct zip_t *zip, const char *entryname); + +/** + * Opens a new entry by index in the zip archive. + * + * This function is only valid if zip archive was opened in 'r' (readonly) mode. + * + * @param zip zip archive handler. + * @param index index in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_entry_openbyindex(struct zip_t *zip, int index); + +/** + * Closes a zip entry, flushes buffer and releases resources. + * + * @param zip zip archive handler. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_entry_close(struct zip_t *zip); + +/** + * Returns a local name of the current zip entry. + * + * The main difference between user's entry name and local entry name + * is optional relative path. + * Following .ZIP File Format Specification - the path stored MUST not contain + * a drive or device letter, or a leading slash. + * All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' + * for compatibility with Amiga and UNIX file systems etc. + * + * @param zip: zip archive handler. + * + * @return the pointer to the current zip entry name, or NULL on error. + */ +extern ZIP_EXPORT const char *zip_entry_name(struct zip_t *zip); + +/** + * Returns an index of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the index on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_entry_index(struct zip_t *zip); + +/** + * Determines if the current zip entry is a directory entry. + * + * @param zip zip archive handler. + * + * @return the return code - 1 (true), 0 (false), negative number (< 0) on + * error. + */ +extern ZIP_EXPORT int zip_entry_isdir(struct zip_t *zip); + +/** + * Returns an uncompressed size of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the uncompressed size in bytes. + */ +extern ZIP_EXPORT unsigned long long zip_entry_size(struct zip_t *zip); + +/** + * Returns CRC-32 checksum of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the CRC-32 checksum. + */ +extern ZIP_EXPORT unsigned int zip_entry_crc32(struct zip_t *zip); + +/** + * Compresses an input buffer for the current zip entry. + * + * @param zip zip archive handler. + * @param buf input buffer. + * @param bufsize input buffer size (in bytes). + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_entry_write(struct zip_t *zip, const void *buf, + size_t bufsize); + +/** + * Compresses a file for the current zip entry. + * + * @param zip zip archive handler. + * @param filename input file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_entry_fwrite(struct zip_t *zip, const char *filename); + +/** + * Extracts the current zip entry into output buffer. + * + * The function allocates sufficient memory for a output buffer. + * + * @param zip zip archive handler. + * @param buf output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note remember to release memory allocated for a output buffer. + * for large entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a negative number (< 0) on error. + */ +extern ZIP_EXPORT ssize_t zip_entry_read(struct zip_t *zip, void **buf, + size_t *bufsize); + +/** + * Extracts the current zip entry into a memory buffer using no memory + * allocation. + * + * @param zip zip archive handler. + * @param buf preallocated output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note ensure supplied output buffer is large enough. + * zip_entry_size function (returns uncompressed size for the current + * entry) can be handy to estimate how big buffer is needed. + * For large entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a negative number (< 0) on error (e.g. bufsize is not large enough). + */ +extern ZIP_EXPORT ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, + size_t bufsize); + +/** + * Extracts the current zip entry into output file. + * + * @param zip zip archive handler. + * @param filename output file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_entry_fread(struct zip_t *zip, const char *filename); + +/** + * Extracts the current zip entry using a callback function (on_extract). + * + * @param zip zip archive handler. + * @param on_extract callback function. + * @param arg opaque pointer (optional argument, which you can pass to the + * on_extract callback) + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int +zip_entry_extract(struct zip_t *zip, + size_t (*on_extract)(void *arg, unsigned long long offset, + const void *data, size_t size), + void *arg); + +/** + * Returns the number of all entries (files and directories) in the zip archive. + * + * @param zip zip archive handler. + * + * @return the return code - the number of entries on success, negative number + * (< 0) on error. + */ +extern ZIP_EXPORT int zip_entries_total(struct zip_t *zip); + +/** + * Deletes zip archive entries. + * + * @param zip zip archive handler. + * @param entries array of zip archive entries to be deleted. + * @param len the number of entries to be deleted. + * @return the number of deleted entries, or negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_entries_delete(struct zip_t *zip, + char *const entries[], size_t len); + +/** + * Extracts a zip archive stream into directory. + * + * If on_extract is not NULL, the callback will be called after + * successfully extracted each zip entry. + * Returning a negative value from the callback will cause abort and return an + * error. The last argument (void *arg) is optional, which you can use to pass + * data to the on_extract callback. + * + * @param stream zip archive stream. + * @param size stream size. + * @param dir output directory. + * @param on_extract on extract callback. + * @param arg opaque pointer. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int +zip_stream_extract(const char *stream, size_t size, const char *dir, + int (*on_extract)(const char *filename, void *arg), + void *arg); + +/** + * Opens zip archive stream into memory. + * + * @param stream zip archive stream. + * @param size stream size. + * + * @return the zip archive handler or NULL on error + */ +extern ZIP_EXPORT struct zip_t *zip_stream_open(const char *stream, size_t size, + int level, char mode); + +/** + * Copy zip archive stream output buffer. + * + * @param zip zip archive handler. + * @param buf output buffer. User should free buf. + * @param bufsize output buffer size (in bytes). + * + * @return copy size + */ +extern ZIP_EXPORT ssize_t zip_stream_copy(struct zip_t *zip, void **buf, + size_t *bufsize); + +/** + * Close zip archive releases resources. + * + * @param zip zip archive handler. + * + * @return + */ +extern ZIP_EXPORT void zip_stream_close(struct zip_t *zip); + +/** + * Creates a new archive and puts files into a single zip archive. + * + * @param zipname zip archive file. + * @param filenames input files. + * @param len: number of input files. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_create(const char *zipname, const char *filenames[], + size_t len); + +/** + * Extracts a zip archive file into directory. + * + * If on_extract_entry is not NULL, the callback will be called after + * successfully extracted each zip entry. + * Returning a negative value from the callback will cause abort and return an + * error. The last argument (void *arg) is optional, which you can use to pass + * data to the on_extract_entry callback. + * + * @param zipname zip archive file. + * @param dir output directory. + * @param on_extract_entry on extract callback. + * @param arg opaque pointer. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ +extern ZIP_EXPORT int zip_extract(const char *zipname, const char *dir, + int (*on_extract_entry)(const char *filename, + void *arg), + void *arg); + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif From 5913140b6bf96e168a0167906a78e2d4aac5bd9d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 21 Sep 2021 15:08:32 -0700 Subject: [PATCH 086/160] stage2: free Sema's arena after generating machine code Previously, linker backends or machine code backends were able to hold on to references to inside Sema's temporary arena. However there can be large objects stored there that we want to free after machine code is generated. The primary change in this commit is to use a temporary arena for Sema of function bodies that gets freed after machine code backend finishes handling `updateFunc` (at the same time that Air and Liveness get freed). The other changes in this commit are fixing issues that fell out from the primary change. * The C linker backend is rewritten to handle updateDecl and updateFunc separately. Also, all Decl updates get access to typedefs and fwd_decls, not only functions. * The C linker backend is updated to the new API that does not depend on allocateDeclIndexes and does not have to handle garbage collected decls. * The C linker backend uses an arena for Type/Value objects that `typedefs` references. These can be garbage collected every so often after flush(), however that garbage collection code is not implemented at this time. It will be pretty simple, just allocate a new arena, copy all the Type objects to it, update the keys of the hash map, free the old arena. * Sema: fix a handful of instances of not copying Type/Value objects from the temporary arena into the appropriate Decl arena. * Type: fix some function types not reporting hasCodeGenBits() correctly. --- src/Compilation.zig | 14 +- src/Module.zig | 32 +- src/Sema.zig | 16 +- src/codegen/c.zig | 1069 ++++++++++++++++++++++--------------------- src/link.zig | 10 +- src/link/C.zig | 262 +++++++---- src/type.zig | 10 +- 7 files changed, 762 insertions(+), 651 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 53e643acb8..48c907c759 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2145,7 +2145,11 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor const module = self.bin_file.options.module.?; const decl = func.owner_decl; - var air = module.analyzeFnBody(decl, func) catch |err| switch (err) { + var tmp_arena = std.heap.ArenaAllocator.init(gpa); + defer tmp_arena.deinit(); + const sema_arena = &tmp_arena.allocator; + + var air = module.analyzeFnBody(decl, func, sema_arena) catch |err| switch (err) { error.AnalysisFail => { assert(func.state != .in_progress); continue; @@ -2207,16 +2211,20 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor const decl_emit_h = decl.getEmitH(module); const fwd_decl = &decl_emit_h.fwd_decl; fwd_decl.shrinkRetainingCapacity(0); + var typedefs_arena = std.heap.ArenaAllocator.init(gpa); + defer typedefs_arena.deinit(); var dg: c_codegen.DeclGen = .{ + .gpa = gpa, .module = module, .error_msg = null, .decl = decl, .fwd_decl = fwd_decl.toManaged(gpa), - // we don't want to emit optionals and error unions to headers since they have no ABI - .typedefs = undefined, + .typedefs = c_codegen.TypedefMap.init(gpa), + .typedefs_arena = &typedefs_arena.allocator, }; defer dg.fwd_decl.deinit(); + defer dg.typedefs.deinit(); c_codegen.genHeader(&dg) catch |err| switch (err) { error.AnalysisFail => { diff --git a/src/Module.zig b/src/Module.zig index fd275b507f..88817efc26 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -610,7 +610,7 @@ pub const Decl = struct { /// If the Decl has a value and it is a function, return it, /// otherwise null. - pub fn getFunction(decl: *Decl) ?*Fn { + pub fn getFunction(decl: *const Decl) ?*Fn { if (!decl.owns_tv) return null; const func = (decl.val.castTag(.function) orelse return null).data; assert(func.owner_decl == decl); @@ -3789,7 +3789,7 @@ pub fn clearDecl( .elf => .{ .elf = link.File.Elf.TextBlock.empty }, .macho => .{ .macho = link.File.MachO.TextBlock.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, - .c => .{ .c = link.File.C.DeclBlock.empty }, + .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, .spirv => .{ .spirv = {} }, }; @@ -3798,7 +3798,7 @@ pub fn clearDecl( .elf => .{ .elf = link.File.Elf.SrcFn.empty }, .macho => .{ .macho = link.File.MachO.SrcFn.empty }, .plan9 => .{ .plan9 = {} }, - .c => .{ .c = link.File.C.FnBlock.empty }, + .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.FnData.empty }, .spirv => .{ .spirv = .{} }, }; @@ -3828,10 +3828,13 @@ pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void { // about the Decl in the first place. // Until then, we did call `allocateDeclIndexes` on this anonymous Decl and so we // must call `freeDecl` in the linker backend now. - if (decl.has_tv) { - if (decl.ty.hasCodeGenBits()) { - mod.comp.bin_file.freeDecl(decl); - } + switch (mod.comp.bin_file.tag) { + .c => {}, // this linker backend has already migrated to the new API + else => if (decl.has_tv) { + if (decl.ty.hasCodeGenBits()) { + mod.comp.bin_file.freeDecl(decl); + } + }, } const dependants = decl.dependants.keys(); @@ -3893,22 +3896,16 @@ fn deleteDeclExports(mod: *Module, decl: *Decl) void { mod.gpa.free(kv.value); } -pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { +pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: *Allocator) SemaError!Air { const tracy = trace(@src()); defer tracy.end(); const gpa = mod.gpa; - // Use the Decl's arena for function memory. - var arena = decl.value_arena.?.promote(gpa); - defer decl.value_arena.?.* = arena.state; - - const fn_ty = decl.ty; - var sema: Sema = .{ .mod = mod, .gpa = gpa, - .arena = &arena.allocator, + .arena = arena, .code = decl.namespace.file_scope.zir, .owner_decl = decl, .namespace = decl.namespace, @@ -3942,6 +3939,7 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { // This could be a generic function instantiation, however, in which case we need to // map the comptime parameters to constant values and only emit arg AIR instructions // for the runtime ones. + const fn_ty = decl.ty; const runtime_params_len = @intCast(u32, fn_ty.fnParamLen()); try inner_block.instructions.ensureTotalCapacity(gpa, runtime_params_len); try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len * 2); // * 2 for the `addType` @@ -4072,7 +4070,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast. .elf => .{ .elf = link.File.Elf.TextBlock.empty }, .macho => .{ .macho = link.File.MachO.TextBlock.empty }, .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty }, - .c => .{ .c = link.File.C.DeclBlock.empty }, + .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.DeclBlock.empty }, .spirv => .{ .spirv = {} }, }, @@ -4081,7 +4079,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast. .elf => .{ .elf = link.File.Elf.SrcFn.empty }, .macho => .{ .macho = link.File.MachO.SrcFn.empty }, .plan9 => .{ .plan9 = {} }, - .c => .{ .c = link.File.C.FnBlock.empty }, + .c => .{ .c = {} }, .wasm => .{ .wasm = link.File.Wasm.FnData.empty }, .spirv => .{ .spirv = .{} }, }, diff --git a/src/Sema.zig b/src/Sema.zig index 786db21b4a..f6bea69129 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2999,6 +2999,8 @@ fn analyzeCall( // TODO: check whether any external comptime memory was mutated by the // comptime function call. If so, then do not memoize the call here. + // TODO: re-evaluate whether memoized_calls needs its own arena. I think + // it should be fine to use the Decl arena for the function. { var arena_allocator = std.heap.ArenaAllocator.init(gpa); errdefer arena_allocator.deinit(); @@ -3009,7 +3011,7 @@ fn analyzeCall( } try mod.memoized_calls.put(gpa, memoized_call_key, .{ - .val = result_val, + .val = try result_val.copy(arena), .arena = arena_allocator.state, }); delete_memoized_call_key = false; @@ -5876,10 +5878,7 @@ fn zirArrayCat(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr else try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type }); const val = try Value.Tag.array.create(anon_decl.arena(), buf); - return sema.analyzeDeclRef(try anon_decl.finish( - ty, - val, - )); + return sema.analyzeDeclRef(try anon_decl.finish(ty, val)); } return sema.mod.fail(&block.base, lhs_src, "TODO array_cat more types of Values", .{}); } else { @@ -5941,10 +5940,7 @@ fn zirArrayMul(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr } } const val = try Value.Tag.array.create(anon_decl.arena(), buf); - return sema.analyzeDeclRef(try anon_decl.finish( - final_ty, - val, - )); + return sema.analyzeDeclRef(try anon_decl.finish(final_ty, val)); } return sema.mod.fail(&block.base, lhs_src, "TODO array_mul more types of Values", .{}); } @@ -9979,7 +9975,7 @@ fn analyzeRef( var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); return sema.analyzeDeclRef(try anon_decl.finish( - operand_ty, + try operand_ty.copy(anon_decl.arena()), try val.copy(anon_decl.arena()), )); } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index fb8211f6b8..c4e1d48062 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -91,55 +91,76 @@ pub fn fmtIdent(ident: []const u8) std.fmt.Formatter(formatIdent) { return .{ .data = ident }; } -/// This data is available when outputting .c code for a Module. +/// This data is available when outputting .c code for a `*Module.Fn`. /// It is not available when generating .h file. -pub const Object = struct { - dg: DeclGen, +pub const Function = struct { air: Air, liveness: Liveness, - gpa: *mem.Allocator, - code: std.ArrayList(u8), value_map: CValueMap, blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{}, next_arg_index: usize = 0, next_local_index: usize = 0, next_block_index: usize = 0, - indent_writer: IndentWriter(std.ArrayList(u8).Writer), + object: Object, + func: *Module.Fn, - fn resolveInst(o: *Object, inst: Air.Inst.Ref) !CValue { - if (o.air.value(inst)) |_| { + fn resolveInst(f: *Function, inst: Air.Inst.Ref) !CValue { + if (f.air.value(inst)) |_| { return CValue{ .constant = inst }; } const index = Air.refToIndex(inst).?; - return o.value_map.get(index).?; // Assertion means instruction does not dominate usage. + return f.value_map.get(index).?; // Assertion means instruction does not dominate usage. } - fn allocLocalValue(o: *Object) CValue { - const result = o.next_local_index; - o.next_local_index += 1; + fn allocLocalValue(f: *Function) CValue { + const result = f.next_local_index; + f.next_local_index += 1; return .{ .local = result }; } - fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue { - const local_value = o.allocLocalValue(); - try o.renderTypeAndName(o.writer(), ty, local_value, mutability); + fn allocLocal(f: *Function, ty: Type, mutability: Mutability) !CValue { + const local_value = f.allocLocalValue(); + try f.object.renderTypeAndName(f.object.writer(), ty, local_value, mutability); return local_value; } + fn writeCValue(f: *Function, w: anytype, c_value: CValue) !void { + switch (c_value) { + .constant => |inst| { + const ty = f.air.typeOf(inst); + const val = f.air.value(inst).?; + return f.object.dg.renderValue(w, ty, val); + }, + else => return Object.writeCValue(w, c_value), + } + } + + fn fail(f: *Function, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { + return f.object.dg.fail(format, args); + } + + fn renderType(f: *Function, w: anytype, t: Type) !void { + return f.object.dg.renderType(w, t); + } +}; + +/// This data is available when outputting .c code for a `Module`. +/// It is not available when generating .h file. +pub const Object = struct { + dg: DeclGen, + code: std.ArrayList(u8), + indent_writer: IndentWriter(std.ArrayList(u8).Writer), + fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer { return o.indent_writer.writer(); } - fn writeCValue(o: *Object, w: anytype, c_value: CValue) !void { + fn writeCValue(w: anytype, c_value: CValue) !void { switch (c_value) { .none => unreachable, .local => |i| return w.print("t{d}", .{i}), .local_ref => |i| return w.print("&t{d}", .{i}), - .constant => |inst| { - const ty = o.air.typeOf(inst); - const val = o.air.value(inst).?; - return o.dg.renderValue(w, ty, val); - }, + .constant => unreachable, .arg => |i| return w.print("a{d}", .{i}), .decl => |decl| return w.writeAll(mem.span(decl.name)), .decl_ref => |decl| return w.print("&{s}", .{decl.name}), @@ -153,7 +174,7 @@ pub const Object = struct { name: CValue, mutability: Mutability, ) error{ OutOfMemory, AnalysisFail }!void { - var suffix = std.ArrayList(u8).init(o.gpa); + var suffix = std.ArrayList(u8).init(o.dg.gpa); defer suffix.deinit(); var render_ty = ty; @@ -177,7 +198,7 @@ pub const Object = struct { .Const => try w.writeAll("const "), .Mut => {}, } - try o.writeCValue(w, name); + try writeCValue(w, name); try w.writeAll(")("); const param_len = render_ty.fnParamLen(); const is_var_args = render_ty.fnIsVarArgs(); @@ -205,7 +226,7 @@ pub const Object = struct { .Mut => "", }; try w.print(" {s}", .{const_prefix}); - try o.writeCValue(w, name); + try writeCValue(w, name); } try w.writeAll(suffix.items); } @@ -213,11 +234,14 @@ pub const Object = struct { /// This data is available both when outputting .c code and when outputting an .h file. pub const DeclGen = struct { + gpa: *std.mem.Allocator, module: *Module, decl: *Decl, fwd_decl: std.ArrayList(u8), error_msg: ?*Module.ErrorMsg, + /// The key of this map is Type which has references to typedefs_arena. typedefs: TypedefMap, + typedefs_arena: *std.mem.Allocator, fn fail(dg: *DeclGen, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { @setCold(true); @@ -545,7 +569,10 @@ pub const DeclGen = struct { try dg.typedefs.ensureUnusedCapacity(1); try w.writeAll(name); - dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); + dg.typedefs.putAssumeCapacityNoClobber( + try t.copy(dg.typedefs_arena), + .{ .name = name, .rendered = rendered }, + ); } else { try dg.renderType(w, t.elemType()); try w.writeAll(" *"); @@ -586,7 +613,10 @@ pub const DeclGen = struct { try dg.typedefs.ensureUnusedCapacity(1); try w.writeAll(name); - dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); + dg.typedefs.putAssumeCapacityNoClobber( + try t.copy(dg.typedefs_arena), + .{ .name = name, .rendered = rendered }, + ); }, .ErrorSet => { comptime std.debug.assert(Type.initTag(.anyerror).abiSize(std.Target.current) == 2); @@ -626,7 +656,10 @@ pub const DeclGen = struct { try dg.typedefs.ensureUnusedCapacity(1); try w.writeAll(name); - dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); + dg.typedefs.putAssumeCapacityNoClobber( + try t.copy(dg.typedefs_arena), + .{ .name = name, .rendered = rendered }, + ); }, .Struct => { if (dg.typedefs.get(t)) |some| { @@ -659,7 +692,10 @@ pub const DeclGen = struct { try dg.typedefs.ensureUnusedCapacity(1); try w.writeAll(name); - dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); + dg.typedefs.putAssumeCapacityNoClobber( + try t.copy(dg.typedefs_arena), + .{ .name = name, .rendered = rendered }, + ); }, .Enum => { // For enums, we simply use the integer tag type. @@ -724,6 +760,29 @@ pub const DeclGen = struct { } }; +pub fn genFunc(f: *Function) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const o = &f.object; + const is_global = o.dg.module.decl_exports.contains(f.func.owner_decl); + const fwd_decl_writer = o.dg.fwd_decl.writer(); + if (is_global) { + try fwd_decl_writer.writeAll("ZIG_EXTERN_C "); + } + try o.dg.renderFunctionSignature(fwd_decl_writer, is_global); + try fwd_decl_writer.writeAll(";\n"); + + try o.indent_writer.insertNewline(); + try o.dg.renderFunctionSignature(o.writer(), is_global); + + try o.writer().writeByte(' '); + const main_body = f.air.getMainBody(); + try genBody(f, main_body); + + try o.indent_writer.insertNewline(); +} + pub fn genDecl(o: *Object) !void { const tracy = trace(@src()); defer tracy.end(); @@ -732,28 +791,6 @@ pub fn genDecl(o: *Object) !void { .ty = o.dg.decl.ty, .val = o.dg.decl.val, }; - if (tv.val.castTag(.function)) |func_payload| { - const func: *Module.Fn = func_payload.data; - if (func.owner_decl == o.dg.decl) { - const is_global = o.dg.declIsGlobal(tv); - const fwd_decl_writer = o.dg.fwd_decl.writer(); - if (is_global) { - try fwd_decl_writer.writeAll("ZIG_EXTERN_C "); - } - try o.dg.renderFunctionSignature(fwd_decl_writer, is_global); - try fwd_decl_writer.writeAll(";\n"); - - try o.indent_writer.insertNewline(); - try o.dg.renderFunctionSignature(o.writer(), is_global); - - try o.writer().writeByte(' '); - const main_body = o.air.getMainBody(); - try genBody(o, main_body); - - try o.indent_writer.insertNewline(); - return; - } - } if (tv.val.tag() == .extern_fn) { const writer = o.writer(); try writer.writeAll("ZIG_EXTERN_C "); @@ -821,250 +858,250 @@ pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { } } -fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfMemory }!void { - const writer = o.writer(); +fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfMemory }!void { + const writer = f.object.writer(); if (body.len == 0) { try writer.writeAll("{}"); return; } try writer.writeAll("{\n"); - o.indent_writer.pushIndent(); + f.object.indent_writer.pushIndent(); - const air_tags = o.air.instructions.items(.tag); + const air_tags = f.air.instructions.items(.tag); for (body) |inst| { const result_value = switch (air_tags[inst]) { // zig fmt: off .constant => unreachable, // excluded from function bodies .const_ty => unreachable, // excluded from function bodies - .arg => airArg(o), + .arg => airArg(f), - .breakpoint => try airBreakpoint(o), - .unreach => try airUnreach(o), - .fence => try airFence(o, inst), + .breakpoint => try airBreakpoint(f), + .unreach => try airUnreach(f), + .fence => try airFence(f, inst), // TODO use a different strategy for add that communicates to the optimizer // that wrapping is UB. - .add, .ptr_add => try airBinOp( o, inst, " + "), - .addwrap => try airWrapOp(o, inst, " + ", "addw_"), + .add, .ptr_add => try airBinOp( f, inst, " + "), + .addwrap => try airWrapOp(f, inst, " + ", "addw_"), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. - .sub, .ptr_sub => try airBinOp( o, inst, " - "), - .subwrap => try airWrapOp(o, inst, " - ", "subw_"), + .sub, .ptr_sub => try airBinOp( f, inst, " - "), + .subwrap => try airWrapOp(f, inst, " - ", "subw_"), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. - .mul => try airBinOp( o, inst, " * "), - .mulwrap => try airWrapOp(o, inst, " * ", "mulw_"), + .mul => try airBinOp( f, inst, " * "), + .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"), // TODO use a different strategy for div that communicates to the optimizer // that wrapping is UB. - .div => try airBinOp( o, inst, " / "), - .rem => try airBinOp( o, inst, " % "), + .div => try airBinOp( f, inst, " / "), + .rem => try airBinOp( f, inst, " % "), - .cmp_eq => try airBinOp(o, inst, " == "), - .cmp_gt => try airBinOp(o, inst, " > "), - .cmp_gte => try airBinOp(o, inst, " >= "), - .cmp_lt => try airBinOp(o, inst, " < "), - .cmp_lte => try airBinOp(o, inst, " <= "), - .cmp_neq => try airBinOp(o, inst, " != "), + .cmp_eq => try airBinOp(f, inst, " == "), + .cmp_gt => try airBinOp(f, inst, " > "), + .cmp_gte => try airBinOp(f, inst, " >= "), + .cmp_lt => try airBinOp(f, inst, " < "), + .cmp_lte => try airBinOp(f, inst, " <= "), + .cmp_neq => try airBinOp(f, inst, " != "), // bool_and and bool_or are non-short-circuit operations - .bool_and => try airBinOp(o, inst, " & "), - .bool_or => try airBinOp(o, inst, " | "), - .bit_and => try airBinOp(o, inst, " & "), - .bit_or => try airBinOp(o, inst, " | "), - .xor => try airBinOp(o, inst, " ^ "), + .bool_and => try airBinOp(f, inst, " & "), + .bool_or => try airBinOp(f, inst, " | "), + .bit_and => try airBinOp(f, inst, " & "), + .bit_or => try airBinOp(f, inst, " | "), + .xor => try airBinOp(f, inst, " ^ "), - .shr => try airBinOp(o, inst, " >> "), - .shl => try airBinOp(o, inst, " << "), + .shr => try airBinOp(f, inst, " >> "), + .shl => try airBinOp(f, inst, " << "), - .not => try airNot( o, inst), + .not => try airNot( f, inst), - .optional_payload => try airOptionalPayload(o, inst), - .optional_payload_ptr => try airOptionalPayload(o, inst), + .optional_payload => try airOptionalPayload(f, inst), + .optional_payload_ptr => try airOptionalPayload(f, inst), - .is_err => try airIsErr(o, inst, "", ".", "!="), - .is_non_err => try airIsErr(o, inst, "", ".", "=="), - .is_err_ptr => try airIsErr(o, inst, "*", "->", "!="), - .is_non_err_ptr => try airIsErr(o, inst, "*", "->", "=="), + .is_err => try airIsErr(f, inst, "", ".", "!="), + .is_non_err => try airIsErr(f, inst, "", ".", "=="), + .is_err_ptr => try airIsErr(f, inst, "*", "->", "!="), + .is_non_err_ptr => try airIsErr(f, inst, "*", "->", "=="), - .is_null => try airIsNull(o, inst, "==", ""), - .is_non_null => try airIsNull(o, inst, "!=", ""), - .is_null_ptr => try airIsNull(o, inst, "==", "[0]"), - .is_non_null_ptr => try airIsNull(o, inst, "!=", "[0]"), + .is_null => try airIsNull(f, inst, "==", ""), + .is_non_null => try airIsNull(f, inst, "!=", ""), + .is_null_ptr => try airIsNull(f, inst, "==", "[0]"), + .is_non_null_ptr => try airIsNull(f, inst, "!=", "[0]"), - .alloc => try airAlloc(o, inst), - .assembly => try airAsm(o, inst), - .block => try airBlock(o, inst), - .bitcast => try airBitcast(o, inst), - .call => try airCall(o, inst), - .dbg_stmt => try airDbgStmt(o, inst), - .intcast => try airIntCast(o, inst), - .trunc => try airTrunc(o, inst), - .bool_to_int => try airBoolToInt(o, inst), - .load => try airLoad(o, inst), - .ret => try airRet(o, inst), - .store => try airStore(o, inst), - .loop => try airLoop(o, inst), - .cond_br => try airCondBr(o, inst), - .br => try airBr(o, inst), - .switch_br => try airSwitchBr(o, inst), - .wrap_optional => try airWrapOptional(o, inst), - .struct_field_ptr => try airStructFieldPtr(o, inst), - .array_to_slice => try airArrayToSlice(o, inst), - .cmpxchg_weak => try airCmpxchg(o, inst, "weak"), - .cmpxchg_strong => try airCmpxchg(o, inst, "strong"), - .atomic_rmw => try airAtomicRmw(o, inst), - .atomic_load => try airAtomicLoad(o, inst), + .alloc => try airAlloc(f, inst), + .assembly => try airAsm(f, inst), + .block => try airBlock(f, inst), + .bitcast => try airBitcast(f, inst), + .call => try airCall(f, inst), + .dbg_stmt => try airDbgStmt(f, inst), + .intcast => try airIntCast(f, inst), + .trunc => try airTrunc(f, inst), + .bool_to_int => try airBoolToInt(f, inst), + .load => try airLoad(f, inst), + .ret => try airRet(f, inst), + .store => try airStore(f, inst), + .loop => try airLoop(f, inst), + .cond_br => try airCondBr(f, inst), + .br => try airBr(f, inst), + .switch_br => try airSwitchBr(f, inst), + .wrap_optional => try airWrapOptional(f, inst), + .struct_field_ptr => try airStructFieldPtr(f, inst), + .array_to_slice => try airArrayToSlice(f, inst), + .cmpxchg_weak => try airCmpxchg(f, inst, "weak"), + .cmpxchg_strong => try airCmpxchg(f, inst, "strong"), + .atomic_rmw => try airAtomicRmw(f, inst), + .atomic_load => try airAtomicLoad(f, inst), - .int_to_float, .float_to_int => try airSimpleCast(o, inst), + .int_to_float, .float_to_int => try airSimpleCast(f, inst), - .atomic_store_unordered => try airAtomicStore(o, inst, toMemoryOrder(.Unordered)), - .atomic_store_monotonic => try airAtomicStore(o, inst, toMemoryOrder(.Monotonic)), - .atomic_store_release => try airAtomicStore(o, inst, toMemoryOrder(.Release)), - .atomic_store_seq_cst => try airAtomicStore(o, inst, toMemoryOrder(.SeqCst)), + .atomic_store_unordered => try airAtomicStore(f, inst, toMemoryOrder(.Unordered)), + .atomic_store_monotonic => try airAtomicStore(f, inst, toMemoryOrder(.Monotonic)), + .atomic_store_release => try airAtomicStore(f, inst, toMemoryOrder(.Release)), + .atomic_store_seq_cst => try airAtomicStore(f, inst, toMemoryOrder(.SeqCst)), - .struct_field_ptr_index_0 => try airStructFieldPtrIndex(o, inst, 0), - .struct_field_ptr_index_1 => try airStructFieldPtrIndex(o, inst, 1), - .struct_field_ptr_index_2 => try airStructFieldPtrIndex(o, inst, 2), - .struct_field_ptr_index_3 => try airStructFieldPtrIndex(o, inst, 3), + .struct_field_ptr_index_0 => try airStructFieldPtrIndex(f, inst, 0), + .struct_field_ptr_index_1 => try airStructFieldPtrIndex(f, inst, 1), + .struct_field_ptr_index_2 => try airStructFieldPtrIndex(f, inst, 2), + .struct_field_ptr_index_3 => try airStructFieldPtrIndex(f, inst, 3), - .struct_field_val => try airStructFieldVal(o, inst), - .slice_ptr => try airSliceField(o, inst, ".ptr;\n"), - .slice_len => try airSliceField(o, inst, ".len;\n"), + .struct_field_val => try airStructFieldVal(f, inst), + .slice_ptr => try airSliceField(f, inst, ".ptr;\n"), + .slice_len => try airSliceField(f, inst, ".len;\n"), - .ptr_elem_val => try airPtrElemVal(o, inst, "["), - .ptr_ptr_elem_val => try airPtrElemVal(o, inst, "[0]["), - .ptr_elem_ptr => try airPtrElemPtr(o, inst), - .slice_elem_val => try airSliceElemVal(o, inst, "["), - .ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["), + .ptr_elem_val => try airPtrElemVal(f, inst, "["), + .ptr_ptr_elem_val => try airPtrElemVal(f, inst, "[0]["), + .ptr_elem_ptr => try airPtrElemPtr(f, inst), + .slice_elem_val => try airSliceElemVal(f, inst, "["), + .ptr_slice_elem_val => try airSliceElemVal(f, inst, "[0]["), - .unwrap_errunion_payload => try airUnwrapErrUnionPay(o, inst), - .unwrap_errunion_err => try airUnwrapErrUnionErr(o, inst), - .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(o, inst), - .unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(o, inst), - .wrap_errunion_payload => try airWrapErrUnionPay(o, inst), - .wrap_errunion_err => try airWrapErrUnionErr(o, inst), + .unwrap_errunion_payload => try airUnwrapErrUnionPay(f, inst), + .unwrap_errunion_err => try airUnwrapErrUnionErr(f, inst), + .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(f, inst), + .unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(f, inst), + .wrap_errunion_payload => try airWrapErrUnionPay(f, inst), + .wrap_errunion_err => try airWrapErrUnionErr(f, inst), - .ptrtoint => return o.dg.fail("TODO: C backend: implement codegen for ptrtoint", .{}), - .floatcast => return o.dg.fail("TODO: C backend: implement codegen for floatcast", .{}), + .ptrtoint => return f.fail("TODO: C backend: implement codegen for ptrtoint", .{}), + .floatcast => return f.fail("TODO: C backend: implement codegen for floatcast", .{}), // zig fmt: on }; switch (result_value) { .none => {}, - else => try o.value_map.putNoClobber(inst, result_value), + else => try f.value_map.putNoClobber(inst, result_value), } } - o.indent_writer.popIndent(); + f.object.indent_writer.popIndent(); try writer.writeAll("}"); } -fn airSliceField(o: *Object, inst: Air.Inst.Index, suffix: []const u8) !CValue { - if (o.liveness.isUnused(inst)) +fn airSliceField(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const operand = try o.resolveInst(ty_op.operand); - const writer = o.writer(); - const local = try o.allocLocal(Type.initTag(.usize), .Const); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const operand = try f.resolveInst(ty_op.operand); + const writer = f.object.writer(); + const local = try f.allocLocal(Type.initTag(.usize), .Const); try writer.writeAll(" = "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(suffix); return local; } -fn airPtrElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { +fn airPtrElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CValue { const is_volatile = false; // TODO - if (!is_volatile and o.liveness.isUnused(inst)) + if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; _ = prefix; - return o.dg.fail("TODO: C backend: airPtrElemVal", .{}); + return f.fail("TODO: C backend: airPtrElemVal", .{}); } -fn airPtrElemPtr(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - return o.dg.fail("TODO: C backend: airPtrElemPtr", .{}); + return f.fail("TODO: C backend: airPtrElemPtr", .{}); } -fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { +fn airSliceElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CValue { const is_volatile = false; // TODO - if (!is_volatile and o.liveness.isUnused(inst)) + if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; - const bin_op = o.air.instructions.items(.data)[inst].bin_op; - const slice = try o.resolveInst(bin_op.lhs); - const index = try o.resolveInst(bin_op.rhs); - const writer = o.writer(); - const local = try o.allocLocal(o.air.typeOfIndex(inst), .Const); + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const slice = try f.resolveInst(bin_op.lhs); + const index = try f.resolveInst(bin_op.rhs); + const writer = f.object.writer(); + const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); try writer.writeAll(" = "); - try o.writeCValue(writer, slice); + try f.writeCValue(writer, slice); try writer.writeAll(prefix); - try o.writeCValue(writer, index); + try f.writeCValue(writer, index); try writer.writeAll("];\n"); return local; } -fn airAlloc(o: *Object, inst: Air.Inst.Index) !CValue { - const writer = o.writer(); - const inst_ty = o.air.typeOfIndex(inst); +fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue { + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); // First line: the variable used as data storage. const elem_type = inst_ty.elemType(); const mutability: Mutability = if (inst_ty.isConstPtr()) .Const else .Mut; - const local = try o.allocLocal(elem_type, mutability); + const local = try f.allocLocal(elem_type, mutability); try writer.writeAll(";\n"); return CValue{ .local_ref = local.local }; } -fn airArg(o: *Object) CValue { - const i = o.next_arg_index; - o.next_arg_index += 1; +fn airArg(f: *Function) CValue { + const i = f.next_arg_index; + f.next_arg_index += 1; return .{ .arg = i }; } -fn airLoad(o: *Object, inst: Air.Inst.Index) !CValue { - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const is_volatile = o.air.typeOf(ty_op.operand).isVolatilePtr(); - if (!is_volatile and o.liveness.isUnused(inst)) +fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const is_volatile = f.air.typeOf(ty_op.operand).isVolatilePtr(); + if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; - const inst_ty = o.air.typeOfIndex(inst); - const operand = try o.resolveInst(ty_op.operand); - const writer = o.writer(); - const local = try o.allocLocal(inst_ty, .Const); + const inst_ty = f.air.typeOfIndex(inst); + const operand = try f.resolveInst(ty_op.operand); + const writer = f.object.writer(); + const local = try f.allocLocal(inst_ty, .Const); switch (operand) { .local_ref => |i| { const wrapped: CValue = .{ .local = i }; try writer.writeAll(" = "); - try o.writeCValue(writer, wrapped); + try f.writeCValue(writer, wrapped); try writer.writeAll(";\n"); }, .decl_ref => |decl| { const wrapped: CValue = .{ .decl = decl }; try writer.writeAll(" = "); - try o.writeCValue(writer, wrapped); + try f.writeCValue(writer, wrapped); try writer.writeAll(";\n"); }, else => { try writer.writeAll(" = *"); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(";\n"); }, } return local; } -fn airRet(o: *Object, inst: Air.Inst.Index) !CValue { - const un_op = o.air.instructions.items(.data)[inst].un_op; - const writer = o.writer(); - if (o.air.typeOf(un_op).hasCodeGenBits()) { - const operand = try o.resolveInst(un_op); +fn airRet(f: *Function, inst: Air.Inst.Index) !CValue { + const un_op = f.air.instructions.items(.data)[inst].un_op; + const writer = f.object.writer(); + if (f.air.typeOf(un_op).hasCodeGenBits()) { + const operand = try f.resolveInst(un_op); try writer.writeAll("return "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(";\n"); } else { try writer.writeAll("return;\n"); @@ -1072,75 +1109,75 @@ fn airRet(o: *Object, inst: Air.Inst.Index) !CValue { return CValue.none; } -fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const operand = try o.resolveInst(ty_op.operand); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const operand = try f.resolveInst(ty_op.operand); - const writer = o.writer(); - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = ("); - try o.dg.renderType(writer, inst_ty); + try f.renderType(writer, inst_ty); try writer.writeAll(")"); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } -fn airTrunc(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const operand = try o.resolveInst(ty_op.operand); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const operand = try f.resolveInst(ty_op.operand); _ = operand; - return o.dg.fail("TODO: C backend: airTrunc", .{}); + return f.fail("TODO: C backend: airTrunc", .{}); } -fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const un_op = o.air.instructions.items(.data)[inst].un_op; - const writer = o.writer(); - const inst_ty = o.air.typeOfIndex(inst); - const operand = try o.resolveInst(un_op); - const local = try o.allocLocal(inst_ty, .Const); + const un_op = f.air.instructions.items(.data)[inst].un_op; + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const operand = try f.resolveInst(un_op); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } -fn airStore(o: *Object, inst: Air.Inst.Index) !CValue { +fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { // *a = b; - const bin_op = o.air.instructions.items(.data)[inst].bin_op; - const dest_ptr = try o.resolveInst(bin_op.lhs); - const src_val = try o.resolveInst(bin_op.rhs); + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const dest_ptr = try f.resolveInst(bin_op.lhs); + const src_val = try f.resolveInst(bin_op.rhs); - const writer = o.writer(); + const writer = f.object.writer(); switch (dest_ptr) { .local_ref => |i| { const dest: CValue = .{ .local = i }; - try o.writeCValue(writer, dest); + try f.writeCValue(writer, dest); try writer.writeAll(" = "); - try o.writeCValue(writer, src_val); + try f.writeCValue(writer, src_val); try writer.writeAll(";\n"); }, .decl_ref => |decl| { const dest: CValue = .{ .decl = decl }; - try o.writeCValue(writer, dest); + try f.writeCValue(writer, dest); try writer.writeAll(" = "); - try o.writeCValue(writer, src_val); + try f.writeCValue(writer, src_val); try writer.writeAll(";\n"); }, else => { try writer.writeAll("*"); - try o.writeCValue(writer, dest_ptr); + try f.writeCValue(writer, dest_ptr); try writer.writeAll(" = "); - try o.writeCValue(writer, src_val); + try f.writeCValue(writer, src_val); try writer.writeAll(";\n"); }, } @@ -1148,17 +1185,17 @@ fn airStore(o: *Object, inst: Air.Inst.Index) !CValue { } fn airWrapOp( - o: *Object, + f: *Function, inst: Air.Inst.Index, str_op: [*:0]const u8, fn_op: [*:0]const u8, ) !CValue { - if (o.liveness.isUnused(inst)) + if (f.liveness.isUnused(inst)) return CValue.none; - const bin_op = o.air.instructions.items(.data)[inst].bin_op; - const inst_ty = o.air.typeOfIndex(inst); - const int_info = inst_ty.intInfo(o.dg.module.getTarget()); + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const inst_ty = f.air.typeOfIndex(inst); + const int_info = inst_ty.intInfo(f.object.dg.module.getTarget()); const bits = int_info.bits; // if it's an unsigned int with non-arbitrary bit size then we can just add @@ -1168,12 +1205,12 @@ fn airWrapOp( else => false, }; if (ok_bits or inst_ty.tag() != .int_unsigned) { - return try airBinOp(o, inst, str_op); + return try airBinOp(f, inst, str_op); } } if (bits > 64) { - return o.dg.fail("TODO: C backend: airWrapOp for large integers", .{}); + return f.fail("TODO: C backend: airWrapOp for large integers", .{}); } var min_buf: [80]u8 = undefined; @@ -1220,11 +1257,11 @@ fn airWrapOp( }, }; - const lhs = try o.resolveInst(bin_op.lhs); - const rhs = try o.resolveInst(bin_op.rhs); - const w = o.writer(); + const lhs = try f.resolveInst(bin_op.lhs); + const rhs = try f.resolveInst(bin_op.rhs); + const w = f.object.writer(); - const ret = try o.allocLocal(inst_ty, .Mut); + const ret = try f.allocLocal(inst_ty, .Mut); try w.print(" = zig_{s}", .{fn_op}); switch (inst_ty.tag()) { @@ -1250,71 +1287,71 @@ fn airWrapOp( } try w.writeByte('('); - try o.writeCValue(w, lhs); + try f.writeCValue(w, lhs); try w.writeAll(", "); - try o.writeCValue(w, rhs); + try f.writeCValue(w, rhs); if (int_info.signedness == .signed) { try w.print(", {s}", .{min}); } try w.print(", {s});", .{max}); - try o.indent_writer.insertNewline(); + try f.object.indent_writer.insertNewline(); return ret; } -fn airNot(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airNot(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const op = try o.resolveInst(ty_op.operand); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const op = try f.resolveInst(ty_op.operand); - const writer = o.writer(); - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); if (inst_ty.zigTypeTag() == .Bool) try writer.writeAll("!") else try writer.writeAll("~"); - try o.writeCValue(writer, op); + try f.writeCValue(writer, op); try writer.writeAll(";\n"); return local; } -fn airBinOp(o: *Object, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { - if (o.liveness.isUnused(inst)) +fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const bin_op = o.air.instructions.items(.data)[inst].bin_op; - const lhs = try o.resolveInst(bin_op.lhs); - const rhs = try o.resolveInst(bin_op.rhs); + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const lhs = try f.resolveInst(bin_op.lhs); + const rhs = try f.resolveInst(bin_op.rhs); - const writer = o.writer(); - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); - try o.writeCValue(writer, lhs); + try f.writeCValue(writer, lhs); try writer.print("{s}", .{operator}); - try o.writeCValue(writer, rhs); + try f.writeCValue(writer, rhs); try writer.writeAll(";\n"); return local; } -fn airCall(o: *Object, inst: Air.Inst.Index) !CValue { - const pl_op = o.air.instructions.items(.data)[inst].pl_op; - const extra = o.air.extraData(Air.Call, pl_op.payload); - const args = @bitCast([]const Air.Inst.Ref, o.air.extra[extra.end..][0..extra.data.args_len]); - const fn_ty = o.air.typeOf(pl_op.operand); +fn airCall(f: *Function, inst: Air.Inst.Index) !CValue { + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const extra = f.air.extraData(Air.Call, pl_op.payload); + const args = @bitCast([]const Air.Inst.Ref, f.air.extra[extra.end..][0..extra.data.args_len]); + const fn_ty = f.air.typeOf(pl_op.operand); const ret_ty = fn_ty.fnReturnType(); - const unused_result = o.liveness.isUnused(inst); - const writer = o.writer(); + const unused_result = f.liveness.isUnused(inst); + const writer = f.object.writer(); var result_local: CValue = .none; if (unused_result) { @@ -1322,11 +1359,11 @@ fn airCall(o: *Object, inst: Air.Inst.Index) !CValue { try writer.print("(void)", .{}); } } else { - result_local = try o.allocLocal(ret_ty, .Const); + result_local = try f.allocLocal(ret_ty, .Const); try writer.writeAll(" = "); } - if (o.air.value(pl_op.operand)) |func_val| { + if (f.air.value(pl_op.operand)) |func_val| { const fn_decl = if (func_val.castTag(.extern_fn)) |extern_fn| extern_fn.data else if (func_val.castTag(.function)) |func_payload| @@ -1336,8 +1373,8 @@ fn airCall(o: *Object, inst: Air.Inst.Index) !CValue { try writer.writeAll(mem.spanZ(fn_decl.name)); } else { - const callee = try o.resolveInst(pl_op.operand); - try o.writeCValue(writer, callee); + const callee = try f.resolveInst(pl_op.operand); + try f.writeCValue(writer, callee); } try writer.writeAll("("); @@ -1345,113 +1382,113 @@ fn airCall(o: *Object, inst: Air.Inst.Index) !CValue { if (i != 0) { try writer.writeAll(", "); } - if (o.air.value(arg)) |val| { - try o.dg.renderValue(writer, o.air.typeOf(arg), val); + if (f.air.value(arg)) |val| { + try f.object.dg.renderValue(writer, f.air.typeOf(arg), val); } else { - const val = try o.resolveInst(arg); - try o.writeCValue(writer, val); + const val = try f.resolveInst(arg); + try f.writeCValue(writer, val); } } try writer.writeAll(");\n"); return result_local; } -fn airDbgStmt(o: *Object, inst: Air.Inst.Index) !CValue { - const dbg_stmt = o.air.instructions.items(.data)[inst].dbg_stmt; - const writer = o.writer(); +fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue { + const dbg_stmt = f.air.instructions.items(.data)[inst].dbg_stmt; + const writer = f.object.writer(); try writer.print("#line {d}\n", .{dbg_stmt.line + 1}); return CValue.none; } -fn airBlock(o: *Object, inst: Air.Inst.Index) !CValue { - const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; - const extra = o.air.extraData(Air.Block, ty_pl.payload); - const body = o.air.extra[extra.end..][0..extra.data.body_len]; +fn airBlock(f: *Function, inst: Air.Inst.Index) !CValue { + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const extra = f.air.extraData(Air.Block, ty_pl.payload); + const body = f.air.extra[extra.end..][0..extra.data.body_len]; - const block_id: usize = o.next_block_index; - o.next_block_index += 1; - const writer = o.writer(); + const block_id: usize = f.next_block_index; + f.next_block_index += 1; + const writer = f.object.writer(); - const inst_ty = o.air.typeOfIndex(inst); - const result = if (inst_ty.tag() != .void and !o.liveness.isUnused(inst)) blk: { + const inst_ty = f.air.typeOfIndex(inst); + const result = if (inst_ty.tag() != .void and !f.liveness.isUnused(inst)) blk: { // allocate a location for the result - const local = try o.allocLocal(inst_ty, .Mut); + const local = try f.allocLocal(inst_ty, .Mut); try writer.writeAll(";\n"); break :blk local; } else CValue{ .none = {} }; - try o.blocks.putNoClobber(o.gpa, inst, .{ + try f.blocks.putNoClobber(f.object.dg.gpa, inst, .{ .block_id = block_id, .result = result, }); - try genBody(o, body); - try o.indent_writer.insertNewline(); + try genBody(f, body); + try f.object.indent_writer.insertNewline(); // label must be followed by an expression, add an empty one. try writer.print("zig_block_{d}:;\n", .{block_id}); return result; } -fn airBr(o: *Object, inst: Air.Inst.Index) !CValue { - const branch = o.air.instructions.items(.data)[inst].br; - const block = o.blocks.get(branch.block_inst).?; +fn airBr(f: *Function, inst: Air.Inst.Index) !CValue { + const branch = f.air.instructions.items(.data)[inst].br; + const block = f.blocks.get(branch.block_inst).?; const result = block.result; - const writer = o.writer(); + const writer = f.object.writer(); // If result is .none then the value of the block is unused. if (result != .none) { - const operand = try o.resolveInst(branch.operand); - try o.writeCValue(writer, result); + const operand = try f.resolveInst(branch.operand); + try f.writeCValue(writer, result); try writer.writeAll(" = "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(";\n"); } - try o.writer().print("goto zig_block_{d};\n", .{block.block_id}); + try f.object.writer().print("goto zig_block_{d};\n", .{block.block_id}); return CValue.none; } -fn airBitcast(o: *Object, inst: Air.Inst.Index) !CValue { - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const operand = try o.resolveInst(ty_op.operand); +fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const operand = try f.resolveInst(ty_op.operand); - const writer = o.writer(); - const inst_ty = o.air.typeOfIndex(inst); + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); if (inst_ty.zigTypeTag() == .Pointer and - o.air.typeOf(ty_op.operand).zigTypeTag() == .Pointer) + f.air.typeOf(ty_op.operand).zigTypeTag() == .Pointer) { - const local = try o.allocLocal(inst_ty, .Const); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = ("); - try o.dg.renderType(writer, inst_ty); + try f.renderType(writer, inst_ty); try writer.writeAll(")"); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } - const local = try o.allocLocal(inst_ty, .Mut); + const local = try f.allocLocal(inst_ty, .Mut); try writer.writeAll(";\n"); try writer.writeAll("memcpy(&"); - try o.writeCValue(writer, local); + try f.writeCValue(writer, local); try writer.writeAll(", &"); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(", sizeof "); - try o.writeCValue(writer, local); + try f.writeCValue(writer, local); try writer.writeAll(");\n"); return local; } -fn airBreakpoint(o: *Object) !CValue { - try o.writer().writeAll("zig_breakpoint();\n"); +fn airBreakpoint(f: *Function) !CValue { + try f.object.writer().writeAll("zig_breakpoint();\n"); return CValue.none; } -fn airFence(o: *Object, inst: Air.Inst.Index) !CValue { - const atomic_order = o.air.instructions.items(.data)[inst].fence; - const writer = o.writer(); +fn airFence(f: *Function, inst: Air.Inst.Index) !CValue { + const atomic_order = f.air.instructions.items(.data)[inst].fence; + const writer = f.object.writer(); try writer.writeAll("zig_fence("); try writeMemoryOrder(writer, atomic_order); @@ -1460,85 +1497,85 @@ fn airFence(o: *Object, inst: Air.Inst.Index) !CValue { return CValue.none; } -fn airUnreach(o: *Object) !CValue { - try o.writer().writeAll("zig_unreachable();\n"); +fn airUnreach(f: *Function) !CValue { + try f.object.writer().writeAll("zig_unreachable();\n"); return CValue.none; } -fn airLoop(o: *Object, inst: Air.Inst.Index) !CValue { - const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; - const loop = o.air.extraData(Air.Block, ty_pl.payload); - const body = o.air.extra[loop.end..][0..loop.data.body_len]; - try o.writer().writeAll("while (true) "); - try genBody(o, body); - try o.indent_writer.insertNewline(); +fn airLoop(f: *Function, inst: Air.Inst.Index) !CValue { + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const loop = f.air.extraData(Air.Block, ty_pl.payload); + const body = f.air.extra[loop.end..][0..loop.data.body_len]; + try f.object.writer().writeAll("while (true) "); + try genBody(f, body); + try f.object.indent_writer.insertNewline(); return CValue.none; } -fn airCondBr(o: *Object, inst: Air.Inst.Index) !CValue { - const pl_op = o.air.instructions.items(.data)[inst].pl_op; - const cond = try o.resolveInst(pl_op.operand); - const extra = o.air.extraData(Air.CondBr, pl_op.payload); - const then_body = o.air.extra[extra.end..][0..extra.data.then_body_len]; - const else_body = o.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; - const writer = o.writer(); +fn airCondBr(f: *Function, inst: Air.Inst.Index) !CValue { + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const cond = try f.resolveInst(pl_op.operand); + const extra = f.air.extraData(Air.CondBr, pl_op.payload); + const then_body = f.air.extra[extra.end..][0..extra.data.then_body_len]; + const else_body = f.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; + const writer = f.object.writer(); try writer.writeAll("if ("); - try o.writeCValue(writer, cond); + try f.writeCValue(writer, cond); try writer.writeAll(") "); - try genBody(o, then_body); + try genBody(f, then_body); try writer.writeAll(" else "); - try genBody(o, else_body); - try o.indent_writer.insertNewline(); + try genBody(f, else_body); + try f.object.indent_writer.insertNewline(); return CValue.none; } -fn airSwitchBr(o: *Object, inst: Air.Inst.Index) !CValue { - const pl_op = o.air.instructions.items(.data)[inst].pl_op; - const condition = try o.resolveInst(pl_op.operand); - const condition_ty = o.air.typeOf(pl_op.operand); - const switch_br = o.air.extraData(Air.SwitchBr, pl_op.payload); - const writer = o.writer(); +fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue { + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const condition = try f.resolveInst(pl_op.operand); + const condition_ty = f.air.typeOf(pl_op.operand); + const switch_br = f.air.extraData(Air.SwitchBr, pl_op.payload); + const writer = f.object.writer(); try writer.writeAll("switch ("); - try o.writeCValue(writer, condition); + try f.writeCValue(writer, condition); try writer.writeAll(") {"); - o.indent_writer.pushIndent(); + f.object.indent_writer.pushIndent(); var extra_index: usize = switch_br.end; var case_i: u32 = 0; while (case_i < switch_br.data.cases_len) : (case_i += 1) { - const case = o.air.extraData(Air.SwitchBr.Case, extra_index); - const items = @bitCast([]const Air.Inst.Ref, o.air.extra[case.end..][0..case.data.items_len]); - const case_body = o.air.extra[case.end + items.len ..][0..case.data.body_len]; + const case = f.air.extraData(Air.SwitchBr.Case, extra_index); + const items = @bitCast([]const Air.Inst.Ref, f.air.extra[case.end..][0..case.data.items_len]); + const case_body = f.air.extra[case.end + items.len ..][0..case.data.body_len]; extra_index = case.end + case.data.items_len + case_body.len; for (items) |item| { - try o.indent_writer.insertNewline(); + try f.object.indent_writer.insertNewline(); try writer.writeAll("case "); - try o.dg.renderValue(writer, condition_ty, o.air.value(item).?); + try f.object.dg.renderValue(writer, condition_ty, f.air.value(item).?); try writer.writeAll(": "); } // The case body must be noreturn so we don't need to insert a break. - try genBody(o, case_body); + try genBody(f, case_body); } - const else_body = o.air.extra[extra_index..][0..switch_br.data.else_body_len]; - try o.indent_writer.insertNewline(); + const else_body = f.air.extra[extra_index..][0..switch_br.data.else_body_len]; + try f.object.indent_writer.insertNewline(); try writer.writeAll("default: "); - try genBody(o, else_body); - try o.indent_writer.insertNewline(); + try genBody(f, else_body); + try f.object.indent_writer.insertNewline(); - o.indent_writer.popIndent(); + f.object.indent_writer.popIndent(); try writer.writeAll("}\n"); return CValue.none; } -fn airAsm(o: *Object, inst: Air.Inst.Index) !CValue { - const air_datas = o.air.instructions.items(.data); - const air_extra = o.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload); - const zir = o.dg.decl.namespace.file_scope.zir; +fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { + const air_datas = f.air.instructions.items(.data); + const air_extra = f.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload); + const zir = f.object.dg.decl.namespace.file_scope.zir; const extended = zir.instructions.items(.data)[air_extra.data.zir_index].extended; const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); @@ -1547,14 +1584,14 @@ fn airAsm(o: *Object, inst: Air.Inst.Index) !CValue { const clobbers_len = @truncate(u5, extended.small >> 10); _ = clobbers_len; // TODO honor these const is_volatile = @truncate(u1, extended.small >> 15) != 0; - const outputs = @bitCast([]const Air.Inst.Ref, o.air.extra[air_extra.end..][0..outputs_len]); - const args = @bitCast([]const Air.Inst.Ref, o.air.extra[air_extra.end + outputs.len ..][0..args_len]); + const outputs = @bitCast([]const Air.Inst.Ref, f.air.extra[air_extra.end..][0..outputs_len]); + const args = @bitCast([]const Air.Inst.Ref, f.air.extra[air_extra.end + outputs.len ..][0..args_len]); if (outputs_len > 1) { - return o.dg.fail("TODO implement codegen for asm with more than 1 output", .{}); + return f.fail("TODO implement codegen for asm with more than 1 output", .{}); } - if (o.liveness.isUnused(inst) and !is_volatile) + if (f.liveness.isUnused(inst) and !is_volatile) return CValue.none; var extra_i: usize = zir_extra.end; @@ -1569,28 +1606,28 @@ fn airAsm(o: *Object, inst: Air.Inst.Index) !CValue { }; const args_extra_begin = extra_i; - const writer = o.writer(); + const writer = f.object.writer(); for (args) |arg| { const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); extra_i = input.end; const constraint = zir.nullTerminatedString(input.data.constraint); if (constraint[0] == '{' and constraint[constraint.len - 1] == '}') { const reg = constraint[1 .. constraint.len - 1]; - const arg_c_value = try o.resolveInst(arg); + const arg_c_value = try f.resolveInst(arg); try writer.writeAll("register "); - try o.dg.renderType(writer, o.air.typeOf(arg)); + try f.renderType(writer, f.air.typeOf(arg)); try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); - try o.writeCValue(writer, arg_c_value); + try f.writeCValue(writer, arg_c_value); try writer.writeAll(";\n"); } else { - return o.dg.fail("TODO non-explicit inline asm regs", .{}); + return f.fail("TODO non-explicit inline asm regs", .{}); } } const volatile_string: []const u8 = if (is_volatile) "volatile " else ""; try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, asm_source }); if (output_constraint) |_| { - return o.dg.fail("TODO: CBE inline asm output", .{}); + return f.fail("TODO: CBE inline asm output", .{}); } if (args.len > 0) { if (output_constraint == null) { @@ -1616,30 +1653,30 @@ fn airAsm(o: *Object, inst: Air.Inst.Index) !CValue { } try writer.writeAll(");\n"); - if (o.liveness.isUnused(inst)) + if (f.liveness.isUnused(inst)) return CValue.none; - return o.dg.fail("TODO: C backend: inline asm expression result used", .{}); + return f.fail("TODO: C backend: inline asm expression result used", .{}); } fn airIsNull( - o: *Object, + f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8, deref_suffix: [*:0]const u8, ) !CValue { - if (o.liveness.isUnused(inst)) + if (f.liveness.isUnused(inst)) return CValue.none; - const un_op = o.air.instructions.items(.data)[inst].un_op; - const writer = o.writer(); - const operand = try o.resolveInst(un_op); + const un_op = f.air.instructions.items(.data)[inst].un_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(un_op); - const local = try o.allocLocal(Type.initTag(.bool), .Const); + const local = try f.allocLocal(Type.initTag(.bool), .Const); try writer.writeAll(" = ("); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); - if (o.air.typeOf(un_op).isPtrLikeOptional()) { + if (f.air.typeOf(un_op).isPtrLikeOptional()) { // operand is a regular pointer, test `operand !=/== NULL` try writer.print("){s} {s} NULL;\n", .{ deref_suffix, operator }); } else { @@ -1648,14 +1685,14 @@ fn airIsNull( return local; } -fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airOptionalPayload(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); - const operand_ty = o.air.typeOf(ty_op.operand); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(ty_op.operand); + const operand_ty = f.air.typeOf(ty_op.operand); const opt_ty = if (operand_ty.zigTypeTag() == .Pointer) operand_ty.elemType() @@ -1668,98 +1705,98 @@ fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue { return operand; } - const inst_ty = o.air.typeOfIndex(inst); + const inst_ty = f.air.typeOfIndex(inst); const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else "."; const maybe_addrof = if (inst_ty.zigTypeTag() == .Pointer) "&" else ""; - const local = try o.allocLocal(inst_ty, .Const); + const local = try f.allocLocal(inst_ty, .Const); try writer.print(" = {s}(", .{maybe_addrof}); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.print("){s}payload;\n", .{maybe_deref}); return local; } -fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airStructFieldPtr(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) // TODO this @as is needed because of a stage1 bug return @as(CValue, CValue.none); - const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; - const extra = o.air.extraData(Air.StructField, ty_pl.payload).data; - const struct_ptr = try o.resolveInst(extra.struct_operand); - const struct_ptr_ty = o.air.typeOf(extra.struct_operand); - return structFieldPtr(o, inst, struct_ptr_ty, struct_ptr, extra.field_index); + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const extra = f.air.extraData(Air.StructField, ty_pl.payload).data; + const struct_ptr = try f.resolveInst(extra.struct_operand); + const struct_ptr_ty = f.air.typeOf(extra.struct_operand); + return structFieldPtr(f, inst, struct_ptr_ty, struct_ptr, extra.field_index); } -fn airStructFieldPtrIndex(o: *Object, inst: Air.Inst.Index, index: u8) !CValue { - if (o.liveness.isUnused(inst)) +fn airStructFieldPtrIndex(f: *Function, inst: Air.Inst.Index, index: u8) !CValue { + if (f.liveness.isUnused(inst)) // TODO this @as is needed because of a stage1 bug return @as(CValue, CValue.none); - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const struct_ptr = try o.resolveInst(ty_op.operand); - const struct_ptr_ty = o.air.typeOf(ty_op.operand); - return structFieldPtr(o, inst, struct_ptr_ty, struct_ptr, index); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const struct_ptr = try f.resolveInst(ty_op.operand); + const struct_ptr_ty = f.air.typeOf(ty_op.operand); + return structFieldPtr(f, inst, struct_ptr_ty, struct_ptr, index); } -fn structFieldPtr(o: *Object, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue { - const writer = o.writer(); +fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue { + const writer = f.object.writer(); const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data; const field_name = struct_obj.fields.keys()[index]; - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); switch (struct_ptr) { .local_ref => |i| { try writer.print(" = &t{d}.{};\n", .{ i, fmtIdent(field_name) }); }, else => { try writer.writeAll(" = &"); - try o.writeCValue(writer, struct_ptr); + try f.writeCValue(writer, struct_ptr); try writer.print("->{};\n", .{fmtIdent(field_name)}); }, } return local; } -fn airStructFieldVal(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; - const extra = o.air.extraData(Air.StructField, ty_pl.payload).data; - const writer = o.writer(); - const struct_byval = try o.resolveInst(extra.struct_operand); - const struct_ty = o.air.typeOf(extra.struct_operand); + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const extra = f.air.extraData(Air.StructField, ty_pl.payload).data; + const writer = f.object.writer(); + const struct_byval = try f.resolveInst(extra.struct_operand); + const struct_ty = f.air.typeOf(extra.struct_operand); const struct_obj = struct_ty.castTag(.@"struct").?.data; const field_name = struct_obj.fields.keys()[extra.field_index]; - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); - try o.writeCValue(writer, struct_byval); + try f.writeCValue(writer, struct_byval); try writer.print(".{};\n", .{fmtIdent(field_name)}); return local; } // *(E!T) -> E NOT *E -fn airUnwrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airUnwrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const inst_ty = o.air.typeOfIndex(inst); - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); - const operand_ty = o.air.typeOf(ty_op.operand); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const inst_ty = f.air.typeOfIndex(inst); + const writer = f.object.writer(); + const operand = try f.resolveInst(ty_op.operand); + const operand_ty = f.air.typeOf(ty_op.operand); const payload_ty = operand_ty.errorUnionPayload(); if (!payload_ty.hasCodeGenBits()) { if (operand_ty.zigTypeTag() == .Pointer) { - const local = try o.allocLocal(inst_ty, .Const); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = *"); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } else { @@ -1769,172 +1806,172 @@ fn airUnwrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue { const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else "."; - const local = try o.allocLocal(inst_ty, .Const); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = ("); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.print("){s}error;\n", .{maybe_deref}); return local; } -fn airUnwrapErrUnionPay(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); - const operand_ty = o.air.typeOf(ty_op.operand); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(ty_op.operand); + const operand_ty = f.air.typeOf(ty_op.operand); const payload_ty = operand_ty.errorUnionPayload(); if (!payload_ty.hasCodeGenBits()) { return CValue.none; } - const inst_ty = o.air.typeOfIndex(inst); + const inst_ty = f.air.typeOfIndex(inst); const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else "."; const maybe_addrof = if (inst_ty.zigTypeTag() == .Pointer) "&" else ""; - const local = try o.allocLocal(inst_ty, .Const); + const local = try f.allocLocal(inst_ty, .Const); try writer.print(" = {s}(", .{maybe_addrof}); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.print("){s}payload;\n", .{maybe_deref}); return local; } -fn airWrapOptional(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airWrapOptional(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(ty_op.operand); - const inst_ty = o.air.typeOfIndex(inst); + const inst_ty = f.air.typeOfIndex(inst); if (inst_ty.isPtrLikeOptional()) { // the operand is just a regular pointer, no need to do anything special. return operand; } // .wrap_optional is used to convert non-optionals into optionals so it can never be null. - const local = try o.allocLocal(inst_ty, .Const); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = { .is_null = false, .payload ="); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll("};\n"); return local; } -fn airWrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const writer = o.writer(); - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const operand = try o.resolveInst(ty_op.operand); + const writer = f.object.writer(); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const operand = try f.resolveInst(ty_op.operand); - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = { .error = "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(" };\n"); return local; } -fn airWrapErrUnionPay(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(ty_op.operand); - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = { .error = 0, .payload = "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(" };\n"); return local; } fn airIsErr( - o: *Object, + f: *Function, inst: Air.Inst.Index, deref_prefix: [*:0]const u8, deref_suffix: [*:0]const u8, op_str: [*:0]const u8, ) !CValue { - if (o.liveness.isUnused(inst)) + if (f.liveness.isUnused(inst)) return CValue.none; - const un_op = o.air.instructions.items(.data)[inst].un_op; - const writer = o.writer(); - const operand = try o.resolveInst(un_op); - const operand_ty = o.air.typeOf(un_op); - const local = try o.allocLocal(Type.initTag(.bool), .Const); + const un_op = f.air.instructions.items(.data)[inst].un_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(un_op); + const operand_ty = f.air.typeOf(un_op); + const local = try f.allocLocal(Type.initTag(.bool), .Const); const payload_ty = operand_ty.errorUnionPayload(); if (!payload_ty.hasCodeGenBits()) { try writer.print(" = {s}", .{deref_prefix}); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.print(" {s} 0;\n", .{op_str}); } else { try writer.writeAll(" = "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.print("{s}error {s} 0;\n", .{ deref_suffix, op_str }); } return local; } -fn airArrayToSlice(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); - const array_len = o.air.typeOf(ty_op.operand).elemType().arrayLen(); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(ty_op.operand); + const array_len = f.air.typeOf(ty_op.operand).elemType().arrayLen(); try writer.writeAll(" = { .ptr = "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.print(", .len = {d} }};\n", .{array_len}); return local; } /// Emits a local variable with the result type and initializes it /// with the operand. -fn airSimpleCast(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) +fn airSimpleCast(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(ty_op.operand); try writer.writeAll(" = "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } -fn airCmpxchg(o: *Object, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue { - const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; - const extra = o.air.extraData(Air.Cmpxchg, ty_pl.payload).data; - const inst_ty = o.air.typeOfIndex(inst); - const ptr = try o.resolveInst(extra.ptr); - const expected_value = try o.resolveInst(extra.expected_value); - const new_value = try o.resolveInst(extra.new_value); - const local = try o.allocLocal(inst_ty, .Const); - const writer = o.writer(); +fn airCmpxchg(f: *Function, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue { + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const extra = f.air.extraData(Air.Cmpxchg, ty_pl.payload).data; + const inst_ty = f.air.typeOfIndex(inst); + const ptr = try f.resolveInst(extra.ptr); + const expected_value = try f.resolveInst(extra.expected_value); + const new_value = try f.resolveInst(extra.new_value); + const local = try f.allocLocal(inst_ty, .Const); + const writer = f.object.writer(); try writer.print(" = zig_cmpxchg_{s}(", .{flavor}); - try o.writeCValue(writer, ptr); + try f.writeCValue(writer, ptr); try writer.writeAll(", "); - try o.writeCValue(writer, expected_value); + try f.writeCValue(writer, expected_value); try writer.writeAll(", "); - try o.writeCValue(writer, new_value); + try f.writeCValue(writer, new_value); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.successOrder()); try writer.writeAll(", "); @@ -1944,19 +1981,19 @@ fn airCmpxchg(o: *Object, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue { return local; } -fn airAtomicRmw(o: *Object, inst: Air.Inst.Index) !CValue { - const pl_op = o.air.instructions.items(.data)[inst].pl_op; - const extra = o.air.extraData(Air.AtomicRmw, pl_op.payload).data; - const inst_ty = o.air.typeOfIndex(inst); - const ptr = try o.resolveInst(pl_op.operand); - const operand = try o.resolveInst(extra.operand); - const local = try o.allocLocal(inst_ty, .Const); - const writer = o.writer(); +fn airAtomicRmw(f: *Function, inst: Air.Inst.Index) !CValue { + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const extra = f.air.extraData(Air.AtomicRmw, pl_op.payload).data; + const inst_ty = f.air.typeOfIndex(inst); + const ptr = try f.resolveInst(pl_op.operand); + const operand = try f.resolveInst(extra.operand); + const local = try f.allocLocal(inst_ty, .Const); + const writer = f.object.writer(); try writer.print(" = zig_atomicrmw_{s}(", .{toAtomicRmwSuffix(extra.op())}); - try o.writeCValue(writer, ptr); + try f.writeCValue(writer, ptr); try writer.writeAll(", "); - try o.writeCValue(writer, operand); + try f.writeCValue(writer, operand); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.ordering()); try writer.writeAll(");\n"); @@ -1964,15 +2001,15 @@ fn airAtomicRmw(o: *Object, inst: Air.Inst.Index) !CValue { return local; } -fn airAtomicLoad(o: *Object, inst: Air.Inst.Index) !CValue { - const atomic_load = o.air.instructions.items(.data)[inst].atomic_load; - const inst_ty = o.air.typeOfIndex(inst); - const ptr = try o.resolveInst(atomic_load.ptr); - const local = try o.allocLocal(inst_ty, .Const); - const writer = o.writer(); +fn airAtomicLoad(f: *Function, inst: Air.Inst.Index) !CValue { + const atomic_load = f.air.instructions.items(.data)[inst].atomic_load; + const inst_ty = f.air.typeOfIndex(inst); + const ptr = try f.resolveInst(atomic_load.ptr); + const local = try f.allocLocal(inst_ty, .Const); + const writer = f.object.writer(); try writer.writeAll(" = zig_atomic_load("); - try o.writeCValue(writer, ptr); + try f.writeCValue(writer, ptr); try writer.writeAll(", "); try writeMemoryOrder(writer, atomic_load.order); try writer.writeAll(");\n"); @@ -1980,18 +2017,18 @@ fn airAtomicLoad(o: *Object, inst: Air.Inst.Index) !CValue { return local; } -fn airAtomicStore(o: *Object, inst: Air.Inst.Index, order: [*:0]const u8) !CValue { - const bin_op = o.air.instructions.items(.data)[inst].bin_op; - const ptr = try o.resolveInst(bin_op.lhs); - const element = try o.resolveInst(bin_op.rhs); - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); - const writer = o.writer(); +fn airAtomicStore(f: *Function, inst: Air.Inst.Index, order: [*:0]const u8) !CValue { + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const ptr = try f.resolveInst(bin_op.lhs); + const element = try f.resolveInst(bin_op.rhs); + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); + const writer = f.object.writer(); try writer.writeAll(" = zig_atomic_store("); - try o.writeCValue(writer, ptr); + try f.writeCValue(writer, ptr); try writer.writeAll(", "); - try o.writeCValue(writer, element); + try f.writeCValue(writer, element); try writer.print(", {s});\n", .{order}); return local; diff --git a/src/link.zig b/src/link.zig index e649101f08..4f21a10d18 100644 --- a/src/link.zig +++ b/src/link.zig @@ -149,7 +149,7 @@ pub const File = struct { coff: Coff.TextBlock, macho: MachO.TextBlock, plan9: Plan9.DeclBlock, - c: C.DeclBlock, + c: void, wasm: Wasm.DeclBlock, spirv: void, }; @@ -159,7 +159,7 @@ pub const File = struct { coff: Coff.SrcFn, macho: MachO.SrcFn, plan9: void, - c: C.FnBlock, + c: void, wasm: Wasm.FnData, spirv: SpirV.FnData, }; @@ -372,16 +372,18 @@ pub const File = struct { /// Must be called before any call to updateDecl or updateDeclExports for /// any given Decl. + /// TODO we're transitioning to deleting this function and instead having + /// each linker backend notice the first time updateDecl or updateFunc is called, or + /// a callee referenced from AIR. pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void { log.debug("allocateDeclIndexes {*} ({s})", .{ decl, decl.name }); switch (base.tag) { .coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl), .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl), - .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl), .wasm => return @fieldParentPtr(Wasm, "base", base).allocateDeclIndexes(decl), .plan9 => return @fieldParentPtr(Plan9, "base", base).allocateDeclIndexes(decl), - .spirv => {}, + .c, .spirv => {}, } } diff --git a/src/link/C.zig b/src/link/C.zig index 103cb60901..8689a6859a 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -21,30 +21,34 @@ base: link.File, /// This linker backend does not try to incrementally link output C source code. /// Instead, it tracks all declarations in this table, and iterates over it /// in the flush function, stitching pre-rendered pieces of C code together. -decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, void) = .{}, +decl_table: std.AutoArrayHashMapUnmanaged(*const Module.Decl, DeclBlock) = .{}, +/// Stores Type/Value data for `typedefs` to reference. +/// Accumulates allocations and then there is a periodic garbage collection after flush(). +arena: std.heap.ArenaAllocator, /// Per-declaration data. For functions this is the body, and /// the forward declaration is stored in the FnBlock. -pub const DeclBlock = struct { - code: std.ArrayListUnmanaged(u8), +const DeclBlock = struct { + code: std.ArrayListUnmanaged(u8) = .{}, + fwd_decl: std.ArrayListUnmanaged(u8) = .{}, + /// Each Decl stores a mapping of Zig Types to corresponding C types, for every + /// Zig Type used by the Decl. In flush(), we iterate over each Decl + /// and emit the typedef code for all types, making sure to not emit the same thing twice. + /// Any arena memory the Type points to lives in the `arena` field of `C`. + typedefs: codegen.TypedefMap.Unmanaged = .{}, - pub const empty: DeclBlock = .{ - .code = .{}, - }; + fn deinit(db: *DeclBlock, gpa: *Allocator) void { + db.code.deinit(gpa); + db.fwd_decl.deinit(gpa); + for (db.typedefs.values()) |typedef| { + gpa.free(typedef.rendered); + } + db.typedefs.deinit(gpa); + db.* = undefined; + } }; -/// Per-function data. -pub const FnBlock = struct { - fwd_decl: std.ArrayListUnmanaged(u8), - typedefs: codegen.TypedefMap.Unmanaged, - - pub const empty: FnBlock = .{ - .fwd_decl = .{}, - .typedefs = .{}, - }; -}; - -pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { +pub fn openPath(gpa: *Allocator, sub_path: []const u8, options: link.Options) !*C { assert(options.object_format == .c); if (options.use_llvm) return error.LLVMHasNoCBackend; @@ -57,15 +61,16 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio }); errdefer file.close(); - var c_file = try allocator.create(C); - errdefer allocator.destroy(c_file); + var c_file = try gpa.create(C); + errdefer gpa.destroy(c_file); c_file.* = C{ + .arena = std.heap.ArenaAllocator.init(gpa), .base = .{ .tag = .c, .options = options, .file = file, - .allocator = allocator, + .allocator = gpa, }, }; @@ -73,38 +78,105 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio } pub fn deinit(self: *C) void { - for (self.decl_table.keys()) |key| { - deinitDecl(self.base.allocator, key); - } - self.decl_table.deinit(self.base.allocator); -} + const gpa = self.base.allocator; -pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void { - _ = self; - _ = decl; + for (self.decl_table.values()) |*db| { + db.deinit(gpa); + } + self.decl_table.deinit(gpa); + + self.arena.deinit(); } pub fn freeDecl(self: *C, decl: *Module.Decl) void { - _ = self.decl_table.swapRemove(decl); - deinitDecl(self.base.allocator, decl); -} - -fn deinitDecl(gpa: *Allocator, decl: *Module.Decl) void { - decl.link.c.code.deinit(gpa); - decl.fn_link.c.fwd_decl.deinit(gpa); - for (decl.fn_link.c.typedefs.values()) |value| { - gpa.free(value.rendered); + const gpa = self.base.allocator; + if (self.decl_table.fetchSwapRemove(decl)) |*kv| { + kv.value.deinit(gpa); } - decl.fn_link.c.typedefs.deinit(gpa); } -pub fn finishUpdateDecl(self: *C, module: *Module, decl: *Module.Decl, air: Air, liveness: Liveness) !void { - // Keep track of all decls so we can iterate over them on flush(). - _ = try self.decl_table.getOrPut(self.base.allocator, decl); +pub fn updateFunc(self: *C, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void { + const tracy = trace(@src()); + defer tracy.end(); - const fwd_decl = &decl.fn_link.c.fwd_decl; - const typedefs = &decl.fn_link.c.typedefs; - const code = &decl.link.c.code; + const decl = func.owner_decl; + const gop = try self.decl_table.getOrPut(self.base.allocator, decl); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + const fwd_decl = &gop.value_ptr.fwd_decl; + const typedefs = &gop.value_ptr.typedefs; + const code = &gop.value_ptr.code; + fwd_decl.shrinkRetainingCapacity(0); + { + for (typedefs.values()) |value| { + module.gpa.free(value.rendered); + } + } + typedefs.clearRetainingCapacity(); + code.shrinkRetainingCapacity(0); + + var function: codegen.Function = .{ + .value_map = codegen.CValueMap.init(module.gpa), + .air = air, + .liveness = liveness, + .func = func, + .object = .{ + .dg = .{ + .gpa = module.gpa, + .module = module, + .error_msg = null, + .decl = decl, + .fwd_decl = fwd_decl.toManaged(module.gpa), + .typedefs = typedefs.promote(module.gpa), + .typedefs_arena = &self.arena.allocator, + }, + .code = code.toManaged(module.gpa), + .indent_writer = undefined, // set later so we can get a pointer to object.code + }, + }; + + function.object.indent_writer = .{ .underlying_writer = function.object.code.writer() }; + defer { + function.value_map.deinit(); + function.blocks.deinit(module.gpa); + function.object.code.deinit(); + function.object.dg.fwd_decl.deinit(); + for (function.object.dg.typedefs.values()) |value| { + module.gpa.free(value.rendered); + } + function.object.dg.typedefs.deinit(); + } + + codegen.genFunc(&function) catch |err| switch (err) { + error.AnalysisFail => { + try module.failed_decls.put(module.gpa, decl, function.object.dg.error_msg.?); + return; + }, + else => |e| return e, + }; + + fwd_decl.* = function.object.dg.fwd_decl.moveToUnmanaged(); + typedefs.* = function.object.dg.typedefs.unmanaged; + function.object.dg.typedefs.unmanaged = .{}; + code.* = function.object.code.moveToUnmanaged(); + + // Free excess allocated memory for this Decl. + fwd_decl.shrinkAndFree(module.gpa, fwd_decl.items.len); + code.shrinkAndFree(module.gpa, code.items.len); +} + +pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gop = try self.decl_table.getOrPut(self.base.allocator, decl); + if (!gop.found_existing) { + gop.value_ptr.* = .{}; + } + const fwd_decl = &gop.value_ptr.fwd_decl; + const typedefs = &gop.value_ptr.typedefs; + const code = &gop.value_ptr.code; fwd_decl.shrinkRetainingCapacity(0); { for (typedefs.values()) |value| { @@ -116,23 +188,19 @@ pub fn finishUpdateDecl(self: *C, module: *Module, decl: *Module.Decl, air: Air, var object: codegen.Object = .{ .dg = .{ + .gpa = module.gpa, .module = module, .error_msg = null, .decl = decl, .fwd_decl = fwd_decl.toManaged(module.gpa), .typedefs = typedefs.promote(module.gpa), + .typedefs_arena = &self.arena.allocator, }, - .gpa = module.gpa, .code = code.toManaged(module.gpa), - .value_map = codegen.CValueMap.init(module.gpa), .indent_writer = undefined, // set later so we can get a pointer to object.code - .air = air, - .liveness = liveness, }; object.indent_writer = .{ .underlying_writer = object.code.writer() }; defer { - object.value_map.deinit(); - object.blocks.deinit(module.gpa); object.code.deinit(); object.dg.fwd_decl.deinit(); for (object.dg.typedefs.values()) |value| { @@ -159,24 +227,12 @@ pub fn finishUpdateDecl(self: *C, module: *Module, decl: *Module.Decl, air: Air, code.shrinkAndFree(module.gpa, code.items.len); } -pub fn updateFunc(self: *C, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void { - const tracy = trace(@src()); - defer tracy.end(); - - return self.finishUpdateDecl(module, func.owner_decl, air, liveness); -} - -pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { - const tracy = trace(@src()); - defer tracy.end(); - - return self.finishUpdateDecl(module, decl, undefined, undefined); -} - pub fn updateDeclLineNumber(self: *C, module: *Module, decl: *Module.Decl) !void { // The C backend does not have the ability to fix line numbers without re-generating // the entire Decl. - return self.updateDecl(module, decl); + _ = self; + _ = module; + _ = decl; } pub fn flush(self: *C, comp: *Compilation) !void { @@ -223,32 +279,42 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { var typedefs = std.HashMap(Type, void, Type.HashContext64, std.hash_map.default_max_load_percentage).init(comp.gpa); defer typedefs.deinit(); - // Typedefs, forward decls and non-functions first. + // Typedefs, forward decls, and non-functions first. // TODO: performance investigation: would keeping a list of Decls that we should // generate, rather than querying here, be faster? - for (self.decl_table.keys()) |decl| { - if (!decl.has_tv) continue; - const buf = buf: { - if (decl.val.castTag(.function)) |_| { - try typedefs.ensureUnusedCapacity(@intCast(u32, decl.fn_link.c.typedefs.count())); - var it = decl.fn_link.c.typedefs.iterator(); - while (it.next()) |new| { - const gop = typedefs.getOrPutAssumeCapacity(new.key_ptr.*); - if (!gop.found_existing) { - try err_typedef_writer.writeAll(new.value_ptr.rendered); - } + const decl_keys = self.decl_table.keys(); + const decl_values = self.decl_table.values(); + for (decl_keys) |decl, i| { + if (!decl.has_tv) continue; // TODO do we really need this branch? + + const decl_block = &decl_values[i]; + + if (decl_block.fwd_decl.items.len != 0) { + try typedefs.ensureUnusedCapacity(@intCast(u32, decl_block.typedefs.count())); + var it = decl_block.typedefs.iterator(); + while (it.next()) |new| { + const gop = typedefs.getOrPutAssumeCapacity(new.key_ptr.*); + if (!gop.found_existing) { + try err_typedef_writer.writeAll(new.value_ptr.rendered); } - fn_count += 1; - break :buf decl.fn_link.c.fwd_decl.items; - } else { - break :buf decl.link.c.code.items; } - }; - all_buffers.appendAssumeCapacity(.{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }); - file_size += buf.len; + const buf = decl_block.fwd_decl.items; + all_buffers.appendAssumeCapacity(.{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }); + file_size += buf.len; + } + if (decl.getFunction() != null) { + fn_count += 1; + } else if (decl_block.code.items.len != 0) { + const buf = decl_block.code.items; + all_buffers.appendAssumeCapacity(.{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }); + file_size += buf.len; + } } err_typedef_item.* = .{ @@ -259,15 +325,17 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { // Now the function bodies. try all_buffers.ensureUnusedCapacity(fn_count); - for (self.decl_table.keys()) |decl| { - if (!decl.has_tv) continue; - if (decl.val.castTag(.function)) |_| { - const buf = decl.link.c.code.items; - all_buffers.appendAssumeCapacity(.{ - .iov_base = buf.ptr, - .iov_len = buf.len, - }); - file_size += buf.len; + for (decl_keys) |decl, i| { + if (decl.getFunction() != null) { + const decl_block = &decl_values[i]; + const buf = decl_block.code.items; + if (buf.len != 0) { + all_buffers.appendAssumeCapacity(.{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }); + file_size += buf.len; + } } } diff --git a/src/type.zig b/src/type.zig index 5d184ed2fc..d4993151df 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1366,10 +1366,6 @@ pub const Type = extern union { .f128, .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, .const_slice_u8, .array_u8_sentinel_0, @@ -1397,6 +1393,12 @@ pub const Type = extern union { .function => !self.castTag(.function).?.data.is_generic, + .fn_noreturn_no_args, + .fn_void_no_args, + .fn_naked_noreturn_no_args, + .fn_ccc_void_no_args, + => true, + .@"struct" => { // TODO introduce lazy value mechanism const struct_obj = self.castTag(.@"struct").?.data; From 8c86043178a30a75aa9ff58b4432964237fb52dc Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Tue, 21 Sep 2021 21:06:10 +0200 Subject: [PATCH 087/160] std.build: fix handling of -Dcpu Currently -Dcpu is completely ignored if -Dtarget isn't passed as well. Further, -Dcpu=baseline is ignored even if -Dtarget=native is passed. This patch fixes these 2 issues, always respecting the -Dcpu option if present. --- lib/std/build.zig | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index 7b976405dc..58fc94503f 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -684,7 +684,11 @@ pub const Builder = struct { ); const mcpu = self.option([]const u8, "cpu", "Target CPU features to add or subtract"); - const triple = maybe_triple orelse return args.default_target; + if (maybe_triple == null and mcpu == null) { + return args.default_target; + } + + const triple = maybe_triple orelse "native"; var diags: CrossTarget.ParseOptions.Diagnostics = .{}; const selected_target = CrossTarget.parse(.{ @@ -2432,11 +2436,8 @@ pub const LibExeObjStep = struct { if (populated_cpu_features.eql(cross.cpu.features)) { // The CPU name alone is sufficient. - // If it is the baseline CPU, no command line args are required. - if (cross.cpu.model != std.Target.Cpu.baseline(cross.cpu.arch).model) { - try zig_args.append("-mcpu"); - try zig_args.append(cross.cpu.model.name); - } + try zig_args.append("-mcpu"); + try zig_args.append(cross.cpu.model.name); } else { var mcpu_buffer = std.ArrayList(u8).init(builder.allocator); From 01f20c7f4891c7364486efb2d0f2e59df18dd1e4 Mon Sep 17 00:00:00 2001 From: Vincent Rischmann Date: Sun, 19 Sep 2021 21:10:49 +0200 Subject: [PATCH 088/160] io_uring: implement read_fixed/write_fixed --- lib/std/os/linux/io_uring.zig | 128 ++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/lib/std/os/linux/io_uring.zig b/lib/std/os/linux/io_uring.zig index a98ce009a9..9653e2a08c 100644 --- a/lib/std/os/linux/io_uring.zig +++ b/lib/std/os/linux/io_uring.zig @@ -404,6 +404,25 @@ pub const IO_Uring = struct { return sqe; } + /// Queues (but does not submit) an SQE to perform a IORING_OP_READ_FIXED. + /// The `buffer` provided must be registered with the kernel by calling `register_buffers` first. + /// The `buffer_index` must be the same as its index in the array provided to `register_buffers`. + /// + /// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases. + pub fn read_fixed( + self: *IO_Uring, + user_data: u64, + fd: os.fd_t, + buffer: *os.iovec, + offset: u64, + buffer_index: u16, + ) !*io_uring_sqe { + const sqe = try self.get_sqe(); + io_uring_prep_read_fixed(sqe, fd, buffer, offset, buffer_index); + sqe.user_data = user_data; + return sqe; + } + /// Queues (but does not submit) an SQE to perform a `pwritev()`. /// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases. /// For example, if you want to do a `pwritev2()` then set `rw_flags` on the returned SQE. @@ -421,6 +440,25 @@ pub const IO_Uring = struct { return sqe; } + /// Queues (but does not submit) an SQE to perform a IORING_OP_WRITE_FIXED. + /// The `buffer` provided must be registered with the kernel by calling `register_buffers` first. + /// The `buffer_index` must be the same as its index in the array provided to `register_buffers`. + /// + /// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases. + pub fn write_fixed( + self: *IO_Uring, + user_data: u64, + fd: os.fd_t, + buffer: *os.iovec, + offset: u64, + buffer_index: u16, + ) !*io_uring_sqe { + const sqe = try self.get_sqe(); + io_uring_prep_write_fixed(sqe, fd, buffer, offset, buffer_index); + sqe.user_data = user_data; + return sqe; + } + /// Queues (but does not submit) an SQE to perform an `accept4(2)` on a socket. /// Returns a pointer to the SQE. pub fn accept( @@ -674,6 +712,29 @@ pub const IO_Uring = struct { try handle_registration_result(res); } + /// Registers an array of buffers for use with `read_fixed` and `write_fixed`. + pub fn register_buffers(self: *IO_Uring, buffers: []const os.iovec) !void { + assert(self.fd >= 0); + const res = linux.io_uring_register( + self.fd, + .REGISTER_BUFFERS, + buffers.ptr, + @intCast(u32, buffers.len), + ); + try handle_registration_result(res); + } + + /// Unregister the registered buffers. + pub fn unregister_buffers(self: *IO_Uring) !void { + assert(self.fd >= 0); + const res = linux.io_uring_register(self.fd, .UNREGISTER_BUFFERS, null, 0); + switch (linux.getErrno(res)) { + .SUCCESS => {}, + .NXIO => return error.BuffersNotRegistered, + else => |errno| return os.unexpectedErrno(errno), + } + } + fn handle_registration_result(res: usize) !void { switch (linux.getErrno(res)) { .SUCCESS => {}, @@ -905,6 +966,16 @@ pub fn io_uring_prep_writev( io_uring_prep_rw(.WRITEV, sqe, fd, @ptrToInt(iovecs.ptr), iovecs.len, offset); } +pub fn io_uring_prep_read_fixed(sqe: *io_uring_sqe, fd: os.fd_t, buffer: *os.iovec, offset: u64, buffer_index: u16) void { + io_uring_prep_rw(.READ_FIXED, sqe, fd, @ptrToInt(buffer.iov_base), buffer.iov_len, offset); + sqe.buf_index = buffer_index; +} + +pub fn io_uring_prep_write_fixed(sqe: *io_uring_sqe, fd: os.fd_t, buffer: *os.iovec, offset: u64, buffer_index: u16) void { + io_uring_prep_rw(.WRITE_FIXED, sqe, fd, @ptrToInt(buffer.iov_base), buffer.iov_len, offset); + sqe.buf_index = buffer_index; +} + pub fn io_uring_prep_accept( sqe: *io_uring_sqe, fd: os.fd_t, @@ -1282,6 +1353,63 @@ test "write/read" { try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); } +test "write_fixed/read_fixed" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + + var ring = IO_Uring.init(2, 0) catch |err| switch (err) { + error.SystemOutdated => return error.SkipZigTest, + error.PermissionDenied => return error.SkipZigTest, + else => return err, + }; + defer ring.deinit(); + + const path = "test_io_uring_write_read_fixed"; + const file = try std.fs.cwd().createFile(path, .{ .read = true, .truncate = true }); + defer file.close(); + defer std.fs.cwd().deleteFile(path) catch {}; + const fd = file.handle; + + var raw_buffers: [2][11]u8 = undefined; + // First buffer will be written to the file. + std.mem.set(u8, &raw_buffers[0], 'z'); + std.mem.copy(u8, &raw_buffers[0], "foobar"); + + var buffers = [2]os.iovec{ + .{ .iov_base = &raw_buffers[0], .iov_len = raw_buffers[0].len }, + .{ .iov_base = &raw_buffers[1], .iov_len = raw_buffers[1].len }, + }; + try ring.register_buffers(&buffers); + + const sqe_write = try ring.write_fixed(0x45454545, fd, &buffers[0], 3, 0); + try testing.expectEqual(linux.IORING_OP.WRITE_FIXED, sqe_write.opcode); + try testing.expectEqual(@as(u64, 3), sqe_write.off); + sqe_write.flags |= linux.IOSQE_IO_LINK; + + const sqe_read = try ring.read_fixed(0x12121212, fd, &buffers[1], 0, 1); + try testing.expectEqual(linux.IORING_OP.READ_FIXED, sqe_read.opcode); + try testing.expectEqual(@as(u64, 0), sqe_read.off); + + try testing.expectEqual(@as(u32, 2), try ring.submit()); + + const cqe_write = try ring.copy_cqe(); + const cqe_read = try ring.copy_cqe(); + + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x45454545, + .res = @intCast(i32, buffers[0].iov_len), + .flags = 0, + }, cqe_write); + try testing.expectEqual(linux.io_uring_cqe{ + .user_data = 0x12121212, + .res = @intCast(i32, buffers[1].iov_len), + .flags = 0, + }, cqe_read); + + try testing.expectEqualSlices(u8, "\x00\x00\x00", buffers[1].iov_base[0..3]); + try testing.expectEqualSlices(u8, "foobar", buffers[1].iov_base[3..9]); + try testing.expectEqualSlices(u8, "zz", buffers[1].iov_base[9..11]); +} + test "openat" { if (builtin.os.tag != .linux) return error.SkipZigTest; From be71195bba13256f0e0a955833b2ada3a27492fc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 21 Sep 2021 17:02:15 -0700 Subject: [PATCH 089/160] stage2: enable f16 math There was panic that said TODO add __trunctfhf2 to compiler-rt, but I checked and that function has been in compiler-rt since April. --- src/value.zig | 39 +++++++++++++----------------- test/behavior.zig | 3 ++- test/behavior/widening.zig | 37 ---------------------------- test/behavior/widening_stage1.zig | 40 +++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 60 deletions(-) create mode 100644 test/behavior/widening_stage1.zig diff --git a/src/value.zig b/src/value.zig index 1075d2bb26..bdf5b9c37c 100644 --- a/src/value.zig +++ b/src/value.zig @@ -937,7 +937,7 @@ pub const Value = extern union { /// Asserts that the value is a float or an integer. pub fn toFloat(self: Value, comptime T: type) T { return switch (self.tag()) { - .float_16 => @panic("TODO soft float"), + .float_16 => @floatCast(T, self.castTag(.float_16).?.data), .float_32 => @floatCast(T, self.castTag(.float_32).?.data), .float_64 => @floatCast(T, self.castTag(.float_64).?.data), .float_128 => @floatCast(T, self.castTag(.float_128).?.data), @@ -1046,11 +1046,10 @@ pub const Value = extern union { pub fn floatCast(self: Value, allocator: *Allocator, dest_ty: Type) !Value { switch (dest_ty.tag()) { .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const res = try Value.Tag.float_16.create(allocator, self.toFloat(f16)); - //if (!self.eql(res)) - // return error.Overflow; - //return res; + const res = try Value.Tag.float_16.create(allocator, self.toFloat(f16)); + if (!self.eql(res, dest_ty)) + return error.Overflow; + return res; }, .f32 => { const res = try Value.Tag.float_32.create(allocator, self.toFloat(f32)); @@ -1901,10 +1900,9 @@ pub const Value = extern union { ) !Value { switch (float_type.tag()) { .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val + rhs_val); + const lhs_val = lhs.toFloat(f16); + const rhs_val = rhs.toFloat(f16); + return Value.Tag.float_16.create(arena, lhs_val + rhs_val); }, .f32 => { const lhs_val = lhs.toFloat(f32); @@ -1933,10 +1931,9 @@ pub const Value = extern union { ) !Value { switch (float_type.tag()) { .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val - rhs_val); + const lhs_val = lhs.toFloat(f16); + const rhs_val = rhs.toFloat(f16); + return Value.Tag.float_16.create(arena, lhs_val - rhs_val); }, .f32 => { const lhs_val = lhs.toFloat(f32); @@ -1965,10 +1962,9 @@ pub const Value = extern union { ) !Value { switch (float_type.tag()) { .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val / rhs_val); + const lhs_val = lhs.toFloat(f16); + const rhs_val = rhs.toFloat(f16); + return Value.Tag.float_16.create(arena, lhs_val / rhs_val); }, .f32 => { const lhs_val = lhs.toFloat(f32); @@ -1997,10 +1993,9 @@ pub const Value = extern union { ) !Value { switch (float_type.tag()) { .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val * rhs_val); + const lhs_val = lhs.toFloat(f16); + const rhs_val = rhs.toFloat(f16); + return Value.Tag.float_16.create(arena, lhs_val * rhs_val); }, .f32 => { const lhs_val = lhs.toFloat(f32); diff --git a/test/behavior.zig b/test/behavior.zig index ee3a789c39..f328db968e 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -13,6 +13,7 @@ test { _ = @import("behavior/atomics.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); _ = @import("behavior/translate_c_macros.zig"); + _ = @import("behavior/widening.zig"); if (builtin.zig_is_stage2) { // When all comptime_memory.zig tests pass, #9646 can be closed. @@ -157,7 +158,7 @@ test { _ = @import("behavior/wasm.zig"); } _ = @import("behavior/while.zig"); - _ = @import("behavior/widening.zig"); + _ = @import("behavior/widening_stage1.zig"); _ = @import("behavior/src.zig"); _ = @import("behavior/translate_c_macros_stage1.zig"); } diff --git a/test/behavior/widening.zig b/test/behavior/widening.zig index 19abf767b8..daa592e64c 100644 --- a/test/behavior/widening.zig +++ b/test/behavior/widening.zig @@ -11,40 +11,3 @@ test "integer widening" { var f: u128 = e; try expect(f == a); } - -test "implicit unsigned integer to signed integer" { - var a: u8 = 250; - var b: i16 = a; - try expect(b == 250); -} - -test "float widening" { - var a: f16 = 12.34; - var b: f32 = a; - var c: f64 = b; - var d: f128 = c; - try expect(a == b); - try expect(b == c); - try expect(c == d); -} - -test "float widening f16 to f128" { - // TODO https://github.com/ziglang/zig/issues/3282 - if (@import("builtin").target.cpu.arch == .aarch64) return error.SkipZigTest; - if (@import("builtin").target.cpu.arch == .powerpc64le) return error.SkipZigTest; - - var x: f16 = 12.34; - var y: f128 = x; - try expect(x == y); -} - -test "cast small unsigned to larger signed" { - try expect(castSmallUnsignedToLargerSigned1(200) == @as(i16, 200)); - try expect(castSmallUnsignedToLargerSigned2(9999) == @as(i64, 9999)); -} -fn castSmallUnsignedToLargerSigned1(x: u8) i16 { - return x; -} -fn castSmallUnsignedToLargerSigned2(x: u16) i64 { - return x; -} diff --git a/test/behavior/widening_stage1.zig b/test/behavior/widening_stage1.zig new file mode 100644 index 0000000000..5b5bc67e45 --- /dev/null +++ b/test/behavior/widening_stage1.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const expect = std.testing.expect; +const mem = std.mem; + +test "implicit unsigned integer to signed integer" { + var a: u8 = 250; + var b: i16 = a; + try expect(b == 250); +} + +test "float widening" { + var a: f16 = 12.34; + var b: f32 = a; + var c: f64 = b; + var d: f128 = c; + try expect(a == b); + try expect(b == c); + try expect(c == d); +} + +test "float widening f16 to f128" { + // TODO https://github.com/ziglang/zig/issues/3282 + if (@import("builtin").stage2_arch == .aarch64) return error.SkipZigTest; + if (@import("builtin").stage2_arch == .powerpc64le) return error.SkipZigTest; + + var x: f16 = 12.34; + var y: f128 = x; + try expect(x == y); +} + +test "cast small unsigned to larger signed" { + try expect(castSmallUnsignedToLargerSigned1(200) == @as(i16, 200)); + try expect(castSmallUnsignedToLargerSigned2(9999) == @as(i64, 9999)); +} +fn castSmallUnsignedToLargerSigned1(x: u8) i16 { + return x; +} +fn castSmallUnsignedToLargerSigned2(x: u16) i64 { + return x; +} From 0e2b9ac7770df07212d4d1cbfb15c3aaed0bef18 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 21 Sep 2021 17:24:55 -0700 Subject: [PATCH 090/160] stage2: fix unsigned integer to signed integer coercion --- src/Sema.zig | 2 +- src/codegen/llvm.zig | 18 +++++++++++++----- test/behavior/widening.zig | 6 ++++++ test/behavior/widening_stage1.zig | 6 ------ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index f6bea69129..91d12b7b31 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9565,7 +9565,7 @@ fn coerce( const src_info = inst_ty.intInfo(target); if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or // small enough unsigned ints can get casted to large enough signed ints - (src_info.signedness == .signed and dst_info.signedness == .unsigned and dst_info.bits > src_info.bits)) + (dst_info.signedness == .signed and dst_info.bits > src_info.bits)) { try sema.requireRuntimeBlock(block, inst_src); return block.addTyOp(.intcast, dest_type, inst); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 29efa27685..3a977bc582 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2032,14 +2032,22 @@ pub const FuncGen = struct { if (self.liveness.isUnused(inst)) return null; + const target = self.dg.module.getTarget(); const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const dest_ty = self.air.typeOfIndex(inst); + const dest_info = dest_ty.intInfo(target); + const dest_llvm_ty = try self.dg.llvmType(dest_ty); const operand = try self.resolveInst(ty_op.operand); - const inst_ty = self.air.typeOfIndex(inst); + const operand_ty = self.air.typeOf(ty_op.operand); + const operand_info = operand_ty.intInfo(target); - const signed = inst_ty.isSignedInt(); - // TODO: Should we use intcast here or just a simple bitcast? - // LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes - return self.builder.buildIntCast2(operand, try self.dg.llvmType(inst_ty), llvm.Bool.fromBool(signed), ""); + if (operand_info.bits < dest_info.bits) { + switch (operand_info.signedness) { + .signed => return self.builder.buildSExt(operand, dest_llvm_ty, ""), + .unsigned => return self.builder.buildZExt(operand, dest_llvm_ty, ""), + } + } + return self.builder.buildTrunc(operand, dest_llvm_ty, ""); } fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { diff --git a/test/behavior/widening.zig b/test/behavior/widening.zig index daa592e64c..efcbab9883 100644 --- a/test/behavior/widening.zig +++ b/test/behavior/widening.zig @@ -11,3 +11,9 @@ test "integer widening" { var f: u128 = e; try expect(f == a); } + +test "implicit unsigned integer to signed integer" { + var a: u8 = 250; + var b: i16 = a; + try expect(b == 250); +} diff --git a/test/behavior/widening_stage1.zig b/test/behavior/widening_stage1.zig index 5b5bc67e45..0cec3988cb 100644 --- a/test/behavior/widening_stage1.zig +++ b/test/behavior/widening_stage1.zig @@ -2,12 +2,6 @@ const std = @import("std"); const expect = std.testing.expect; const mem = std.mem; -test "implicit unsigned integer to signed integer" { - var a: u8 = 250; - var b: i16 = a; - try expect(b == 250); -} - test "float widening" { var a: f16 = 12.34; var b: f32 = a; From aecebf38acc8835db21eeea7b53e4ee26ec739a8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 21 Sep 2021 22:33:00 -0700 Subject: [PATCH 091/160] stage2: progress towards ability to compile compiler-rt * prepare compiler-rt to support being compiled by stage2 - put in a few minor workarounds that will be removed later, such as using `builtin.stage2_arch` rather than `builtin.cpu.arch`. - only try to export a few symbols for now - we'll move more symbols over to the "working in stage2" section as they become functional and gain test coverage. - use `inline fn` at function declarations rather than `@call` with an always_inline modifier at the callsites, to avoid depending on the anonymous array literal syntax language feature (for now). * AIR: replace floatcast instruction with fptrunc and fpext for shortening and widening floating point values, respectively. * Introduce a new ZIR instruction, `export_value`, which implements `@export` for the case when the thing to be exported is a local comptime value that points to a function. - AstGen: fix `@export` not properly reporting ambiguous decl references. * Sema: handle ExportOptions linkage. The value is now available to all backends. - Implement setting global linkage as appropriate in the LLVM backend. I did not yet inspect the LLVM IR, so this still needs to be audited. There is already a pending task to make sure the alias stuff is working as intended, and this is related. - Sema almost handles section, just a tiny bit more code is needed in `resolveExportOptions`. * Sema: implement float widening and shortening for both `@floatCast` and float coercion. - Implement the LLVM backend code for this as well. --- lib/std/special/compiler_rt.zig | 1184 ++++++++++--------- lib/std/special/compiler_rt/extendXfYf2.zig | 12 +- src/Air.zig | 10 +- src/AstGen.zig | 74 +- src/Liveness.zig | 3 +- src/Module.zig | 42 +- src/Sema.zig | 138 ++- src/Zir.zig | 14 +- src/codegen.zig | 15 +- src/codegen/c.zig | 10 +- src/codegen/llvm.zig | 36 +- src/codegen/llvm/bindings.zig | 16 + src/print_air.zig | 3 +- src/print_zir.zig | 12 + src/stage1/codegen.cpp | 1 + src/value.zig | 29 +- test/behavior.zig | 4 +- test/behavior/union.zig | 813 ------------- test/behavior/union_stage1.zig | 799 +++++++++++++ test/behavior/widening.zig | 43 + test/behavior/widening_stage1.zig | 34 - 21 files changed, 1734 insertions(+), 1558 deletions(-) create mode 100644 test/behavior/union_stage1.zig delete mode 100644 test/behavior/widening_stage1.zig diff --git a/lib/std/special/compiler_rt.zig b/lib/std/special/compiler_rt.zig index ed7f9d0c1c..2fb68f85dc 100644 --- a/lib/std/special/compiler_rt.zig +++ b/lib/std/special/compiler_rt.zig @@ -1,171 +1,24 @@ const std = @import("std"); -const builtin = std.builtin; +const builtin = @import("builtin"); const is_test = builtin.is_test; const os_tag = std.Target.current.os.tag; -const arch = std.Target.current.cpu.arch; +const arch = builtin.stage2_arch; const abi = std.Target.current.abi; const is_gnu = abi.isGnu(); const is_mingw = os_tag == .windows and is_gnu; +const linkage = if (is_test) + std.builtin.GlobalLinkage.Internal +else + std.builtin.GlobalLinkage.Weak; + +const strong_linkage = if (is_test) + std.builtin.GlobalLinkage.Internal +else + std.builtin.GlobalLinkage.Strong; + comptime { - const linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Weak; - const strong_linkage = if (is_test) builtin.GlobalLinkage.Internal else builtin.GlobalLinkage.Strong; - - switch (arch) { - .i386, - .x86_64, - => { - const zig_probe_stack = @import("compiler_rt/stack_probe.zig").zig_probe_stack; - @export(zig_probe_stack, .{ - .name = "__zig_probe_stack", - .linkage = linkage, - }); - }, - - else => {}, - } - - // __clear_cache manages its own logic about whether to be exported or not. - _ = @import("compiler_rt/clear_cache.zig").clear_cache; - - const __lesf2 = @import("compiler_rt/compareXf2.zig").__lesf2; - @export(__lesf2, .{ .name = "__lesf2", .linkage = linkage }); - const __ledf2 = @import("compiler_rt/compareXf2.zig").__ledf2; - @export(__ledf2, .{ .name = "__ledf2", .linkage = linkage }); - const __letf2 = @import("compiler_rt/compareXf2.zig").__letf2; - @export(__letf2, .{ .name = "__letf2", .linkage = linkage }); - - const __gesf2 = @import("compiler_rt/compareXf2.zig").__gesf2; - @export(__gesf2, .{ .name = "__gesf2", .linkage = linkage }); - const __gedf2 = @import("compiler_rt/compareXf2.zig").__gedf2; - @export(__gedf2, .{ .name = "__gedf2", .linkage = linkage }); - const __getf2 = @import("compiler_rt/compareXf2.zig").__getf2; - @export(__getf2, .{ .name = "__getf2", .linkage = linkage }); - - if (!is_test) { - @export(__lesf2, .{ .name = "__cmpsf2", .linkage = linkage }); - @export(__ledf2, .{ .name = "__cmpdf2", .linkage = linkage }); - @export(__letf2, .{ .name = "__cmptf2", .linkage = linkage }); - - const __eqsf2 = @import("compiler_rt/compareXf2.zig").__eqsf2; - @export(__eqsf2, .{ .name = "__eqsf2", .linkage = linkage }); - const __eqdf2 = @import("compiler_rt/compareXf2.zig").__eqdf2; - @export(__eqdf2, .{ .name = "__eqdf2", .linkage = linkage }); - @export(__letf2, .{ .name = "__eqtf2", .linkage = linkage }); - - const __ltsf2 = @import("compiler_rt/compareXf2.zig").__ltsf2; - @export(__ltsf2, .{ .name = "__ltsf2", .linkage = linkage }); - const __ltdf2 = @import("compiler_rt/compareXf2.zig").__ltdf2; - @export(__ltdf2, .{ .name = "__ltdf2", .linkage = linkage }); - @export(__letf2, .{ .name = "__lttf2", .linkage = linkage }); - - const __nesf2 = @import("compiler_rt/compareXf2.zig").__nesf2; - @export(__nesf2, .{ .name = "__nesf2", .linkage = linkage }); - const __nedf2 = @import("compiler_rt/compareXf2.zig").__nedf2; - @export(__nedf2, .{ .name = "__nedf2", .linkage = linkage }); - @export(__letf2, .{ .name = "__netf2", .linkage = linkage }); - - const __gtsf2 = @import("compiler_rt/compareXf2.zig").__gtsf2; - @export(__gtsf2, .{ .name = "__gtsf2", .linkage = linkage }); - const __gtdf2 = @import("compiler_rt/compareXf2.zig").__gtdf2; - @export(__gtdf2, .{ .name = "__gtdf2", .linkage = linkage }); - @export(__getf2, .{ .name = "__gttf2", .linkage = linkage }); - - const __extendhfsf2 = @import("compiler_rt/extendXfYf2.zig").__extendhfsf2; - @export(__extendhfsf2, .{ .name = "__gnu_h2f_ieee", .linkage = linkage }); - const __truncsfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncsfhf2; - @export(__truncsfhf2, .{ .name = "__gnu_f2h_ieee", .linkage = linkage }); - } - - const __unordsf2 = @import("compiler_rt/compareXf2.zig").__unordsf2; - @export(__unordsf2, .{ .name = "__unordsf2", .linkage = linkage }); - const __unorddf2 = @import("compiler_rt/compareXf2.zig").__unorddf2; - @export(__unorddf2, .{ .name = "__unorddf2", .linkage = linkage }); - const __unordtf2 = @import("compiler_rt/compareXf2.zig").__unordtf2; - @export(__unordtf2, .{ .name = "__unordtf2", .linkage = linkage }); - - const __addsf3 = @import("compiler_rt/addXf3.zig").__addsf3; - @export(__addsf3, .{ .name = "__addsf3", .linkage = linkage }); - const __adddf3 = @import("compiler_rt/addXf3.zig").__adddf3; - @export(__adddf3, .{ .name = "__adddf3", .linkage = linkage }); - const __addtf3 = @import("compiler_rt/addXf3.zig").__addtf3; - @export(__addtf3, .{ .name = "__addtf3", .linkage = linkage }); - const __subsf3 = @import("compiler_rt/addXf3.zig").__subsf3; - @export(__subsf3, .{ .name = "__subsf3", .linkage = linkage }); - const __subdf3 = @import("compiler_rt/addXf3.zig").__subdf3; - @export(__subdf3, .{ .name = "__subdf3", .linkage = linkage }); - const __subtf3 = @import("compiler_rt/addXf3.zig").__subtf3; - @export(__subtf3, .{ .name = "__subtf3", .linkage = linkage }); - - const __mulsf3 = @import("compiler_rt/mulXf3.zig").__mulsf3; - @export(__mulsf3, .{ .name = "__mulsf3", .linkage = linkage }); - const __muldf3 = @import("compiler_rt/mulXf3.zig").__muldf3; - @export(__muldf3, .{ .name = "__muldf3", .linkage = linkage }); - const __multf3 = @import("compiler_rt/mulXf3.zig").__multf3; - @export(__multf3, .{ .name = "__multf3", .linkage = linkage }); - - const __divsf3 = @import("compiler_rt/divsf3.zig").__divsf3; - @export(__divsf3, .{ .name = "__divsf3", .linkage = linkage }); - const __divdf3 = @import("compiler_rt/divdf3.zig").__divdf3; - @export(__divdf3, .{ .name = "__divdf3", .linkage = linkage }); - const __divtf3 = @import("compiler_rt/divtf3.zig").__divtf3; - @export(__divtf3, .{ .name = "__divtf3", .linkage = linkage }); - - const __ashldi3 = @import("compiler_rt/shift.zig").__ashldi3; - @export(__ashldi3, .{ .name = "__ashldi3", .linkage = linkage }); - const __ashlti3 = @import("compiler_rt/shift.zig").__ashlti3; - @export(__ashlti3, .{ .name = "__ashlti3", .linkage = linkage }); - const __ashrdi3 = @import("compiler_rt/shift.zig").__ashrdi3; - @export(__ashrdi3, .{ .name = "__ashrdi3", .linkage = linkage }); - const __ashrti3 = @import("compiler_rt/shift.zig").__ashrti3; - @export(__ashrti3, .{ .name = "__ashrti3", .linkage = linkage }); - const __lshrdi3 = @import("compiler_rt/shift.zig").__lshrdi3; - @export(__lshrdi3, .{ .name = "__lshrdi3", .linkage = linkage }); - const __lshrti3 = @import("compiler_rt/shift.zig").__lshrti3; - @export(__lshrti3, .{ .name = "__lshrti3", .linkage = linkage }); - - const __floatsidf = @import("compiler_rt/floatsiXf.zig").__floatsidf; - @export(__floatsidf, .{ .name = "__floatsidf", .linkage = linkage }); - const __floatsisf = @import("compiler_rt/floatsiXf.zig").__floatsisf; - @export(__floatsisf, .{ .name = "__floatsisf", .linkage = linkage }); - const __floatdidf = @import("compiler_rt/floatdidf.zig").__floatdidf; - @export(__floatdidf, .{ .name = "__floatdidf", .linkage = linkage }); - const __floatsitf = @import("compiler_rt/floatsiXf.zig").__floatsitf; - @export(__floatsitf, .{ .name = "__floatsitf", .linkage = linkage }); - - const __floatunsisf = @import("compiler_rt/floatunsisf.zig").__floatunsisf; - @export(__floatunsisf, .{ .name = "__floatunsisf", .linkage = linkage }); - const __floatundisf = @import("compiler_rt/floatundisf.zig").__floatundisf; - @export(__floatundisf, .{ .name = "__floatundisf", .linkage = linkage }); - const __floatunsidf = @import("compiler_rt/floatunsidf.zig").__floatunsidf; - @export(__floatunsidf, .{ .name = "__floatunsidf", .linkage = linkage }); - const __floatundidf = @import("compiler_rt/floatundidf.zig").__floatundidf; - @export(__floatundidf, .{ .name = "__floatundidf", .linkage = linkage }); - - const __floatditf = @import("compiler_rt/floatditf.zig").__floatditf; - @export(__floatditf, .{ .name = "__floatditf", .linkage = linkage }); - const __floattitf = @import("compiler_rt/floattitf.zig").__floattitf; - @export(__floattitf, .{ .name = "__floattitf", .linkage = linkage }); - const __floattidf = @import("compiler_rt/floattidf.zig").__floattidf; - @export(__floattidf, .{ .name = "__floattidf", .linkage = linkage }); - const __floattisf = @import("compiler_rt/floatXisf.zig").__floattisf; - @export(__floattisf, .{ .name = "__floattisf", .linkage = linkage }); - const __floatdisf = @import("compiler_rt/floatXisf.zig").__floatdisf; - @export(__floatdisf, .{ .name = "__floatdisf", .linkage = linkage }); - - const __floatunditf = @import("compiler_rt/floatunditf.zig").__floatunditf; - @export(__floatunditf, .{ .name = "__floatunditf", .linkage = linkage }); - const __floatunsitf = @import("compiler_rt/floatunsitf.zig").__floatunsitf; - @export(__floatunsitf, .{ .name = "__floatunsitf", .linkage = linkage }); - - const __floatuntitf = @import("compiler_rt/floatuntitf.zig").__floatuntitf; - @export(__floatuntitf, .{ .name = "__floatuntitf", .linkage = linkage }); - const __floatuntidf = @import("compiler_rt/floatuntidf.zig").__floatuntidf; - @export(__floatuntidf, .{ .name = "__floatuntidf", .linkage = linkage }); - const __floatuntisf = @import("compiler_rt/floatuntisf.zig").__floatuntisf; - @export(__floatuntisf, .{ .name = "__floatuntisf", .linkage = linkage }); - const __extenddftf2 = @import("compiler_rt/extendXfYf2.zig").__extenddftf2; @export(__extenddftf2, .{ .name = "__extenddftf2", .linkage = linkage }); const __extendsftf2 = @import("compiler_rt/extendXfYf2.zig").__extendsftf2; @@ -175,446 +28,611 @@ comptime { const __extendhftf2 = @import("compiler_rt/extendXfYf2.zig").__extendhftf2; @export(__extendhftf2, .{ .name = "__extendhftf2", .linkage = linkage }); - const __truncsfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncsfhf2; - @export(__truncsfhf2, .{ .name = "__truncsfhf2", .linkage = linkage }); - const __truncdfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncdfhf2; - @export(__truncdfhf2, .{ .name = "__truncdfhf2", .linkage = linkage }); - const __trunctfhf2 = @import("compiler_rt/truncXfYf2.zig").__trunctfhf2; - @export(__trunctfhf2, .{ .name = "__trunctfhf2", .linkage = linkage }); - const __trunctfdf2 = @import("compiler_rt/truncXfYf2.zig").__trunctfdf2; - @export(__trunctfdf2, .{ .name = "__trunctfdf2", .linkage = linkage }); - const __trunctfsf2 = @import("compiler_rt/truncXfYf2.zig").__trunctfsf2; - @export(__trunctfsf2, .{ .name = "__trunctfsf2", .linkage = linkage }); - - const __truncdfsf2 = @import("compiler_rt/truncXfYf2.zig").__truncdfsf2; - @export(__truncdfsf2, .{ .name = "__truncdfsf2", .linkage = linkage }); - - const __extendsfdf2 = @import("compiler_rt/extendXfYf2.zig").__extendsfdf2; - @export(__extendsfdf2, .{ .name = "__extendsfdf2", .linkage = linkage }); - - const __fixunssfsi = @import("compiler_rt/fixunssfsi.zig").__fixunssfsi; - @export(__fixunssfsi, .{ .name = "__fixunssfsi", .linkage = linkage }); - const __fixunssfdi = @import("compiler_rt/fixunssfdi.zig").__fixunssfdi; - @export(__fixunssfdi, .{ .name = "__fixunssfdi", .linkage = linkage }); - const __fixunssfti = @import("compiler_rt/fixunssfti.zig").__fixunssfti; - @export(__fixunssfti, .{ .name = "__fixunssfti", .linkage = linkage }); - - const __fixunsdfsi = @import("compiler_rt/fixunsdfsi.zig").__fixunsdfsi; - @export(__fixunsdfsi, .{ .name = "__fixunsdfsi", .linkage = linkage }); - const __fixunsdfdi = @import("compiler_rt/fixunsdfdi.zig").__fixunsdfdi; - @export(__fixunsdfdi, .{ .name = "__fixunsdfdi", .linkage = linkage }); - const __fixunsdfti = @import("compiler_rt/fixunsdfti.zig").__fixunsdfti; - @export(__fixunsdfti, .{ .name = "__fixunsdfti", .linkage = linkage }); - - const __fixunstfsi = @import("compiler_rt/fixunstfsi.zig").__fixunstfsi; - @export(__fixunstfsi, .{ .name = "__fixunstfsi", .linkage = linkage }); - const __fixunstfdi = @import("compiler_rt/fixunstfdi.zig").__fixunstfdi; - @export(__fixunstfdi, .{ .name = "__fixunstfdi", .linkage = linkage }); - const __fixunstfti = @import("compiler_rt/fixunstfti.zig").__fixunstfti; - @export(__fixunstfti, .{ .name = "__fixunstfti", .linkage = linkage }); - - const __fixdfdi = @import("compiler_rt/fixdfdi.zig").__fixdfdi; - @export(__fixdfdi, .{ .name = "__fixdfdi", .linkage = linkage }); - const __fixdfsi = @import("compiler_rt/fixdfsi.zig").__fixdfsi; - @export(__fixdfsi, .{ .name = "__fixdfsi", .linkage = linkage }); - const __fixdfti = @import("compiler_rt/fixdfti.zig").__fixdfti; - @export(__fixdfti, .{ .name = "__fixdfti", .linkage = linkage }); - const __fixsfdi = @import("compiler_rt/fixsfdi.zig").__fixsfdi; - @export(__fixsfdi, .{ .name = "__fixsfdi", .linkage = linkage }); - const __fixsfsi = @import("compiler_rt/fixsfsi.zig").__fixsfsi; - @export(__fixsfsi, .{ .name = "__fixsfsi", .linkage = linkage }); - const __fixsfti = @import("compiler_rt/fixsfti.zig").__fixsfti; - @export(__fixsfti, .{ .name = "__fixsfti", .linkage = linkage }); - const __fixtfdi = @import("compiler_rt/fixtfdi.zig").__fixtfdi; - @export(__fixtfdi, .{ .name = "__fixtfdi", .linkage = linkage }); - const __fixtfsi = @import("compiler_rt/fixtfsi.zig").__fixtfsi; - @export(__fixtfsi, .{ .name = "__fixtfsi", .linkage = linkage }); - const __fixtfti = @import("compiler_rt/fixtfti.zig").__fixtfti; - @export(__fixtfti, .{ .name = "__fixtfti", .linkage = linkage }); - - const __udivmoddi4 = @import("compiler_rt/int.zig").__udivmoddi4; - @export(__udivmoddi4, .{ .name = "__udivmoddi4", .linkage = linkage }); - const __popcountdi2 = @import("compiler_rt/popcountdi2.zig").__popcountdi2; - @export(__popcountdi2, .{ .name = "__popcountdi2", .linkage = linkage }); - - const __mulsi3 = @import("compiler_rt/int.zig").__mulsi3; - @export(__mulsi3, .{ .name = "__mulsi3", .linkage = linkage }); - const __muldi3 = @import("compiler_rt/muldi3.zig").__muldi3; - @export(__muldi3, .{ .name = "__muldi3", .linkage = linkage }); - const __divmoddi4 = @import("compiler_rt/int.zig").__divmoddi4; - @export(__divmoddi4, .{ .name = "__divmoddi4", .linkage = linkage }); - const __divsi3 = @import("compiler_rt/int.zig").__divsi3; - @export(__divsi3, .{ .name = "__divsi3", .linkage = linkage }); - const __divdi3 = @import("compiler_rt/int.zig").__divdi3; - @export(__divdi3, .{ .name = "__divdi3", .linkage = linkage }); - const __udivsi3 = @import("compiler_rt/int.zig").__udivsi3; - @export(__udivsi3, .{ .name = "__udivsi3", .linkage = linkage }); - const __udivdi3 = @import("compiler_rt/int.zig").__udivdi3; - @export(__udivdi3, .{ .name = "__udivdi3", .linkage = linkage }); - const __modsi3 = @import("compiler_rt/int.zig").__modsi3; - @export(__modsi3, .{ .name = "__modsi3", .linkage = linkage }); - const __moddi3 = @import("compiler_rt/int.zig").__moddi3; - @export(__moddi3, .{ .name = "__moddi3", .linkage = linkage }); - const __umodsi3 = @import("compiler_rt/int.zig").__umodsi3; - @export(__umodsi3, .{ .name = "__umodsi3", .linkage = linkage }); - const __umoddi3 = @import("compiler_rt/int.zig").__umoddi3; - @export(__umoddi3, .{ .name = "__umoddi3", .linkage = linkage }); - const __divmodsi4 = @import("compiler_rt/int.zig").__divmodsi4; - @export(__divmodsi4, .{ .name = "__divmodsi4", .linkage = linkage }); - const __udivmodsi4 = @import("compiler_rt/int.zig").__udivmodsi4; - @export(__udivmodsi4, .{ .name = "__udivmodsi4", .linkage = linkage }); - - const __negsf2 = @import("compiler_rt/negXf2.zig").__negsf2; - @export(__negsf2, .{ .name = "__negsf2", .linkage = linkage }); - const __negdf2 = @import("compiler_rt/negXf2.zig").__negdf2; - @export(__negdf2, .{ .name = "__negdf2", .linkage = linkage }); - - const __clzsi2 = @import("compiler_rt/count0bits.zig").__clzsi2; - @export(__clzsi2, .{ .name = "__clzsi2", .linkage = linkage }); - const __clzdi2 = @import("compiler_rt/count0bits.zig").__clzdi2; - @export(__clzdi2, .{ .name = "__clzdi2", .linkage = linkage }); - const __clzti2 = @import("compiler_rt/count0bits.zig").__clzti2; - @export(__clzti2, .{ .name = "__clzti2", .linkage = linkage }); - - if (builtin.link_libc and os_tag == .openbsd) { - const __emutls_get_address = @import("compiler_rt/emutls.zig").__emutls_get_address; - @export(__emutls_get_address, .{ .name = "__emutls_get_address", .linkage = linkage }); - } - - if ((arch.isARM() or arch.isThumb()) and !is_test) { - const __aeabi_unwind_cpp_pr0 = @import("compiler_rt/arm.zig").__aeabi_unwind_cpp_pr0; - @export(__aeabi_unwind_cpp_pr0, .{ .name = "__aeabi_unwind_cpp_pr0", .linkage = linkage }); - const __aeabi_unwind_cpp_pr1 = @import("compiler_rt/arm.zig").__aeabi_unwind_cpp_pr1; - @export(__aeabi_unwind_cpp_pr1, .{ .name = "__aeabi_unwind_cpp_pr1", .linkage = linkage }); - const __aeabi_unwind_cpp_pr2 = @import("compiler_rt/arm.zig").__aeabi_unwind_cpp_pr2; - @export(__aeabi_unwind_cpp_pr2, .{ .name = "__aeabi_unwind_cpp_pr2", .linkage = linkage }); - - @export(__muldi3, .{ .name = "__aeabi_lmul", .linkage = linkage }); - - const __aeabi_ldivmod = @import("compiler_rt/arm.zig").__aeabi_ldivmod; - @export(__aeabi_ldivmod, .{ .name = "__aeabi_ldivmod", .linkage = linkage }); - const __aeabi_uldivmod = @import("compiler_rt/arm.zig").__aeabi_uldivmod; - @export(__aeabi_uldivmod, .{ .name = "__aeabi_uldivmod", .linkage = linkage }); - - @export(__divsi3, .{ .name = "__aeabi_idiv", .linkage = linkage }); - const __aeabi_idivmod = @import("compiler_rt/arm.zig").__aeabi_idivmod; - @export(__aeabi_idivmod, .{ .name = "__aeabi_idivmod", .linkage = linkage }); - @export(__udivsi3, .{ .name = "__aeabi_uidiv", .linkage = linkage }); - const __aeabi_uidivmod = @import("compiler_rt/arm.zig").__aeabi_uidivmod; - @export(__aeabi_uidivmod, .{ .name = "__aeabi_uidivmod", .linkage = linkage }); - - const __aeabi_memcpy = @import("compiler_rt/arm.zig").__aeabi_memcpy; - @export(__aeabi_memcpy, .{ .name = "__aeabi_memcpy", .linkage = linkage }); - @export(__aeabi_memcpy, .{ .name = "__aeabi_memcpy4", .linkage = linkage }); - @export(__aeabi_memcpy, .{ .name = "__aeabi_memcpy8", .linkage = linkage }); - - const __aeabi_memmove = @import("compiler_rt/arm.zig").__aeabi_memmove; - @export(__aeabi_memmove, .{ .name = "__aeabi_memmove", .linkage = linkage }); - @export(__aeabi_memmove, .{ .name = "__aeabi_memmove4", .linkage = linkage }); - @export(__aeabi_memmove, .{ .name = "__aeabi_memmove8", .linkage = linkage }); - - const __aeabi_memset = @import("compiler_rt/arm.zig").__aeabi_memset; - @export(__aeabi_memset, .{ .name = "__aeabi_memset", .linkage = linkage }); - @export(__aeabi_memset, .{ .name = "__aeabi_memset4", .linkage = linkage }); - @export(__aeabi_memset, .{ .name = "__aeabi_memset8", .linkage = linkage }); - - const __aeabi_memclr = @import("compiler_rt/arm.zig").__aeabi_memclr; - @export(__aeabi_memclr, .{ .name = "__aeabi_memclr", .linkage = linkage }); - @export(__aeabi_memclr, .{ .name = "__aeabi_memclr4", .linkage = linkage }); - @export(__aeabi_memclr, .{ .name = "__aeabi_memclr8", .linkage = linkage }); - - if (os_tag == .linux) { - const __aeabi_read_tp = @import("compiler_rt/arm.zig").__aeabi_read_tp; - @export(__aeabi_read_tp, .{ .name = "__aeabi_read_tp", .linkage = linkage }); - } - - const __aeabi_f2d = @import("compiler_rt/extendXfYf2.zig").__aeabi_f2d; - @export(__aeabi_f2d, .{ .name = "__aeabi_f2d", .linkage = linkage }); - const __aeabi_i2d = @import("compiler_rt/floatsiXf.zig").__aeabi_i2d; - @export(__aeabi_i2d, .{ .name = "__aeabi_i2d", .linkage = linkage }); - const __aeabi_l2d = @import("compiler_rt/floatdidf.zig").__aeabi_l2d; - @export(__aeabi_l2d, .{ .name = "__aeabi_l2d", .linkage = linkage }); - const __aeabi_l2f = @import("compiler_rt/floatXisf.zig").__aeabi_l2f; - @export(__aeabi_l2f, .{ .name = "__aeabi_l2f", .linkage = linkage }); - const __aeabi_ui2d = @import("compiler_rt/floatunsidf.zig").__aeabi_ui2d; - @export(__aeabi_ui2d, .{ .name = "__aeabi_ui2d", .linkage = linkage }); - const __aeabi_ul2d = @import("compiler_rt/floatundidf.zig").__aeabi_ul2d; - @export(__aeabi_ul2d, .{ .name = "__aeabi_ul2d", .linkage = linkage }); - const __aeabi_ui2f = @import("compiler_rt/floatunsisf.zig").__aeabi_ui2f; - @export(__aeabi_ui2f, .{ .name = "__aeabi_ui2f", .linkage = linkage }); - const __aeabi_ul2f = @import("compiler_rt/floatundisf.zig").__aeabi_ul2f; - @export(__aeabi_ul2f, .{ .name = "__aeabi_ul2f", .linkage = linkage }); - - const __aeabi_fneg = @import("compiler_rt/negXf2.zig").__aeabi_fneg; - @export(__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = linkage }); - const __aeabi_dneg = @import("compiler_rt/negXf2.zig").__aeabi_dneg; - @export(__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = linkage }); - - const __aeabi_fmul = @import("compiler_rt/mulXf3.zig").__aeabi_fmul; - @export(__aeabi_fmul, .{ .name = "__aeabi_fmul", .linkage = linkage }); - const __aeabi_dmul = @import("compiler_rt/mulXf3.zig").__aeabi_dmul; - @export(__aeabi_dmul, .{ .name = "__aeabi_dmul", .linkage = linkage }); - - const __aeabi_d2h = @import("compiler_rt/truncXfYf2.zig").__aeabi_d2h; - @export(__aeabi_d2h, .{ .name = "__aeabi_d2h", .linkage = linkage }); - - const __aeabi_f2ulz = @import("compiler_rt/fixunssfdi.zig").__aeabi_f2ulz; - @export(__aeabi_f2ulz, .{ .name = "__aeabi_f2ulz", .linkage = linkage }); - const __aeabi_d2ulz = @import("compiler_rt/fixunsdfdi.zig").__aeabi_d2ulz; - @export(__aeabi_d2ulz, .{ .name = "__aeabi_d2ulz", .linkage = linkage }); - - const __aeabi_f2lz = @import("compiler_rt/fixsfdi.zig").__aeabi_f2lz; - @export(__aeabi_f2lz, .{ .name = "__aeabi_f2lz", .linkage = linkage }); - const __aeabi_d2lz = @import("compiler_rt/fixdfdi.zig").__aeabi_d2lz; - @export(__aeabi_d2lz, .{ .name = "__aeabi_d2lz", .linkage = linkage }); - - const __aeabi_d2uiz = @import("compiler_rt/fixunsdfsi.zig").__aeabi_d2uiz; - @export(__aeabi_d2uiz, .{ .name = "__aeabi_d2uiz", .linkage = linkage }); - - const __aeabi_h2f = @import("compiler_rt/extendXfYf2.zig").__aeabi_h2f; - @export(__aeabi_h2f, .{ .name = "__aeabi_h2f", .linkage = linkage }); - const __aeabi_f2h = @import("compiler_rt/truncXfYf2.zig").__aeabi_f2h; - @export(__aeabi_f2h, .{ .name = "__aeabi_f2h", .linkage = linkage }); - - const __aeabi_i2f = @import("compiler_rt/floatsiXf.zig").__aeabi_i2f; - @export(__aeabi_i2f, .{ .name = "__aeabi_i2f", .linkage = linkage }); - const __aeabi_d2f = @import("compiler_rt/truncXfYf2.zig").__aeabi_d2f; - @export(__aeabi_d2f, .{ .name = "__aeabi_d2f", .linkage = linkage }); - - const __aeabi_fadd = @import("compiler_rt/addXf3.zig").__aeabi_fadd; - @export(__aeabi_fadd, .{ .name = "__aeabi_fadd", .linkage = linkage }); - const __aeabi_dadd = @import("compiler_rt/addXf3.zig").__aeabi_dadd; - @export(__aeabi_dadd, .{ .name = "__aeabi_dadd", .linkage = linkage }); - const __aeabi_fsub = @import("compiler_rt/addXf3.zig").__aeabi_fsub; - @export(__aeabi_fsub, .{ .name = "__aeabi_fsub", .linkage = linkage }); - const __aeabi_dsub = @import("compiler_rt/addXf3.zig").__aeabi_dsub; - @export(__aeabi_dsub, .{ .name = "__aeabi_dsub", .linkage = linkage }); - - const __aeabi_f2uiz = @import("compiler_rt/fixunssfsi.zig").__aeabi_f2uiz; - @export(__aeabi_f2uiz, .{ .name = "__aeabi_f2uiz", .linkage = linkage }); - - const __aeabi_f2iz = @import("compiler_rt/fixsfsi.zig").__aeabi_f2iz; - @export(__aeabi_f2iz, .{ .name = "__aeabi_f2iz", .linkage = linkage }); - const __aeabi_d2iz = @import("compiler_rt/fixdfsi.zig").__aeabi_d2iz; - @export(__aeabi_d2iz, .{ .name = "__aeabi_d2iz", .linkage = linkage }); - - const __aeabi_fdiv = @import("compiler_rt/divsf3.zig").__aeabi_fdiv; - @export(__aeabi_fdiv, .{ .name = "__aeabi_fdiv", .linkage = linkage }); - const __aeabi_ddiv = @import("compiler_rt/divdf3.zig").__aeabi_ddiv; - @export(__aeabi_ddiv, .{ .name = "__aeabi_ddiv", .linkage = linkage }); - - const __aeabi_llsl = @import("compiler_rt/shift.zig").__aeabi_llsl; - @export(__aeabi_llsl, .{ .name = "__aeabi_llsl", .linkage = linkage }); - const __aeabi_lasr = @import("compiler_rt/shift.zig").__aeabi_lasr; - @export(__aeabi_lasr, .{ .name = "__aeabi_lasr", .linkage = linkage }); - const __aeabi_llsr = @import("compiler_rt/shift.zig").__aeabi_llsr; - @export(__aeabi_llsr, .{ .name = "__aeabi_llsr", .linkage = linkage }); - - const __aeabi_fcmpeq = @import("compiler_rt/compareXf2.zig").__aeabi_fcmpeq; - @export(__aeabi_fcmpeq, .{ .name = "__aeabi_fcmpeq", .linkage = linkage }); - const __aeabi_fcmplt = @import("compiler_rt/compareXf2.zig").__aeabi_fcmplt; - @export(__aeabi_fcmplt, .{ .name = "__aeabi_fcmplt", .linkage = linkage }); - const __aeabi_fcmple = @import("compiler_rt/compareXf2.zig").__aeabi_fcmple; - @export(__aeabi_fcmple, .{ .name = "__aeabi_fcmple", .linkage = linkage }); - const __aeabi_fcmpge = @import("compiler_rt/compareXf2.zig").__aeabi_fcmpge; - @export(__aeabi_fcmpge, .{ .name = "__aeabi_fcmpge", .linkage = linkage }); - const __aeabi_fcmpgt = @import("compiler_rt/compareXf2.zig").__aeabi_fcmpgt; - @export(__aeabi_fcmpgt, .{ .name = "__aeabi_fcmpgt", .linkage = linkage }); - const __aeabi_fcmpun = @import("compiler_rt/compareXf2.zig").__aeabi_fcmpun; - @export(__aeabi_fcmpun, .{ .name = "__aeabi_fcmpun", .linkage = linkage }); - - const __aeabi_dcmpeq = @import("compiler_rt/compareXf2.zig").__aeabi_dcmpeq; - @export(__aeabi_dcmpeq, .{ .name = "__aeabi_dcmpeq", .linkage = linkage }); - const __aeabi_dcmplt = @import("compiler_rt/compareXf2.zig").__aeabi_dcmplt; - @export(__aeabi_dcmplt, .{ .name = "__aeabi_dcmplt", .linkage = linkage }); - const __aeabi_dcmple = @import("compiler_rt/compareXf2.zig").__aeabi_dcmple; - @export(__aeabi_dcmple, .{ .name = "__aeabi_dcmple", .linkage = linkage }); - const __aeabi_dcmpge = @import("compiler_rt/compareXf2.zig").__aeabi_dcmpge; - @export(__aeabi_dcmpge, .{ .name = "__aeabi_dcmpge", .linkage = linkage }); - const __aeabi_dcmpgt = @import("compiler_rt/compareXf2.zig").__aeabi_dcmpgt; - @export(__aeabi_dcmpgt, .{ .name = "__aeabi_dcmpgt", .linkage = linkage }); - const __aeabi_dcmpun = @import("compiler_rt/compareXf2.zig").__aeabi_dcmpun; - @export(__aeabi_dcmpun, .{ .name = "__aeabi_dcmpun", .linkage = linkage }); - } - - if (arch == .i386 and abi == .msvc) { - // Don't let LLVM apply the stdcall name mangling on those MSVC builtins - const _alldiv = @import("compiler_rt/aulldiv.zig")._alldiv; - @export(_alldiv, .{ .name = "\x01__alldiv", .linkage = strong_linkage }); - const _aulldiv = @import("compiler_rt/aulldiv.zig")._aulldiv; - @export(_aulldiv, .{ .name = "\x01__aulldiv", .linkage = strong_linkage }); - const _allrem = @import("compiler_rt/aullrem.zig")._allrem; - @export(_allrem, .{ .name = "\x01__allrem", .linkage = strong_linkage }); - const _aullrem = @import("compiler_rt/aullrem.zig")._aullrem; - @export(_aullrem, .{ .name = "\x01__aullrem", .linkage = strong_linkage }); - } - - if (arch.isSPARC()) { - // SPARC systems use a different naming scheme - const _Qp_add = @import("compiler_rt/sparc.zig")._Qp_add; - @export(_Qp_add, .{ .name = "_Qp_add", .linkage = linkage }); - const _Qp_div = @import("compiler_rt/sparc.zig")._Qp_div; - @export(_Qp_div, .{ .name = "_Qp_div", .linkage = linkage }); - const _Qp_mul = @import("compiler_rt/sparc.zig")._Qp_mul; - @export(_Qp_mul, .{ .name = "_Qp_mul", .linkage = linkage }); - const _Qp_sub = @import("compiler_rt/sparc.zig")._Qp_sub; - @export(_Qp_sub, .{ .name = "_Qp_sub", .linkage = linkage }); - - const _Qp_cmp = @import("compiler_rt/sparc.zig")._Qp_cmp; - @export(_Qp_cmp, .{ .name = "_Qp_cmp", .linkage = linkage }); - const _Qp_feq = @import("compiler_rt/sparc.zig")._Qp_feq; - @export(_Qp_feq, .{ .name = "_Qp_feq", .linkage = linkage }); - const _Qp_fne = @import("compiler_rt/sparc.zig")._Qp_fne; - @export(_Qp_fne, .{ .name = "_Qp_fne", .linkage = linkage }); - const _Qp_flt = @import("compiler_rt/sparc.zig")._Qp_flt; - @export(_Qp_flt, .{ .name = "_Qp_flt", .linkage = linkage }); - const _Qp_fle = @import("compiler_rt/sparc.zig")._Qp_fle; - @export(_Qp_fle, .{ .name = "_Qp_fle", .linkage = linkage }); - const _Qp_fgt = @import("compiler_rt/sparc.zig")._Qp_fgt; - @export(_Qp_fgt, .{ .name = "_Qp_fgt", .linkage = linkage }); - const _Qp_fge = @import("compiler_rt/sparc.zig")._Qp_fge; - @export(_Qp_fge, .{ .name = "_Qp_fge", .linkage = linkage }); - - const _Qp_itoq = @import("compiler_rt/sparc.zig")._Qp_itoq; - @export(_Qp_itoq, .{ .name = "_Qp_itoq", .linkage = linkage }); - const _Qp_uitoq = @import("compiler_rt/sparc.zig")._Qp_uitoq; - @export(_Qp_uitoq, .{ .name = "_Qp_uitoq", .linkage = linkage }); - const _Qp_xtoq = @import("compiler_rt/sparc.zig")._Qp_xtoq; - @export(_Qp_xtoq, .{ .name = "_Qp_xtoq", .linkage = linkage }); - const _Qp_uxtoq = @import("compiler_rt/sparc.zig")._Qp_uxtoq; - @export(_Qp_uxtoq, .{ .name = "_Qp_uxtoq", .linkage = linkage }); - const _Qp_stoq = @import("compiler_rt/sparc.zig")._Qp_stoq; - @export(_Qp_stoq, .{ .name = "_Qp_stoq", .linkage = linkage }); - const _Qp_dtoq = @import("compiler_rt/sparc.zig")._Qp_dtoq; - @export(_Qp_dtoq, .{ .name = "_Qp_dtoq", .linkage = linkage }); - const _Qp_qtoi = @import("compiler_rt/sparc.zig")._Qp_qtoi; - @export(_Qp_qtoi, .{ .name = "_Qp_qtoi", .linkage = linkage }); - const _Qp_qtoui = @import("compiler_rt/sparc.zig")._Qp_qtoui; - @export(_Qp_qtoui, .{ .name = "_Qp_qtoui", .linkage = linkage }); - const _Qp_qtox = @import("compiler_rt/sparc.zig")._Qp_qtox; - @export(_Qp_qtox, .{ .name = "_Qp_qtox", .linkage = linkage }); - const _Qp_qtoux = @import("compiler_rt/sparc.zig")._Qp_qtoux; - @export(_Qp_qtoux, .{ .name = "_Qp_qtoux", .linkage = linkage }); - const _Qp_qtos = @import("compiler_rt/sparc.zig")._Qp_qtos; - @export(_Qp_qtos, .{ .name = "_Qp_qtos", .linkage = linkage }); - const _Qp_qtod = @import("compiler_rt/sparc.zig")._Qp_qtod; - @export(_Qp_qtod, .{ .name = "_Qp_qtod", .linkage = linkage }); - } - - if ((arch == .powerpc or arch.isPPC64()) and !is_test) { - @export(__addtf3, .{ .name = "__addkf3", .linkage = linkage }); - @export(__subtf3, .{ .name = "__subkf3", .linkage = linkage }); - @export(__multf3, .{ .name = "__mulkf3", .linkage = linkage }); - @export(__divtf3, .{ .name = "__divkf3", .linkage = linkage }); - @export(__extendsftf2, .{ .name = "__extendsfkf2", .linkage = linkage }); - @export(__extenddftf2, .{ .name = "__extenddfkf2", .linkage = linkage }); - @export(__trunctfsf2, .{ .name = "__trunckfsf2", .linkage = linkage }); - @export(__trunctfdf2, .{ .name = "__trunckfdf2", .linkage = linkage }); - @export(__fixtfdi, .{ .name = "__fixkfdi", .linkage = linkage }); - @export(__fixtfsi, .{ .name = "__fixkfsi", .linkage = linkage }); - @export(__fixunstfsi, .{ .name = "__fixunskfsi", .linkage = linkage }); - @export(__fixunstfdi, .{ .name = "__fixunskfdi", .linkage = linkage }); - @export(__floatsitf, .{ .name = "__floatsikf", .linkage = linkage }); - @export(__floatditf, .{ .name = "__floatdikf", .linkage = linkage }); - @export(__floatunditf, .{ .name = "__floatundikf", .linkage = linkage }); - @export(__floatunsitf, .{ .name = "__floatunsikf", .linkage = linkage }); - - @export(__letf2, .{ .name = "__eqkf2", .linkage = linkage }); - @export(__letf2, .{ .name = "__nekf2", .linkage = linkage }); - @export(__getf2, .{ .name = "__gekf2", .linkage = linkage }); - @export(__letf2, .{ .name = "__ltkf2", .linkage = linkage }); - @export(__letf2, .{ .name = "__lekf2", .linkage = linkage }); - @export(__getf2, .{ .name = "__gtkf2", .linkage = linkage }); - @export(__unordtf2, .{ .name = "__unordkf2", .linkage = linkage }); - } - - if (builtin.os.tag == .windows) { - // Default stack-probe functions emitted by LLVM - if (is_mingw) { - const _chkstk = @import("compiler_rt/stack_probe.zig")._chkstk; - @export(_chkstk, .{ .name = "_alloca", .linkage = strong_linkage }); - const ___chkstk_ms = @import("compiler_rt/stack_probe.zig").___chkstk_ms; - @export(___chkstk_ms, .{ .name = "___chkstk_ms", .linkage = strong_linkage }); - } else if (!builtin.link_libc) { - // This symbols are otherwise exported by MSVCRT.lib - const _chkstk = @import("compiler_rt/stack_probe.zig")._chkstk; - @export(_chkstk, .{ .name = "_chkstk", .linkage = strong_linkage }); - const __chkstk = @import("compiler_rt/stack_probe.zig").__chkstk; - @export(__chkstk, .{ .name = "__chkstk", .linkage = strong_linkage }); - } - + if (!builtin.zig_is_stage2) { switch (arch) { - .i386 => { - const __divti3 = @import("compiler_rt/divti3.zig").__divti3; - @export(__divti3, .{ .name = "__divti3", .linkage = linkage }); - const __modti3 = @import("compiler_rt/modti3.zig").__modti3; - @export(__modti3, .{ .name = "__modti3", .linkage = linkage }); - const __multi3 = @import("compiler_rt/multi3.zig").__multi3; - @export(__multi3, .{ .name = "__multi3", .linkage = linkage }); - const __udivti3 = @import("compiler_rt/udivti3.zig").__udivti3; - @export(__udivti3, .{ .name = "__udivti3", .linkage = linkage }); - const __udivmodti4 = @import("compiler_rt/udivmodti4.zig").__udivmodti4; - @export(__udivmodti4, .{ .name = "__udivmodti4", .linkage = linkage }); - const __umodti3 = @import("compiler_rt/umodti3.zig").__umodti3; - @export(__umodti3, .{ .name = "__umodti3", .linkage = linkage }); - }, - .x86_64 => { - // The "ti" functions must use Vector(2, u64) parameter types to adhere to the ABI - // that LLVM expects compiler-rt to have. - const __divti3_windows_x86_64 = @import("compiler_rt/divti3.zig").__divti3_windows_x86_64; - @export(__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = linkage }); - const __modti3_windows_x86_64 = @import("compiler_rt/modti3.zig").__modti3_windows_x86_64; - @export(__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = linkage }); - const __multi3_windows_x86_64 = @import("compiler_rt/multi3.zig").__multi3_windows_x86_64; - @export(__multi3_windows_x86_64, .{ .name = "__multi3", .linkage = linkage }); - const __udivti3_windows_x86_64 = @import("compiler_rt/udivti3.zig").__udivti3_windows_x86_64; - @export(__udivti3_windows_x86_64, .{ .name = "__udivti3", .linkage = linkage }); - const __udivmodti4_windows_x86_64 = @import("compiler_rt/udivmodti4.zig").__udivmodti4_windows_x86_64; - @export(__udivmodti4_windows_x86_64, .{ .name = "__udivmodti4", .linkage = linkage }); - const __umodti3_windows_x86_64 = @import("compiler_rt/umodti3.zig").__umodti3_windows_x86_64; - @export(__umodti3_windows_x86_64, .{ .name = "__umodti3", .linkage = linkage }); + .i386, + .x86_64, + => { + const zig_probe_stack = @import("compiler_rt/stack_probe.zig").zig_probe_stack; + @export(zig_probe_stack, .{ + .name = "__zig_probe_stack", + .linkage = linkage, + }); }, + else => {}, } - if (arch.isAARCH64()) { - const __chkstk = @import("compiler_rt/stack_probe.zig").__chkstk; - @export(__chkstk, .{ .name = "__chkstk", .linkage = strong_linkage }); - const __divti3_windows = @import("compiler_rt/divti3.zig").__divti3; - @export(__divti3_windows, .{ .name = "__divti3", .linkage = linkage }); + + // __clear_cache manages its own logic about whether to be exported or not. + _ = @import("compiler_rt/clear_cache.zig").clear_cache; + + const __lesf2 = @import("compiler_rt/compareXf2.zig").__lesf2; + @export(__lesf2, .{ .name = "__lesf2", .linkage = linkage }); + const __ledf2 = @import("compiler_rt/compareXf2.zig").__ledf2; + @export(__ledf2, .{ .name = "__ledf2", .linkage = linkage }); + const __letf2 = @import("compiler_rt/compareXf2.zig").__letf2; + @export(__letf2, .{ .name = "__letf2", .linkage = linkage }); + + const __gesf2 = @import("compiler_rt/compareXf2.zig").__gesf2; + @export(__gesf2, .{ .name = "__gesf2", .linkage = linkage }); + const __gedf2 = @import("compiler_rt/compareXf2.zig").__gedf2; + @export(__gedf2, .{ .name = "__gedf2", .linkage = linkage }); + const __getf2 = @import("compiler_rt/compareXf2.zig").__getf2; + @export(__getf2, .{ .name = "__getf2", .linkage = linkage }); + + if (!is_test) { + @export(__lesf2, .{ .name = "__cmpsf2", .linkage = linkage }); + @export(__ledf2, .{ .name = "__cmpdf2", .linkage = linkage }); + @export(__letf2, .{ .name = "__cmptf2", .linkage = linkage }); + + const __eqsf2 = @import("compiler_rt/compareXf2.zig").__eqsf2; + @export(__eqsf2, .{ .name = "__eqsf2", .linkage = linkage }); + const __eqdf2 = @import("compiler_rt/compareXf2.zig").__eqdf2; + @export(__eqdf2, .{ .name = "__eqdf2", .linkage = linkage }); + @export(__letf2, .{ .name = "__eqtf2", .linkage = linkage }); + + const __ltsf2 = @import("compiler_rt/compareXf2.zig").__ltsf2; + @export(__ltsf2, .{ .name = "__ltsf2", .linkage = linkage }); + const __ltdf2 = @import("compiler_rt/compareXf2.zig").__ltdf2; + @export(__ltdf2, .{ .name = "__ltdf2", .linkage = linkage }); + @export(__letf2, .{ .name = "__lttf2", .linkage = linkage }); + + const __nesf2 = @import("compiler_rt/compareXf2.zig").__nesf2; + @export(__nesf2, .{ .name = "__nesf2", .linkage = linkage }); + const __nedf2 = @import("compiler_rt/compareXf2.zig").__nedf2; + @export(__nedf2, .{ .name = "__nedf2", .linkage = linkage }); + @export(__letf2, .{ .name = "__netf2", .linkage = linkage }); + + const __gtsf2 = @import("compiler_rt/compareXf2.zig").__gtsf2; + @export(__gtsf2, .{ .name = "__gtsf2", .linkage = linkage }); + const __gtdf2 = @import("compiler_rt/compareXf2.zig").__gtdf2; + @export(__gtdf2, .{ .name = "__gtdf2", .linkage = linkage }); + @export(__getf2, .{ .name = "__gttf2", .linkage = linkage }); + + @export(@import("compiler_rt/extendXfYf2.zig").__extendhfsf2, .{ + .name = "__gnu_h2f_ieee", + .linkage = linkage, + }); + @export(@import("compiler_rt/truncXfYf2.zig").__truncsfhf2, .{ + .name = "__gnu_f2h_ieee", + .linkage = linkage, + }); + } + + const __unordsf2 = @import("compiler_rt/compareXf2.zig").__unordsf2; + @export(__unordsf2, .{ .name = "__unordsf2", .linkage = linkage }); + const __unorddf2 = @import("compiler_rt/compareXf2.zig").__unorddf2; + @export(__unorddf2, .{ .name = "__unorddf2", .linkage = linkage }); + const __unordtf2 = @import("compiler_rt/compareXf2.zig").__unordtf2; + @export(__unordtf2, .{ .name = "__unordtf2", .linkage = linkage }); + + const __addsf3 = @import("compiler_rt/addXf3.zig").__addsf3; + @export(__addsf3, .{ .name = "__addsf3", .linkage = linkage }); + const __adddf3 = @import("compiler_rt/addXf3.zig").__adddf3; + @export(__adddf3, .{ .name = "__adddf3", .linkage = linkage }); + const __addtf3 = @import("compiler_rt/addXf3.zig").__addtf3; + @export(__addtf3, .{ .name = "__addtf3", .linkage = linkage }); + const __subsf3 = @import("compiler_rt/addXf3.zig").__subsf3; + @export(__subsf3, .{ .name = "__subsf3", .linkage = linkage }); + const __subdf3 = @import("compiler_rt/addXf3.zig").__subdf3; + @export(__subdf3, .{ .name = "__subdf3", .linkage = linkage }); + const __subtf3 = @import("compiler_rt/addXf3.zig").__subtf3; + @export(__subtf3, .{ .name = "__subtf3", .linkage = linkage }); + + const __mulsf3 = @import("compiler_rt/mulXf3.zig").__mulsf3; + @export(__mulsf3, .{ .name = "__mulsf3", .linkage = linkage }); + const __muldf3 = @import("compiler_rt/mulXf3.zig").__muldf3; + @export(__muldf3, .{ .name = "__muldf3", .linkage = linkage }); + const __multf3 = @import("compiler_rt/mulXf3.zig").__multf3; + @export(__multf3, .{ .name = "__multf3", .linkage = linkage }); + + const __divsf3 = @import("compiler_rt/divsf3.zig").__divsf3; + @export(__divsf3, .{ .name = "__divsf3", .linkage = linkage }); + const __divdf3 = @import("compiler_rt/divdf3.zig").__divdf3; + @export(__divdf3, .{ .name = "__divdf3", .linkage = linkage }); + const __divtf3 = @import("compiler_rt/divtf3.zig").__divtf3; + @export(__divtf3, .{ .name = "__divtf3", .linkage = linkage }); + + const __ashldi3 = @import("compiler_rt/shift.zig").__ashldi3; + @export(__ashldi3, .{ .name = "__ashldi3", .linkage = linkage }); + const __ashlti3 = @import("compiler_rt/shift.zig").__ashlti3; + @export(__ashlti3, .{ .name = "__ashlti3", .linkage = linkage }); + const __ashrdi3 = @import("compiler_rt/shift.zig").__ashrdi3; + @export(__ashrdi3, .{ .name = "__ashrdi3", .linkage = linkage }); + const __ashrti3 = @import("compiler_rt/shift.zig").__ashrti3; + @export(__ashrti3, .{ .name = "__ashrti3", .linkage = linkage }); + const __lshrdi3 = @import("compiler_rt/shift.zig").__lshrdi3; + @export(__lshrdi3, .{ .name = "__lshrdi3", .linkage = linkage }); + const __lshrti3 = @import("compiler_rt/shift.zig").__lshrti3; + @export(__lshrti3, .{ .name = "__lshrti3", .linkage = linkage }); + + const __floatsidf = @import("compiler_rt/floatsiXf.zig").__floatsidf; + @export(__floatsidf, .{ .name = "__floatsidf", .linkage = linkage }); + const __floatsisf = @import("compiler_rt/floatsiXf.zig").__floatsisf; + @export(__floatsisf, .{ .name = "__floatsisf", .linkage = linkage }); + const __floatdidf = @import("compiler_rt/floatdidf.zig").__floatdidf; + @export(__floatdidf, .{ .name = "__floatdidf", .linkage = linkage }); + const __floatsitf = @import("compiler_rt/floatsiXf.zig").__floatsitf; + @export(__floatsitf, .{ .name = "__floatsitf", .linkage = linkage }); + + const __floatunsisf = @import("compiler_rt/floatunsisf.zig").__floatunsisf; + @export(__floatunsisf, .{ .name = "__floatunsisf", .linkage = linkage }); + const __floatundisf = @import("compiler_rt/floatundisf.zig").__floatundisf; + @export(__floatundisf, .{ .name = "__floatundisf", .linkage = linkage }); + const __floatunsidf = @import("compiler_rt/floatunsidf.zig").__floatunsidf; + @export(__floatunsidf, .{ .name = "__floatunsidf", .linkage = linkage }); + const __floatundidf = @import("compiler_rt/floatundidf.zig").__floatundidf; + @export(__floatundidf, .{ .name = "__floatundidf", .linkage = linkage }); + + const __floatditf = @import("compiler_rt/floatditf.zig").__floatditf; + @export(__floatditf, .{ .name = "__floatditf", .linkage = linkage }); + const __floattitf = @import("compiler_rt/floattitf.zig").__floattitf; + @export(__floattitf, .{ .name = "__floattitf", .linkage = linkage }); + const __floattidf = @import("compiler_rt/floattidf.zig").__floattidf; + @export(__floattidf, .{ .name = "__floattidf", .linkage = linkage }); + const __floattisf = @import("compiler_rt/floatXisf.zig").__floattisf; + @export(__floattisf, .{ .name = "__floattisf", .linkage = linkage }); + const __floatdisf = @import("compiler_rt/floatXisf.zig").__floatdisf; + @export(__floatdisf, .{ .name = "__floatdisf", .linkage = linkage }); + + const __floatunditf = @import("compiler_rt/floatunditf.zig").__floatunditf; + @export(__floatunditf, .{ .name = "__floatunditf", .linkage = linkage }); + const __floatunsitf = @import("compiler_rt/floatunsitf.zig").__floatunsitf; + @export(__floatunsitf, .{ .name = "__floatunsitf", .linkage = linkage }); + + const __floatuntitf = @import("compiler_rt/floatuntitf.zig").__floatuntitf; + @export(__floatuntitf, .{ .name = "__floatuntitf", .linkage = linkage }); + const __floatuntidf = @import("compiler_rt/floatuntidf.zig").__floatuntidf; + @export(__floatuntidf, .{ .name = "__floatuntidf", .linkage = linkage }); + const __floatuntisf = @import("compiler_rt/floatuntisf.zig").__floatuntisf; + @export(__floatuntisf, .{ .name = "__floatuntisf", .linkage = linkage }); + + const __truncsfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncsfhf2; + @export(__truncsfhf2, .{ .name = "__truncsfhf2", .linkage = linkage }); + const __truncdfhf2 = @import("compiler_rt/truncXfYf2.zig").__truncdfhf2; + @export(__truncdfhf2, .{ .name = "__truncdfhf2", .linkage = linkage }); + const __trunctfhf2 = @import("compiler_rt/truncXfYf2.zig").__trunctfhf2; + @export(__trunctfhf2, .{ .name = "__trunctfhf2", .linkage = linkage }); + const __trunctfdf2 = @import("compiler_rt/truncXfYf2.zig").__trunctfdf2; + @export(__trunctfdf2, .{ .name = "__trunctfdf2", .linkage = linkage }); + const __trunctfsf2 = @import("compiler_rt/truncXfYf2.zig").__trunctfsf2; + @export(__trunctfsf2, .{ .name = "__trunctfsf2", .linkage = linkage }); + + const __truncdfsf2 = @import("compiler_rt/truncXfYf2.zig").__truncdfsf2; + @export(__truncdfsf2, .{ .name = "__truncdfsf2", .linkage = linkage }); + + const __extendsfdf2 = @import("compiler_rt/extendXfYf2.zig").__extendsfdf2; + @export(__extendsfdf2, .{ .name = "__extendsfdf2", .linkage = linkage }); + + const __fixunssfsi = @import("compiler_rt/fixunssfsi.zig").__fixunssfsi; + @export(__fixunssfsi, .{ .name = "__fixunssfsi", .linkage = linkage }); + const __fixunssfdi = @import("compiler_rt/fixunssfdi.zig").__fixunssfdi; + @export(__fixunssfdi, .{ .name = "__fixunssfdi", .linkage = linkage }); + const __fixunssfti = @import("compiler_rt/fixunssfti.zig").__fixunssfti; + @export(__fixunssfti, .{ .name = "__fixunssfti", .linkage = linkage }); + + const __fixunsdfsi = @import("compiler_rt/fixunsdfsi.zig").__fixunsdfsi; + @export(__fixunsdfsi, .{ .name = "__fixunsdfsi", .linkage = linkage }); + const __fixunsdfdi = @import("compiler_rt/fixunsdfdi.zig").__fixunsdfdi; + @export(__fixunsdfdi, .{ .name = "__fixunsdfdi", .linkage = linkage }); + const __fixunsdfti = @import("compiler_rt/fixunsdfti.zig").__fixunsdfti; + @export(__fixunsdfti, .{ .name = "__fixunsdfti", .linkage = linkage }); + + const __fixunstfsi = @import("compiler_rt/fixunstfsi.zig").__fixunstfsi; + @export(__fixunstfsi, .{ .name = "__fixunstfsi", .linkage = linkage }); + const __fixunstfdi = @import("compiler_rt/fixunstfdi.zig").__fixunstfdi; + @export(__fixunstfdi, .{ .name = "__fixunstfdi", .linkage = linkage }); + const __fixunstfti = @import("compiler_rt/fixunstfti.zig").__fixunstfti; + @export(__fixunstfti, .{ .name = "__fixunstfti", .linkage = linkage }); + + const __fixdfdi = @import("compiler_rt/fixdfdi.zig").__fixdfdi; + @export(__fixdfdi, .{ .name = "__fixdfdi", .linkage = linkage }); + const __fixdfsi = @import("compiler_rt/fixdfsi.zig").__fixdfsi; + @export(__fixdfsi, .{ .name = "__fixdfsi", .linkage = linkage }); + const __fixdfti = @import("compiler_rt/fixdfti.zig").__fixdfti; + @export(__fixdfti, .{ .name = "__fixdfti", .linkage = linkage }); + const __fixsfdi = @import("compiler_rt/fixsfdi.zig").__fixsfdi; + @export(__fixsfdi, .{ .name = "__fixsfdi", .linkage = linkage }); + const __fixsfsi = @import("compiler_rt/fixsfsi.zig").__fixsfsi; + @export(__fixsfsi, .{ .name = "__fixsfsi", .linkage = linkage }); + const __fixsfti = @import("compiler_rt/fixsfti.zig").__fixsfti; + @export(__fixsfti, .{ .name = "__fixsfti", .linkage = linkage }); + const __fixtfdi = @import("compiler_rt/fixtfdi.zig").__fixtfdi; + @export(__fixtfdi, .{ .name = "__fixtfdi", .linkage = linkage }); + const __fixtfsi = @import("compiler_rt/fixtfsi.zig").__fixtfsi; + @export(__fixtfsi, .{ .name = "__fixtfsi", .linkage = linkage }); + const __fixtfti = @import("compiler_rt/fixtfti.zig").__fixtfti; + @export(__fixtfti, .{ .name = "__fixtfti", .linkage = linkage }); + + const __udivmoddi4 = @import("compiler_rt/int.zig").__udivmoddi4; + @export(__udivmoddi4, .{ .name = "__udivmoddi4", .linkage = linkage }); + const __popcountdi2 = @import("compiler_rt/popcountdi2.zig").__popcountdi2; + @export(__popcountdi2, .{ .name = "__popcountdi2", .linkage = linkage }); + + const __mulsi3 = @import("compiler_rt/int.zig").__mulsi3; + @export(__mulsi3, .{ .name = "__mulsi3", .linkage = linkage }); + const __muldi3 = @import("compiler_rt/muldi3.zig").__muldi3; + @export(__muldi3, .{ .name = "__muldi3", .linkage = linkage }); + const __divmoddi4 = @import("compiler_rt/int.zig").__divmoddi4; + @export(__divmoddi4, .{ .name = "__divmoddi4", .linkage = linkage }); + const __divsi3 = @import("compiler_rt/int.zig").__divsi3; + @export(__divsi3, .{ .name = "__divsi3", .linkage = linkage }); + const __divdi3 = @import("compiler_rt/int.zig").__divdi3; + @export(__divdi3, .{ .name = "__divdi3", .linkage = linkage }); + const __udivsi3 = @import("compiler_rt/int.zig").__udivsi3; + @export(__udivsi3, .{ .name = "__udivsi3", .linkage = linkage }); + const __udivdi3 = @import("compiler_rt/int.zig").__udivdi3; + @export(__udivdi3, .{ .name = "__udivdi3", .linkage = linkage }); + const __modsi3 = @import("compiler_rt/int.zig").__modsi3; + @export(__modsi3, .{ .name = "__modsi3", .linkage = linkage }); + const __moddi3 = @import("compiler_rt/int.zig").__moddi3; + @export(__moddi3, .{ .name = "__moddi3", .linkage = linkage }); + const __umodsi3 = @import("compiler_rt/int.zig").__umodsi3; + @export(__umodsi3, .{ .name = "__umodsi3", .linkage = linkage }); + const __umoddi3 = @import("compiler_rt/int.zig").__umoddi3; + @export(__umoddi3, .{ .name = "__umoddi3", .linkage = linkage }); + const __divmodsi4 = @import("compiler_rt/int.zig").__divmodsi4; + @export(__divmodsi4, .{ .name = "__divmodsi4", .linkage = linkage }); + const __udivmodsi4 = @import("compiler_rt/int.zig").__udivmodsi4; + @export(__udivmodsi4, .{ .name = "__udivmodsi4", .linkage = linkage }); + + const __negsf2 = @import("compiler_rt/negXf2.zig").__negsf2; + @export(__negsf2, .{ .name = "__negsf2", .linkage = linkage }); + const __negdf2 = @import("compiler_rt/negXf2.zig").__negdf2; + @export(__negdf2, .{ .name = "__negdf2", .linkage = linkage }); + + const __clzsi2 = @import("compiler_rt/count0bits.zig").__clzsi2; + @export(__clzsi2, .{ .name = "__clzsi2", .linkage = linkage }); + const __clzdi2 = @import("compiler_rt/count0bits.zig").__clzdi2; + @export(__clzdi2, .{ .name = "__clzdi2", .linkage = linkage }); + const __clzti2 = @import("compiler_rt/count0bits.zig").__clzti2; + @export(__clzti2, .{ .name = "__clzti2", .linkage = linkage }); + + if (builtin.link_libc and os_tag == .openbsd) { + const __emutls_get_address = @import("compiler_rt/emutls.zig").__emutls_get_address; + @export(__emutls_get_address, .{ .name = "__emutls_get_address", .linkage = linkage }); + } + + if ((arch.isARM() or arch.isThumb()) and !is_test) { + const __aeabi_unwind_cpp_pr0 = @import("compiler_rt/arm.zig").__aeabi_unwind_cpp_pr0; + @export(__aeabi_unwind_cpp_pr0, .{ .name = "__aeabi_unwind_cpp_pr0", .linkage = linkage }); + const __aeabi_unwind_cpp_pr1 = @import("compiler_rt/arm.zig").__aeabi_unwind_cpp_pr1; + @export(__aeabi_unwind_cpp_pr1, .{ .name = "__aeabi_unwind_cpp_pr1", .linkage = linkage }); + const __aeabi_unwind_cpp_pr2 = @import("compiler_rt/arm.zig").__aeabi_unwind_cpp_pr2; + @export(__aeabi_unwind_cpp_pr2, .{ .name = "__aeabi_unwind_cpp_pr2", .linkage = linkage }); + + @export(__muldi3, .{ .name = "__aeabi_lmul", .linkage = linkage }); + + const __aeabi_ldivmod = @import("compiler_rt/arm.zig").__aeabi_ldivmod; + @export(__aeabi_ldivmod, .{ .name = "__aeabi_ldivmod", .linkage = linkage }); + const __aeabi_uldivmod = @import("compiler_rt/arm.zig").__aeabi_uldivmod; + @export(__aeabi_uldivmod, .{ .name = "__aeabi_uldivmod", .linkage = linkage }); + + @export(__divsi3, .{ .name = "__aeabi_idiv", .linkage = linkage }); + const __aeabi_idivmod = @import("compiler_rt/arm.zig").__aeabi_idivmod; + @export(__aeabi_idivmod, .{ .name = "__aeabi_idivmod", .linkage = linkage }); + @export(__udivsi3, .{ .name = "__aeabi_uidiv", .linkage = linkage }); + const __aeabi_uidivmod = @import("compiler_rt/arm.zig").__aeabi_uidivmod; + @export(__aeabi_uidivmod, .{ .name = "__aeabi_uidivmod", .linkage = linkage }); + + const __aeabi_memcpy = @import("compiler_rt/arm.zig").__aeabi_memcpy; + @export(__aeabi_memcpy, .{ .name = "__aeabi_memcpy", .linkage = linkage }); + @export(__aeabi_memcpy, .{ .name = "__aeabi_memcpy4", .linkage = linkage }); + @export(__aeabi_memcpy, .{ .name = "__aeabi_memcpy8", .linkage = linkage }); + + const __aeabi_memmove = @import("compiler_rt/arm.zig").__aeabi_memmove; + @export(__aeabi_memmove, .{ .name = "__aeabi_memmove", .linkage = linkage }); + @export(__aeabi_memmove, .{ .name = "__aeabi_memmove4", .linkage = linkage }); + @export(__aeabi_memmove, .{ .name = "__aeabi_memmove8", .linkage = linkage }); + + const __aeabi_memset = @import("compiler_rt/arm.zig").__aeabi_memset; + @export(__aeabi_memset, .{ .name = "__aeabi_memset", .linkage = linkage }); + @export(__aeabi_memset, .{ .name = "__aeabi_memset4", .linkage = linkage }); + @export(__aeabi_memset, .{ .name = "__aeabi_memset8", .linkage = linkage }); + + const __aeabi_memclr = @import("compiler_rt/arm.zig").__aeabi_memclr; + @export(__aeabi_memclr, .{ .name = "__aeabi_memclr", .linkage = linkage }); + @export(__aeabi_memclr, .{ .name = "__aeabi_memclr4", .linkage = linkage }); + @export(__aeabi_memclr, .{ .name = "__aeabi_memclr8", .linkage = linkage }); + + if (os_tag == .linux) { + const __aeabi_read_tp = @import("compiler_rt/arm.zig").__aeabi_read_tp; + @export(__aeabi_read_tp, .{ .name = "__aeabi_read_tp", .linkage = linkage }); + } + + const __aeabi_f2d = @import("compiler_rt/extendXfYf2.zig").__aeabi_f2d; + @export(__aeabi_f2d, .{ .name = "__aeabi_f2d", .linkage = linkage }); + const __aeabi_i2d = @import("compiler_rt/floatsiXf.zig").__aeabi_i2d; + @export(__aeabi_i2d, .{ .name = "__aeabi_i2d", .linkage = linkage }); + const __aeabi_l2d = @import("compiler_rt/floatdidf.zig").__aeabi_l2d; + @export(__aeabi_l2d, .{ .name = "__aeabi_l2d", .linkage = linkage }); + const __aeabi_l2f = @import("compiler_rt/floatXisf.zig").__aeabi_l2f; + @export(__aeabi_l2f, .{ .name = "__aeabi_l2f", .linkage = linkage }); + const __aeabi_ui2d = @import("compiler_rt/floatunsidf.zig").__aeabi_ui2d; + @export(__aeabi_ui2d, .{ .name = "__aeabi_ui2d", .linkage = linkage }); + const __aeabi_ul2d = @import("compiler_rt/floatundidf.zig").__aeabi_ul2d; + @export(__aeabi_ul2d, .{ .name = "__aeabi_ul2d", .linkage = linkage }); + const __aeabi_ui2f = @import("compiler_rt/floatunsisf.zig").__aeabi_ui2f; + @export(__aeabi_ui2f, .{ .name = "__aeabi_ui2f", .linkage = linkage }); + const __aeabi_ul2f = @import("compiler_rt/floatundisf.zig").__aeabi_ul2f; + @export(__aeabi_ul2f, .{ .name = "__aeabi_ul2f", .linkage = linkage }); + + const __aeabi_fneg = @import("compiler_rt/negXf2.zig").__aeabi_fneg; + @export(__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = linkage }); + const __aeabi_dneg = @import("compiler_rt/negXf2.zig").__aeabi_dneg; + @export(__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = linkage }); + + const __aeabi_fmul = @import("compiler_rt/mulXf3.zig").__aeabi_fmul; + @export(__aeabi_fmul, .{ .name = "__aeabi_fmul", .linkage = linkage }); + const __aeabi_dmul = @import("compiler_rt/mulXf3.zig").__aeabi_dmul; + @export(__aeabi_dmul, .{ .name = "__aeabi_dmul", .linkage = linkage }); + + const __aeabi_d2h = @import("compiler_rt/truncXfYf2.zig").__aeabi_d2h; + @export(__aeabi_d2h, .{ .name = "__aeabi_d2h", .linkage = linkage }); + + const __aeabi_f2ulz = @import("compiler_rt/fixunssfdi.zig").__aeabi_f2ulz; + @export(__aeabi_f2ulz, .{ .name = "__aeabi_f2ulz", .linkage = linkage }); + const __aeabi_d2ulz = @import("compiler_rt/fixunsdfdi.zig").__aeabi_d2ulz; + @export(__aeabi_d2ulz, .{ .name = "__aeabi_d2ulz", .linkage = linkage }); + + const __aeabi_f2lz = @import("compiler_rt/fixsfdi.zig").__aeabi_f2lz; + @export(__aeabi_f2lz, .{ .name = "__aeabi_f2lz", .linkage = linkage }); + const __aeabi_d2lz = @import("compiler_rt/fixdfdi.zig").__aeabi_d2lz; + @export(__aeabi_d2lz, .{ .name = "__aeabi_d2lz", .linkage = linkage }); + + const __aeabi_d2uiz = @import("compiler_rt/fixunsdfsi.zig").__aeabi_d2uiz; + @export(__aeabi_d2uiz, .{ .name = "__aeabi_d2uiz", .linkage = linkage }); + + const __aeabi_h2f = @import("compiler_rt/extendXfYf2.zig").__aeabi_h2f; + @export(__aeabi_h2f, .{ .name = "__aeabi_h2f", .linkage = linkage }); + const __aeabi_f2h = @import("compiler_rt/truncXfYf2.zig").__aeabi_f2h; + @export(__aeabi_f2h, .{ .name = "__aeabi_f2h", .linkage = linkage }); + + const __aeabi_i2f = @import("compiler_rt/floatsiXf.zig").__aeabi_i2f; + @export(__aeabi_i2f, .{ .name = "__aeabi_i2f", .linkage = linkage }); + const __aeabi_d2f = @import("compiler_rt/truncXfYf2.zig").__aeabi_d2f; + @export(__aeabi_d2f, .{ .name = "__aeabi_d2f", .linkage = linkage }); + + const __aeabi_fadd = @import("compiler_rt/addXf3.zig").__aeabi_fadd; + @export(__aeabi_fadd, .{ .name = "__aeabi_fadd", .linkage = linkage }); + const __aeabi_dadd = @import("compiler_rt/addXf3.zig").__aeabi_dadd; + @export(__aeabi_dadd, .{ .name = "__aeabi_dadd", .linkage = linkage }); + const __aeabi_fsub = @import("compiler_rt/addXf3.zig").__aeabi_fsub; + @export(__aeabi_fsub, .{ .name = "__aeabi_fsub", .linkage = linkage }); + const __aeabi_dsub = @import("compiler_rt/addXf3.zig").__aeabi_dsub; + @export(__aeabi_dsub, .{ .name = "__aeabi_dsub", .linkage = linkage }); + + const __aeabi_f2uiz = @import("compiler_rt/fixunssfsi.zig").__aeabi_f2uiz; + @export(__aeabi_f2uiz, .{ .name = "__aeabi_f2uiz", .linkage = linkage }); + + const __aeabi_f2iz = @import("compiler_rt/fixsfsi.zig").__aeabi_f2iz; + @export(__aeabi_f2iz, .{ .name = "__aeabi_f2iz", .linkage = linkage }); + const __aeabi_d2iz = @import("compiler_rt/fixdfsi.zig").__aeabi_d2iz; + @export(__aeabi_d2iz, .{ .name = "__aeabi_d2iz", .linkage = linkage }); + + const __aeabi_fdiv = @import("compiler_rt/divsf3.zig").__aeabi_fdiv; + @export(__aeabi_fdiv, .{ .name = "__aeabi_fdiv", .linkage = linkage }); + const __aeabi_ddiv = @import("compiler_rt/divdf3.zig").__aeabi_ddiv; + @export(__aeabi_ddiv, .{ .name = "__aeabi_ddiv", .linkage = linkage }); + + const __aeabi_llsl = @import("compiler_rt/shift.zig").__aeabi_llsl; + @export(__aeabi_llsl, .{ .name = "__aeabi_llsl", .linkage = linkage }); + const __aeabi_lasr = @import("compiler_rt/shift.zig").__aeabi_lasr; + @export(__aeabi_lasr, .{ .name = "__aeabi_lasr", .linkage = linkage }); + const __aeabi_llsr = @import("compiler_rt/shift.zig").__aeabi_llsr; + @export(__aeabi_llsr, .{ .name = "__aeabi_llsr", .linkage = linkage }); + + const __aeabi_fcmpeq = @import("compiler_rt/compareXf2.zig").__aeabi_fcmpeq; + @export(__aeabi_fcmpeq, .{ .name = "__aeabi_fcmpeq", .linkage = linkage }); + const __aeabi_fcmplt = @import("compiler_rt/compareXf2.zig").__aeabi_fcmplt; + @export(__aeabi_fcmplt, .{ .name = "__aeabi_fcmplt", .linkage = linkage }); + const __aeabi_fcmple = @import("compiler_rt/compareXf2.zig").__aeabi_fcmple; + @export(__aeabi_fcmple, .{ .name = "__aeabi_fcmple", .linkage = linkage }); + const __aeabi_fcmpge = @import("compiler_rt/compareXf2.zig").__aeabi_fcmpge; + @export(__aeabi_fcmpge, .{ .name = "__aeabi_fcmpge", .linkage = linkage }); + const __aeabi_fcmpgt = @import("compiler_rt/compareXf2.zig").__aeabi_fcmpgt; + @export(__aeabi_fcmpgt, .{ .name = "__aeabi_fcmpgt", .linkage = linkage }); + const __aeabi_fcmpun = @import("compiler_rt/compareXf2.zig").__aeabi_fcmpun; + @export(__aeabi_fcmpun, .{ .name = "__aeabi_fcmpun", .linkage = linkage }); + + const __aeabi_dcmpeq = @import("compiler_rt/compareXf2.zig").__aeabi_dcmpeq; + @export(__aeabi_dcmpeq, .{ .name = "__aeabi_dcmpeq", .linkage = linkage }); + const __aeabi_dcmplt = @import("compiler_rt/compareXf2.zig").__aeabi_dcmplt; + @export(__aeabi_dcmplt, .{ .name = "__aeabi_dcmplt", .linkage = linkage }); + const __aeabi_dcmple = @import("compiler_rt/compareXf2.zig").__aeabi_dcmple; + @export(__aeabi_dcmple, .{ .name = "__aeabi_dcmple", .linkage = linkage }); + const __aeabi_dcmpge = @import("compiler_rt/compareXf2.zig").__aeabi_dcmpge; + @export(__aeabi_dcmpge, .{ .name = "__aeabi_dcmpge", .linkage = linkage }); + const __aeabi_dcmpgt = @import("compiler_rt/compareXf2.zig").__aeabi_dcmpgt; + @export(__aeabi_dcmpgt, .{ .name = "__aeabi_dcmpgt", .linkage = linkage }); + const __aeabi_dcmpun = @import("compiler_rt/compareXf2.zig").__aeabi_dcmpun; + @export(__aeabi_dcmpun, .{ .name = "__aeabi_dcmpun", .linkage = linkage }); + } + + if (arch == .i386 and abi == .msvc) { + // Don't let LLVM apply the stdcall name mangling on those MSVC builtins + const _alldiv = @import("compiler_rt/aulldiv.zig")._alldiv; + @export(_alldiv, .{ .name = "\x01__alldiv", .linkage = strong_linkage }); + const _aulldiv = @import("compiler_rt/aulldiv.zig")._aulldiv; + @export(_aulldiv, .{ .name = "\x01__aulldiv", .linkage = strong_linkage }); + const _allrem = @import("compiler_rt/aullrem.zig")._allrem; + @export(_allrem, .{ .name = "\x01__allrem", .linkage = strong_linkage }); + const _aullrem = @import("compiler_rt/aullrem.zig")._aullrem; + @export(_aullrem, .{ .name = "\x01__aullrem", .linkage = strong_linkage }); + } + + if (arch.isSPARC()) { + // SPARC systems use a different naming scheme + const _Qp_add = @import("compiler_rt/sparc.zig")._Qp_add; + @export(_Qp_add, .{ .name = "_Qp_add", .linkage = linkage }); + const _Qp_div = @import("compiler_rt/sparc.zig")._Qp_div; + @export(_Qp_div, .{ .name = "_Qp_div", .linkage = linkage }); + const _Qp_mul = @import("compiler_rt/sparc.zig")._Qp_mul; + @export(_Qp_mul, .{ .name = "_Qp_mul", .linkage = linkage }); + const _Qp_sub = @import("compiler_rt/sparc.zig")._Qp_sub; + @export(_Qp_sub, .{ .name = "_Qp_sub", .linkage = linkage }); + + const _Qp_cmp = @import("compiler_rt/sparc.zig")._Qp_cmp; + @export(_Qp_cmp, .{ .name = "_Qp_cmp", .linkage = linkage }); + const _Qp_feq = @import("compiler_rt/sparc.zig")._Qp_feq; + @export(_Qp_feq, .{ .name = "_Qp_feq", .linkage = linkage }); + const _Qp_fne = @import("compiler_rt/sparc.zig")._Qp_fne; + @export(_Qp_fne, .{ .name = "_Qp_fne", .linkage = linkage }); + const _Qp_flt = @import("compiler_rt/sparc.zig")._Qp_flt; + @export(_Qp_flt, .{ .name = "_Qp_flt", .linkage = linkage }); + const _Qp_fle = @import("compiler_rt/sparc.zig")._Qp_fle; + @export(_Qp_fle, .{ .name = "_Qp_fle", .linkage = linkage }); + const _Qp_fgt = @import("compiler_rt/sparc.zig")._Qp_fgt; + @export(_Qp_fgt, .{ .name = "_Qp_fgt", .linkage = linkage }); + const _Qp_fge = @import("compiler_rt/sparc.zig")._Qp_fge; + @export(_Qp_fge, .{ .name = "_Qp_fge", .linkage = linkage }); + + const _Qp_itoq = @import("compiler_rt/sparc.zig")._Qp_itoq; + @export(_Qp_itoq, .{ .name = "_Qp_itoq", .linkage = linkage }); + const _Qp_uitoq = @import("compiler_rt/sparc.zig")._Qp_uitoq; + @export(_Qp_uitoq, .{ .name = "_Qp_uitoq", .linkage = linkage }); + const _Qp_xtoq = @import("compiler_rt/sparc.zig")._Qp_xtoq; + @export(_Qp_xtoq, .{ .name = "_Qp_xtoq", .linkage = linkage }); + const _Qp_uxtoq = @import("compiler_rt/sparc.zig")._Qp_uxtoq; + @export(_Qp_uxtoq, .{ .name = "_Qp_uxtoq", .linkage = linkage }); + const _Qp_stoq = @import("compiler_rt/sparc.zig")._Qp_stoq; + @export(_Qp_stoq, .{ .name = "_Qp_stoq", .linkage = linkage }); + const _Qp_dtoq = @import("compiler_rt/sparc.zig")._Qp_dtoq; + @export(_Qp_dtoq, .{ .name = "_Qp_dtoq", .linkage = linkage }); + const _Qp_qtoi = @import("compiler_rt/sparc.zig")._Qp_qtoi; + @export(_Qp_qtoi, .{ .name = "_Qp_qtoi", .linkage = linkage }); + const _Qp_qtoui = @import("compiler_rt/sparc.zig")._Qp_qtoui; + @export(_Qp_qtoui, .{ .name = "_Qp_qtoui", .linkage = linkage }); + const _Qp_qtox = @import("compiler_rt/sparc.zig")._Qp_qtox; + @export(_Qp_qtox, .{ .name = "_Qp_qtox", .linkage = linkage }); + const _Qp_qtoux = @import("compiler_rt/sparc.zig")._Qp_qtoux; + @export(_Qp_qtoux, .{ .name = "_Qp_qtoux", .linkage = linkage }); + const _Qp_qtos = @import("compiler_rt/sparc.zig")._Qp_qtos; + @export(_Qp_qtos, .{ .name = "_Qp_qtos", .linkage = linkage }); + const _Qp_qtod = @import("compiler_rt/sparc.zig")._Qp_qtod; + @export(_Qp_qtod, .{ .name = "_Qp_qtod", .linkage = linkage }); + } + + if ((arch == .powerpc or arch.isPPC64()) and !is_test) { + @export(__addtf3, .{ .name = "__addkf3", .linkage = linkage }); + @export(__subtf3, .{ .name = "__subkf3", .linkage = linkage }); + @export(__multf3, .{ .name = "__mulkf3", .linkage = linkage }); + @export(__divtf3, .{ .name = "__divkf3", .linkage = linkage }); + @export(__extendsftf2, .{ .name = "__extendsfkf2", .linkage = linkage }); + @export(__extenddftf2, .{ .name = "__extenddfkf2", .linkage = linkage }); + @export(__trunctfsf2, .{ .name = "__trunckfsf2", .linkage = linkage }); + @export(__trunctfdf2, .{ .name = "__trunckfdf2", .linkage = linkage }); + @export(__fixtfdi, .{ .name = "__fixkfdi", .linkage = linkage }); + @export(__fixtfsi, .{ .name = "__fixkfsi", .linkage = linkage }); + @export(__fixunstfsi, .{ .name = "__fixunskfsi", .linkage = linkage }); + @export(__fixunstfdi, .{ .name = "__fixunskfdi", .linkage = linkage }); + @export(__floatsitf, .{ .name = "__floatsikf", .linkage = linkage }); + @export(__floatditf, .{ .name = "__floatdikf", .linkage = linkage }); + @export(__floatunditf, .{ .name = "__floatundikf", .linkage = linkage }); + @export(__floatunsitf, .{ .name = "__floatunsikf", .linkage = linkage }); + + @export(__letf2, .{ .name = "__eqkf2", .linkage = linkage }); + @export(__letf2, .{ .name = "__nekf2", .linkage = linkage }); + @export(__getf2, .{ .name = "__gekf2", .linkage = linkage }); + @export(__letf2, .{ .name = "__ltkf2", .linkage = linkage }); + @export(__letf2, .{ .name = "__lekf2", .linkage = linkage }); + @export(__getf2, .{ .name = "__gtkf2", .linkage = linkage }); + @export(__unordtf2, .{ .name = "__unordkf2", .linkage = linkage }); + } + + if (builtin.os.tag == .windows) { + // Default stack-probe functions emitted by LLVM + if (is_mingw) { + const _chkstk = @import("compiler_rt/stack_probe.zig")._chkstk; + @export(_chkstk, .{ .name = "_alloca", .linkage = strong_linkage }); + const ___chkstk_ms = @import("compiler_rt/stack_probe.zig").___chkstk_ms; + @export(___chkstk_ms, .{ .name = "___chkstk_ms", .linkage = strong_linkage }); + } else if (!builtin.link_libc) { + // This symbols are otherwise exported by MSVCRT.lib + const _chkstk = @import("compiler_rt/stack_probe.zig")._chkstk; + @export(_chkstk, .{ .name = "_chkstk", .linkage = strong_linkage }); + const __chkstk = @import("compiler_rt/stack_probe.zig").__chkstk; + @export(__chkstk, .{ .name = "__chkstk", .linkage = strong_linkage }); + } + + switch (arch) { + .i386 => { + const __divti3 = @import("compiler_rt/divti3.zig").__divti3; + @export(__divti3, .{ .name = "__divti3", .linkage = linkage }); + const __modti3 = @import("compiler_rt/modti3.zig").__modti3; + @export(__modti3, .{ .name = "__modti3", .linkage = linkage }); + const __multi3 = @import("compiler_rt/multi3.zig").__multi3; + @export(__multi3, .{ .name = "__multi3", .linkage = linkage }); + const __udivti3 = @import("compiler_rt/udivti3.zig").__udivti3; + @export(__udivti3, .{ .name = "__udivti3", .linkage = linkage }); + const __udivmodti4 = @import("compiler_rt/udivmodti4.zig").__udivmodti4; + @export(__udivmodti4, .{ .name = "__udivmodti4", .linkage = linkage }); + const __umodti3 = @import("compiler_rt/umodti3.zig").__umodti3; + @export(__umodti3, .{ .name = "__umodti3", .linkage = linkage }); + }, + .x86_64 => { + // The "ti" functions must use Vector(2, u64) parameter types to adhere to the ABI + // that LLVM expects compiler-rt to have. + const __divti3_windows_x86_64 = @import("compiler_rt/divti3.zig").__divti3_windows_x86_64; + @export(__divti3_windows_x86_64, .{ .name = "__divti3", .linkage = linkage }); + const __modti3_windows_x86_64 = @import("compiler_rt/modti3.zig").__modti3_windows_x86_64; + @export(__modti3_windows_x86_64, .{ .name = "__modti3", .linkage = linkage }); + const __multi3_windows_x86_64 = @import("compiler_rt/multi3.zig").__multi3_windows_x86_64; + @export(__multi3_windows_x86_64, .{ .name = "__multi3", .linkage = linkage }); + const __udivti3_windows_x86_64 = @import("compiler_rt/udivti3.zig").__udivti3_windows_x86_64; + @export(__udivti3_windows_x86_64, .{ .name = "__udivti3", .linkage = linkage }); + const __udivmodti4_windows_x86_64 = @import("compiler_rt/udivmodti4.zig").__udivmodti4_windows_x86_64; + @export(__udivmodti4_windows_x86_64, .{ .name = "__udivmodti4", .linkage = linkage }); + const __umodti3_windows_x86_64 = @import("compiler_rt/umodti3.zig").__umodti3_windows_x86_64; + @export(__umodti3_windows_x86_64, .{ .name = "__umodti3", .linkage = linkage }); + }, + else => {}, + } + if (arch.isAARCH64()) { + const __chkstk = @import("compiler_rt/stack_probe.zig").__chkstk; + @export(__chkstk, .{ .name = "__chkstk", .linkage = strong_linkage }); + const __divti3_windows = @import("compiler_rt/divti3.zig").__divti3; + @export(__divti3_windows, .{ .name = "__divti3", .linkage = linkage }); + const __modti3 = @import("compiler_rt/modti3.zig").__modti3; + @export(__modti3, .{ .name = "__modti3", .linkage = linkage }); + const __udivti3_windows = @import("compiler_rt/udivti3.zig").__udivti3; + @export(__udivti3_windows, .{ .name = "__udivti3", .linkage = linkage }); + const __umodti3 = @import("compiler_rt/umodti3.zig").__umodti3; + @export(__umodti3, .{ .name = "__umodti3", .linkage = linkage }); + } + } else { + const __divti3 = @import("compiler_rt/divti3.zig").__divti3; + @export(__divti3, .{ .name = "__divti3", .linkage = linkage }); const __modti3 = @import("compiler_rt/modti3.zig").__modti3; @export(__modti3, .{ .name = "__modti3", .linkage = linkage }); - const __udivti3_windows = @import("compiler_rt/udivti3.zig").__udivti3; - @export(__udivti3_windows, .{ .name = "__udivti3", .linkage = linkage }); + const __multi3 = @import("compiler_rt/multi3.zig").__multi3; + @export(__multi3, .{ .name = "__multi3", .linkage = linkage }); + const __udivti3 = @import("compiler_rt/udivti3.zig").__udivti3; + @export(__udivti3, .{ .name = "__udivti3", .linkage = linkage }); + const __udivmodti4 = @import("compiler_rt/udivmodti4.zig").__udivmodti4; + @export(__udivmodti4, .{ .name = "__udivmodti4", .linkage = linkage }); const __umodti3 = @import("compiler_rt/umodti3.zig").__umodti3; @export(__umodti3, .{ .name = "__umodti3", .linkage = linkage }); } - } else { - const __divti3 = @import("compiler_rt/divti3.zig").__divti3; - @export(__divti3, .{ .name = "__divti3", .linkage = linkage }); - const __modti3 = @import("compiler_rt/modti3.zig").__modti3; - @export(__modti3, .{ .name = "__modti3", .linkage = linkage }); - const __multi3 = @import("compiler_rt/multi3.zig").__multi3; - @export(__multi3, .{ .name = "__multi3", .linkage = linkage }); - const __udivti3 = @import("compiler_rt/udivti3.zig").__udivti3; - @export(__udivti3, .{ .name = "__udivti3", .linkage = linkage }); - const __udivmodti4 = @import("compiler_rt/udivmodti4.zig").__udivmodti4; - @export(__udivmodti4, .{ .name = "__udivmodti4", .linkage = linkage }); - const __umodti3 = @import("compiler_rt/umodti3.zig").__umodti3; - @export(__umodti3, .{ .name = "__umodti3", .linkage = linkage }); - } - const __muloti4 = @import("compiler_rt/muloti4.zig").__muloti4; - @export(__muloti4, .{ .name = "__muloti4", .linkage = linkage }); - const __mulodi4 = @import("compiler_rt/mulodi4.zig").__mulodi4; - @export(__mulodi4, .{ .name = "__mulodi4", .linkage = linkage }); + const __muloti4 = @import("compiler_rt/muloti4.zig").__muloti4; + @export(__muloti4, .{ .name = "__muloti4", .linkage = linkage }); + const __mulodi4 = @import("compiler_rt/mulodi4.zig").__mulodi4; + @export(__mulodi4, .{ .name = "__mulodi4", .linkage = linkage }); - _ = @import("compiler_rt/atomics.zig"); + _ = @import("compiler_rt/atomics.zig"); + } } // Avoid dragging in the runtime safety mechanisms into this .o file, // unless we're trying to test this file. -pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { +pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn { _ = error_return_trace; @setCold(true); + if (builtin.zig_is_stage2) { + while (true) { + @breakpoint(); + } + } if (is_test) { std.debug.panic("{s}", .{msg}); } else { diff --git a/lib/std/special/compiler_rt/extendXfYf2.zig b/lib/std/special/compiler_rt/extendXfYf2.zig index 5571bd9ed3..7afb6b1645 100644 --- a/lib/std/special/compiler_rt/extendXfYf2.zig +++ b/lib/std/special/compiler_rt/extendXfYf2.zig @@ -3,23 +3,23 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; pub fn __extendsfdf2(a: f32) callconv(.C) f64 { - return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f64, f32, @bitCast(u32, a) }); + return extendXfYf2(f64, f32, @bitCast(u32, a)); } pub fn __extenddftf2(a: f64) callconv(.C) f128 { - return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f128, f64, @bitCast(u64, a) }); + return extendXfYf2(f128, f64, @bitCast(u64, a)); } pub fn __extendsftf2(a: f32) callconv(.C) f128 { - return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f128, f32, @bitCast(u32, a) }); + return extendXfYf2(f128, f32, @bitCast(u32, a)); } pub fn __extendhfsf2(a: u16) callconv(.C) f32 { - return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f32, f16, a }); + return extendXfYf2(f32, f16, a); } pub fn __extendhftf2(a: u16) callconv(.C) f128 { - return @call(.{ .modifier = .always_inline }, extendXfYf2, .{ f128, f16, a }); + return extendXfYf2(f128, f16, a); } pub fn __aeabi_h2f(arg: u16) callconv(.AAPCS) f32 { @@ -34,7 +34,7 @@ pub fn __aeabi_f2d(arg: f32) callconv(.AAPCS) f64 { const CHAR_BIT = 8; -fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: std.meta.Int(.unsigned, @typeInfo(src_t).Float.bits)) dst_t { +inline fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: std.meta.Int(.unsigned, @typeInfo(src_t).Float.bits)) dst_t { @setRuntimeSafety(builtin.is_test); const src_rep_t = std.meta.Int(.unsigned, @typeInfo(src_t).Float.bits); diff --git a/src/Air.zig b/src/Air.zig index ad95200001..c3181fac60 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -227,9 +227,12 @@ pub const Inst = struct { /// Indicates the program counter will never get to this instruction. /// Result type is always noreturn; no instructions in a block follow this one. unreach, - /// Convert from one float type to another. + /// Convert from a float type to a smaller one. /// Uses the `ty_op` field. - floatcast, + fptrunc, + /// Convert from a float type to a wider one. + /// Uses the `ty_op` field. + fpext, /// Returns an integer with a different type than the operand. The new type may have /// fewer, the same, or more bits than the operand type. However, the instruction /// guarantees that the same integer value fits in both types. @@ -586,7 +589,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .not, .bitcast, .load, - .floatcast, + .fpext, + .fptrunc, .intcast, .trunc, .optional_payload, diff --git a/src/AstGen.zig b/src/AstGen.zig index 176203d37f..6a533dff11 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2166,6 +2166,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .ensure_result_used, .ensure_result_non_error, .@"export", + .export_value, .set_eval_branch_quota, .ensure_err_payload_void, .atomic_store, @@ -7095,32 +7096,55 @@ fn builtinCall( .identifier => { const ident_token = main_tokens[params[0]]; decl_name = try astgen.identAsString(ident_token); - { - var s = scope; - while (true) switch (s.tag) { - .local_val => { - const local_val = s.cast(Scope.LocalVal).?; - if (local_val.name == decl_name) { - local_val.used = true; - break; + + var s = scope; + var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already + while (true) switch (s.tag) { + .local_val => { + const local_val = s.cast(Scope.LocalVal).?; + if (local_val.name == decl_name) { + local_val.used = true; + _ = try gz.addPlNode(.export_value, node, Zir.Inst.ExportValue{ + .operand = local_val.inst, + .options = try comptimeExpr(gz, scope, .{ .coerced_ty = .export_options_type }, params[1]), + }); + return rvalue(gz, rl, .void_value, node); + } + s = local_val.parent; + }, + .local_ptr => { + const local_ptr = s.cast(Scope.LocalPtr).?; + if (local_ptr.name == decl_name) { + if (!local_ptr.maybe_comptime) + return astgen.failNode(params[0], "unable to export runtime-known value", .{}); + local_ptr.used = true; + const loaded = try gz.addUnNode(.load, local_ptr.ptr, node); + _ = try gz.addPlNode(.export_value, node, Zir.Inst.ExportValue{ + .operand = loaded, + .options = try comptimeExpr(gz, scope, .{ .coerced_ty = .export_options_type }, params[1]), + }); + return rvalue(gz, rl, .void_value, node); + } + s = local_ptr.parent; + }, + .gen_zir => s = s.cast(GenZir).?.parent, + .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent, + .namespace => { + const ns = s.cast(Scope.Namespace).?; + if (ns.decls.get(decl_name)) |i| { + if (found_already) |f| { + return astgen.failNodeNotes(node, "ambiguous reference", .{}, &.{ + try astgen.errNoteNode(f, "declared here", .{}), + try astgen.errNoteNode(i, "also declared here", .{}), + }); } - s = local_val.parent; - }, - .local_ptr => { - const local_ptr = s.cast(Scope.LocalPtr).?; - if (local_ptr.name == decl_name) { - if (!local_ptr.maybe_comptime) - return astgen.failNode(params[0], "unable to export runtime-known value", .{}); - local_ptr.used = true; - break; - } - s = local_ptr.parent; - }, - .gen_zir => s = s.cast(GenZir).?.parent, - .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent, - .namespace, .top => break, - }; - } + // We found a match but must continue looking for ambiguous references to decls. + found_already = i; + } + s = ns.parent; + }, + .top => break, + }; }, .field_access => { const namespace_node = node_datas[params[0]].lhs; diff --git a/src/Liveness.zig b/src/Liveness.zig index 1d34da091d..dd0899e745 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -274,7 +274,8 @@ fn analyzeInst( .not, .bitcast, .load, - .floatcast, + .fpext, + .fptrunc, .intcast, .trunc, .optional_payload, diff --git a/src/Module.zig b/src/Module.zig index 88817efc26..70c90d9c01 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2389,6 +2389,7 @@ pub fn deinit(mod: *Module) void { fn freeExportList(gpa: *Allocator, export_list: []*Export) void { for (export_list) |exp| { gpa.free(exp.options.name); + if (exp.options.section) |s| gpa.free(s); gpa.destroy(exp); } gpa.free(export_list); @@ -3317,7 +3318,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { return mod.fail(&block_scope.base, export_src, "export of inline function", .{}); } // The scope needs to have the decl in it. - try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl); + const options: std.builtin.ExportOptions = .{ .name = mem.spanZ(decl.name) }; + try mod.analyzeExport(&block_scope.base, export_src, options, decl); } return type_changed or is_inline != prev_is_inline; } @@ -3376,7 +3378,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { if (decl.is_exported) { const export_src = src; // TODO point to the export token // The scope needs to have the decl in it. - try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl); + const options: std.builtin.ExportOptions = .{ .name = mem.spanZ(decl.name) }; + try mod.analyzeExport(&block_scope.base, export_src, options, decl); } return type_changed; @@ -4119,7 +4122,7 @@ pub fn analyzeExport( mod: *Module, scope: *Scope, src: LazySrcLoc, - borrowed_symbol_name: []const u8, + borrowed_options: std.builtin.ExportOptions, exported_decl: *Decl, ) !void { try mod.ensureDeclAnalyzed(exported_decl); @@ -4128,23 +4131,32 @@ pub fn analyzeExport( else => return mod.fail(scope, src, "unable to export type '{}'", .{exported_decl.ty}), } - try mod.decl_exports.ensureUnusedCapacity(mod.gpa, 1); - try mod.export_owners.ensureUnusedCapacity(mod.gpa, 1); + const gpa = mod.gpa; - const new_export = try mod.gpa.create(Export); - errdefer mod.gpa.destroy(new_export); + try mod.decl_exports.ensureUnusedCapacity(gpa, 1); + try mod.export_owners.ensureUnusedCapacity(gpa, 1); - const symbol_name = try mod.gpa.dupe(u8, borrowed_symbol_name); - errdefer mod.gpa.free(symbol_name); + const new_export = try gpa.create(Export); + errdefer gpa.destroy(new_export); + + const symbol_name = try gpa.dupe(u8, borrowed_options.name); + errdefer gpa.free(symbol_name); + + const section: ?[]const u8 = if (borrowed_options.section) |s| try gpa.dupe(u8, s) else null; + errdefer if (section) |s| gpa.free(s); const owner_decl = scope.ownerDecl().?; log.debug("exporting Decl '{s}' as symbol '{s}' from Decl '{s}'", .{ - exported_decl.name, borrowed_symbol_name, owner_decl.name, + exported_decl.name, symbol_name, owner_decl.name, }); new_export.* = .{ - .options = .{ .name = symbol_name }, + .options = .{ + .name = symbol_name, + .linkage = borrowed_options.linkage, + .section = section, + }, .src = src, .link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = {} }, @@ -4165,18 +4177,18 @@ pub fn analyzeExport( if (!eo_gop.found_existing) { eo_gop.value_ptr.* = &[0]*Export{}; } - eo_gop.value_ptr.* = try mod.gpa.realloc(eo_gop.value_ptr.*, eo_gop.value_ptr.len + 1); + eo_gop.value_ptr.* = try gpa.realloc(eo_gop.value_ptr.*, eo_gop.value_ptr.len + 1); eo_gop.value_ptr.*[eo_gop.value_ptr.len - 1] = new_export; - errdefer eo_gop.value_ptr.* = mod.gpa.shrink(eo_gop.value_ptr.*, eo_gop.value_ptr.len - 1); + errdefer eo_gop.value_ptr.* = gpa.shrink(eo_gop.value_ptr.*, eo_gop.value_ptr.len - 1); // Add to exported_decl table. const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl); if (!de_gop.found_existing) { de_gop.value_ptr.* = &[0]*Export{}; } - de_gop.value_ptr.* = try mod.gpa.realloc(de_gop.value_ptr.*, de_gop.value_ptr.len + 1); + de_gop.value_ptr.* = try gpa.realloc(de_gop.value_ptr.*, de_gop.value_ptr.len + 1); de_gop.value_ptr.*[de_gop.value_ptr.len - 1] = new_export; - errdefer de_gop.value_ptr.* = mod.gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1); + errdefer de_gop.value_ptr.* = gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1); } /// Takes ownership of `name` even if it returns an error. diff --git a/src/Sema.zig b/src/Sema.zig index 91d12b7b31..a0e3250e56 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -458,6 +458,11 @@ pub fn analyzeBody( i += 1; continue; }, + .export_value => { + try sema.zirExportValue(block, inst); + i += 1; + continue; + }, .set_align_stack => { try sema.zirSetAlignStack(block, inst); i += 1; @@ -2392,30 +2397,33 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Export, inst_data.payload_index).data; const src = inst_data.src(); - const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const decl_name = sema.code.nullTerminatedString(extra.decl_name); if (extra.namespace != .none) { return sema.mod.fail(&block.base, src, "TODO: implement exporting with field access", .{}); } - const decl = try sema.lookupIdentifier(block, lhs_src, decl_name); - const options = try sema.resolveInstConst(block, rhs_src, extra.options); - const struct_obj = options.ty.castTag(.@"struct").?.data; - const fields = options.val.castTag(.@"struct").?.data[0..struct_obj.fields.count()]; - const name_index = struct_obj.fields.getIndex("name").?; - const linkage_index = struct_obj.fields.getIndex("linkage").?; - const section_index = struct_obj.fields.getIndex("section").?; - const export_name = try fields[name_index].toAllocatedBytes(sema.arena); - const linkage = fields[linkage_index].toEnum(std.builtin.GlobalLinkage); + const decl = try sema.lookupIdentifier(block, operand_src, decl_name); + const options = try sema.resolveExportOptions(block, options_src, extra.options); + try sema.mod.analyzeExport(&block.base, src, options, decl); +} - if (linkage != .Strong) { - return sema.mod.fail(&block.base, src, "TODO: implement exporting with non-strong linkage", .{}); - } - if (!fields[section_index].isNull()) { - return sema.mod.fail(&block.base, src, "TODO: implement exporting with linksection", .{}); - } +fn zirExportValue(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { + const tracy = trace(@src()); + defer tracy.end(); - try sema.mod.analyzeExport(&block.base, src, export_name, decl); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.ExportValue, inst_data.payload_index).data; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const operand = try sema.resolveInstConst(block, operand_src, extra.operand); + const options = try sema.resolveExportOptions(block, options_src, extra.options); + const decl = switch (operand.val.tag()) { + .function => operand.val.castTag(.function).?.data.owner_decl, + else => return sema.mod.fail(&block.base, operand_src, "TODO implement exporting arbitrary Value objects", .{}), // TODO put this Value into an anonymous Decl and then export it. + }; + try sema.mod.analyzeExport(&block.base, src, options, decl); } fn zirSetAlignStack(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { @@ -4516,11 +4524,18 @@ fn zirFloatCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE if (try sema.isComptimeKnown(block, operand_src, operand)) { return sema.coerce(block, dest_type, operand, operand_src); - } else if (dest_is_comptime_float) { + } + if (dest_is_comptime_float) { return sema.mod.fail(&block.base, src, "unable to cast runtime value to 'comptime_float'", .{}); } - - return sema.mod.fail(&block.base, src, "TODO implement analyze widen or shorten float", .{}); + const target = sema.mod.getTarget(); + const src_bits = operand_ty.floatBits(target); + const dst_bits = dest_type.floatBits(target); + if (dst_bits >= src_bits) { + return sema.coerce(block, dest_type, operand, operand_src); + } + try sema.requireRuntimeBlock(block, operand_src); + return block.addTyOp(.fptrunc, dest_type, operand); } fn zirElemVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -7936,6 +7951,31 @@ fn checkAtomicOperandType( } } +fn resolveExportOptions( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + zir_ref: Zir.Inst.Ref, +) CompileError!std.builtin.ExportOptions { + const export_options_ty = try sema.getBuiltinType(block, src, "ExportOptions"); + const air_ref = sema.resolveInst(zir_ref); + const coerced = try sema.coerce(block, export_options_ty, air_ref, src); + const val = try sema.resolveConstValue(block, src, coerced); + const fields = val.castTag(.@"struct").?.data; + const struct_obj = export_options_ty.castTag(.@"struct").?.data; + const name_index = struct_obj.fields.getIndex("name").?; + const linkage_index = struct_obj.fields.getIndex("linkage").?; + const section_index = struct_obj.fields.getIndex("section").?; + if (!fields[section_index].isNull()) { + return sema.mod.fail(&block.base, src, "TODO: implement exporting with linksection", .{}); + } + return std.builtin.ExportOptions{ + .name = try fields[name_index].toAllocatedBytes(sema.arena), + .linkage = fields[linkage_index].toEnum(std.builtin.GlobalLinkage), + .section = null, // TODO + }; +} + fn resolveAtomicOrder( sema: *Sema, block: *Scope.Block, @@ -9581,7 +9621,7 @@ fn coerce( const dst_bits = dest_type.floatBits(target); if (dst_bits >= src_bits) { try sema.requireRuntimeBlock(block, inst_src); - return block.addTyOp(.floatcast, dest_type, inst); + return block.addTyOp(.fpext, dest_type, inst); } } }, @@ -9729,35 +9769,53 @@ fn coerceNum( const target = sema.mod.getTarget(); switch (dst_zig_tag) { - .ComptimeInt, .Int => { - if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { + .ComptimeInt, .Int => switch (src_zig_tag) { + .Float, .ComptimeFloat => { if (val.floatHasFraction()) { - return sema.mod.fail(&block.base, inst_src, "fractional component prevents float value {} from being casted to type '{}'", .{ val, inst_ty }); + return sema.mod.fail(&block.base, inst_src, "fractional component prevents float value {} from coercion to type '{}'", .{ val, dest_type }); } return sema.mod.fail(&block.base, inst_src, "TODO float to int", .{}); - } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { + }, + .Int, .ComptimeInt => { if (!val.intFitsInType(dest_type, target)) { return sema.mod.fail(&block.base, inst_src, "type {} cannot represent integer value {}", .{ dest_type, val }); } return try sema.addConstant(dest_type, val); - } + }, + else => {}, }, - .ComptimeFloat, .Float => { - if (src_zig_tag == .Float or src_zig_tag == .ComptimeFloat) { - const res = val.floatCast(sema.arena, dest_type) catch |err| switch (err) { - error.Overflow => return sema.mod.fail( + .ComptimeFloat, .Float => switch (src_zig_tag) { + .ComptimeFloat => { + const result_val = try val.floatCast(sema.arena, dest_type); + return try sema.addConstant(dest_type, result_val); + }, + .Float => { + const result_val = try val.floatCast(sema.arena, dest_type); + if (!val.eql(result_val, dest_type)) { + return sema.mod.fail( &block.base, inst_src, - "cast of value {} to type '{}' loses information", - .{ val, dest_type }, - ), - error.OutOfMemory => return error.OutOfMemory, - }; - return try sema.addConstant(dest_type, res); - } else if (src_zig_tag == .Int or src_zig_tag == .ComptimeInt) { - const result_val = try val.intToFloat(sema.arena, dest_type, target); + "type {} cannot represent float value {}", + .{ dest_type, val }, + ); + } return try sema.addConstant(dest_type, result_val); - } + }, + .Int, .ComptimeInt => { + const result_val = try val.intToFloat(sema.arena, dest_type, target); + // TODO implement this compile error + //const int_again_val = try result_val.floatToInt(sema.arena, inst_ty); + //if (!int_again_val.eql(val, inst_ty)) { + // return sema.mod.fail( + // &block.base, + // inst_src, + // "type {} cannot represent integer value {}", + // .{ dest_type, val }, + // ); + //} + return try sema.addConstant(dest_type, result_val); + }, + else => {}, }, else => {}, } diff --git a/src/Zir.zig b/src/Zir.zig index 7f752bcced..d3b1678d97 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -319,9 +319,13 @@ pub const Inst = struct { /// `error.Foo` syntax. Uses the `str_tok` field of the Data union. error_value, /// Implements the `@export` builtin function, based on either an identifier to a Decl, - /// or field access of a Decl. + /// or field access of a Decl. The thing being exported is the Decl. /// Uses the `pl_node` union field. Payload is `Export`. @"export", + /// Implements the `@export` builtin function, based on a comptime-known value. + /// The thing being exported is the comptime-known value which is the operand. + /// Uses the `pl_node` union field. Payload is `ExportValue`. + export_value, /// Given a pointer to a struct or object that contains virtual fields, returns a pointer /// to the named field. The field name is stored in string_bytes. Used by a.b syntax. /// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field. @@ -1010,6 +1014,7 @@ pub const Inst = struct { .ensure_result_used, .ensure_result_non_error, .@"export", + .export_value, .field_ptr, .field_val, .field_ptr_named, @@ -1273,6 +1278,7 @@ pub const Inst = struct { .error_union_type = .pl_node, .error_value = .str_tok, .@"export" = .pl_node, + .export_value = .pl_node, .field_ptr = .pl_node, .field_val = .pl_node, .field_ptr_named = .pl_node, @@ -2843,6 +2849,12 @@ pub const Inst = struct { options: Ref, }; + pub const ExportValue = struct { + /// The comptime value to export. + operand: Ref, + options: Ref, + }; + /// Trailing: `CompileErrors.Item` for each `items_len`. pub const CompileErrors = struct { items_len: u32, diff --git a/src/codegen.zig b/src/codegen.zig index 54c1400211..06b520c9dd 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -859,7 +859,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .call => try self.airCall(inst), .cond_br => try self.airCondBr(inst), .dbg_stmt => try self.airDbgStmt(inst), - .floatcast => try self.airFloatCast(inst), + .fptrunc => try self.airFptrunc(inst), + .fpext => try self.airFpext(inst), .intcast => try self.airIntCast(inst), .trunc => try self.airTrunc(inst), .bool_to_int => try self.airBoolToInt(inst), @@ -1172,10 +1173,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none }); } - fn airFloatCast(self: *Self, inst: Air.Inst.Index) !void { + fn airFptrunc(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { - else => return self.fail("TODO implement floatCast for {}", .{self.target.cpu.arch}), + else => return self.fail("TODO implement airFptrunc for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + + fn airFpext(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement airFpext for {}", .{self.target.cpu.arch}), }; return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index c4e1d48062..a82f0e57f7 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -954,7 +954,12 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .atomic_rmw => try airAtomicRmw(f, inst), .atomic_load => try airAtomicLoad(f, inst), - .int_to_float, .float_to_int => try airSimpleCast(f, inst), + .int_to_float, + .float_to_int, + .fptrunc, + .fpext, + .ptrtoint, + => try airSimpleCast(f, inst), .atomic_store_unordered => try airAtomicStore(f, inst, toMemoryOrder(.Unordered)), .atomic_store_monotonic => try airAtomicStore(f, inst, toMemoryOrder(.Monotonic)), @@ -982,9 +987,6 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(f, inst), .wrap_errunion_payload => try airWrapErrUnionPay(f, inst), .wrap_errunion_err => try airWrapErrUnionErr(f, inst), - - .ptrtoint => return f.fail("TODO: C backend: implement codegen for ptrtoint", .{}), - .floatcast => return f.fail("TODO: C backend: implement codegen for floatcast", .{}), // zig fmt: on }; switch (result_value) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 3a977bc582..0d3bca2b03 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -472,7 +472,18 @@ pub const Object = struct { alias.setAliasee(llvm_fn); } else { const alias = self.llvm_module.addAlias(llvm_fn.typeOf(), llvm_fn, exp_name_z); - _ = alias; + switch (exp.options.linkage) { + .Internal => alias.setLinkage(.Internal), + .Strong => alias.setLinkage(.External), + .Weak => { + if (is_extern) { + alias.setLinkage(.ExternalWeak); + } else { + alias.setLinkage(.WeakODR); + } + }, + .LinkOnce => alias.setLinkage(.LinkOnceODR), + } } } } @@ -1137,7 +1148,8 @@ pub const FuncGen = struct { .cond_br => try self.airCondBr(inst), .intcast => try self.airIntCast(inst), .trunc => try self.airTrunc(inst), - .floatcast => try self.airFloatCast(inst), + .fptrunc => try self.airFptrunc(inst), + .fpext => try self.airFpext(inst), .ptrtoint => try self.airPtrToInt(inst), .load => try self.airLoad(inst), .loop => try self.airLoop(inst), @@ -2060,12 +2072,26 @@ pub const FuncGen = struct { return self.builder.buildTrunc(operand, dest_llvm_ty, ""); } - fn airFloatCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airFptrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; - // TODO split floatcast AIR into float_widen and float_shorten - return self.todo("implement 'airFloatCast'", .{}); + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst)); + + return self.builder.buildFPTrunc(operand, dest_llvm_ty, ""); + } + + fn airFpext(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst)); + + return self.builder.buildFPExt(operand, dest_llvm_ty, ""); } fn airPtrToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 039232426b..bf951fa67e 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -601,6 +601,22 @@ pub const Builder = opaque { DestTy: *const Type, Name: [*:0]const u8, ) *const Value; + + pub const buildFPTrunc = LLVMBuildFPTrunc; + extern fn LLVMBuildFPTrunc( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; + + pub const buildFPExt = LLVMBuildFPExt; + extern fn LLVMBuildFPExt( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; }; pub const IntPredicate = enum(c_uint) { diff --git a/src/print_air.zig b/src/print_air.zig index 3d13fa688f..a9ad993eb0 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -156,7 +156,8 @@ const Writer = struct { .not, .bitcast, .load, - .floatcast, + .fptrunc, + .fpext, .intcast, .trunc, .optional_payload, diff --git a/src/print_zir.zig b/src/print_zir.zig index 35b3da4479..4527b62262 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -285,6 +285,7 @@ const Writer = struct { => try self.writePlNodeBin(stream, inst), .@"export" => try self.writePlNodeExport(stream, inst), + .export_value => try self.writePlNodeExportValue(stream, inst), .call, .call_chkused, @@ -611,6 +612,17 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writePlNodeExportValue(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.ExportValue, inst_data.payload_index).data; + + try self.writeInstRef(stream, extra.operand); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.options); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writeStructInit(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.StructInit, inst_data.payload_index); diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 3b22101df9..1304dcc004 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -9325,6 +9325,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { buf_appendf(contents, "pub const single_threaded = %s;\n", bool_to_str(g->is_single_threaded)); buf_appendf(contents, "pub const abi = std.Target.Abi.%s;\n", cur_abi); buf_appendf(contents, "pub const cpu = std.Target.Cpu.baseline(.%s);\n", cur_arch); + buf_appendf(contents, "pub const stage2_arch: std.Target.Cpu.Arch = .%s;\n", cur_arch); buf_appendf(contents, "pub const os = std.Target.Os.Tag.defaultVersionRange(.%s);\n", cur_os); buf_appendf(contents, "pub const target = std.Target{\n" diff --git a/src/value.zig b/src/value.zig index bdf5b9c37c..cb5d211b1e 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1041,30 +1041,15 @@ pub const Value = extern union { } } - /// Converts an integer or a float to a float. - /// Returns `error.Overflow` if the value does not fit in the new type. - pub fn floatCast(self: Value, allocator: *Allocator, dest_ty: Type) !Value { + /// Converts an integer or a float to a float. May result in a loss of information. + /// Caller can find out by equality checking the result against the operand. + pub fn floatCast(self: Value, arena: *Allocator, dest_ty: Type) !Value { switch (dest_ty.tag()) { - .f16 => { - const res = try Value.Tag.float_16.create(allocator, self.toFloat(f16)); - if (!self.eql(res, dest_ty)) - return error.Overflow; - return res; - }, - .f32 => { - const res = try Value.Tag.float_32.create(allocator, self.toFloat(f32)); - if (!self.eql(res, dest_ty)) - return error.Overflow; - return res; - }, - .f64 => { - const res = try Value.Tag.float_64.create(allocator, self.toFloat(f64)); - if (!self.eql(res, dest_ty)) - return error.Overflow; - return res; - }, + .f16 => return Value.Tag.float_16.create(arena, self.toFloat(f16)), + .f32 => return Value.Tag.float_32.create(arena, self.toFloat(f32)), + .f64 => return Value.Tag.float_64.create(arena, self.toFloat(f64)), .f128, .comptime_float, .c_longdouble => { - return Value.Tag.float_128.create(allocator, self.toFloat(f128)); + return Value.Tag.float_128.create(arena, self.toFloat(f128)); }, else => unreachable, } diff --git a/test/behavior.zig b/test/behavior.zig index f328db968e..7fbba99a8c 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -13,6 +13,7 @@ test { _ = @import("behavior/atomics.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); _ = @import("behavior/translate_c_macros.zig"); + _ = @import("behavior/union.zig"); _ = @import("behavior/widening.zig"); if (builtin.zig_is_stage2) { @@ -149,7 +150,7 @@ test { _ = @import("behavior/typename.zig"); _ = @import("behavior/undefined.zig"); _ = @import("behavior/underscore.zig"); - _ = @import("behavior/union.zig"); + _ = @import("behavior/union_stage1.zig"); _ = @import("behavior/usingnamespace_stage1.zig"); _ = @import("behavior/var_args.zig"); _ = @import("behavior/vector.zig"); @@ -158,7 +159,6 @@ test { _ = @import("behavior/wasm.zig"); } _ = @import("behavior/while.zig"); - _ = @import("behavior/widening_stage1.zig"); _ = @import("behavior/src.zig"); _ = @import("behavior/translate_c_macros_stage1.zig"); } diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 323dd18f4d..14b5e374dd 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -2,816 +2,3 @@ const std = @import("std"); const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const Tag = std.meta.Tag; - -const Value = union(enum) { - Int: u64, - Array: [9]u8, -}; - -const Agg = struct { - val1: Value, - val2: Value, -}; - -const v1 = Value{ .Int = 1234 }; -const v2 = Value{ .Array = [_]u8{3} ** 9 }; - -const err = @as(anyerror!Agg, Agg{ - .val1 = v1, - .val2 = v2, -}); - -const array = [_]Value{ - v1, - v2, - v1, - v2, -}; - -test "unions embedded in aggregate types" { - switch (array[1]) { - Value.Array => |arr| try expect(arr[4] == 3), - else => unreachable, - } - switch ((err catch unreachable).val1) { - Value.Int => |x| try expect(x == 1234), - else => unreachable, - } -} - -const Foo = union { - float: f64, - int: i32, -}; - -test "basic unions" { - var foo = Foo{ .int = 1 }; - try expect(foo.int == 1); - foo = Foo{ .float = 12.34 }; - try expect(foo.float == 12.34); -} - -test "comptime union field access" { - comptime { - var foo = Foo{ .int = 0 }; - try expect(foo.int == 0); - - foo = Foo{ .float = 42.42 }; - try expect(foo.float == 42.42); - } -} - -test "init union with runtime value" { - var foo: Foo = undefined; - - setFloat(&foo, 12.34); - try expect(foo.float == 12.34); - - setInt(&foo, 42); - try expect(foo.int == 42); -} - -fn setFloat(foo: *Foo, x: f64) void { - foo.* = Foo{ .float = x }; -} - -fn setInt(foo: *Foo, x: i32) void { - foo.* = Foo{ .int = x }; -} - -const FooExtern = extern union { - float: f64, - int: i32, -}; - -test "basic extern unions" { - var foo = FooExtern{ .int = 1 }; - try expect(foo.int == 1); - foo.float = 12.34; - try expect(foo.float == 12.34); -} - -const Letter = enum { - A, - B, - C, -}; -const Payload = union(Letter) { - A: i32, - B: f64, - C: bool, -}; - -test "union with specified enum tag" { - try doTest(); - comptime try doTest(); -} - -fn doTest() error{TestUnexpectedResult}!void { - try expect((try bar(Payload{ .A = 1234 })) == -10); -} - -fn bar(value: Payload) error{TestUnexpectedResult}!i32 { - try expect(@as(Letter, value) == Letter.A); - return switch (value) { - Payload.A => |x| return x - 1244, - Payload.B => |x| if (x == 12.34) @as(i32, 20) else 21, - Payload.C => |x| if (x) @as(i32, 30) else 31, - }; -} - -const MultipleChoice = union(enum(u32)) { - A = 20, - B = 40, - C = 60, - D = 1000, -}; -test "simple union(enum(u32))" { - var x = MultipleChoice.C; - try expect(x == MultipleChoice.C); - try expect(@enumToInt(@as(Tag(MultipleChoice), x)) == 60); -} - -const MultipleChoice2 = union(enum(u32)) { - Unspecified1: i32, - A: f32 = 20, - Unspecified2: void, - B: bool = 40, - Unspecified3: i32, - C: i8 = 60, - Unspecified4: void, - D: void = 1000, - Unspecified5: i32, -}; - -test "union(enum(u32)) with specified and unspecified tag values" { - comptime try expect(Tag(Tag(MultipleChoice2)) == u32); - try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 }); - comptime try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 }); -} - -fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void { - try expect(@enumToInt(@as(Tag(MultipleChoice2), x)) == 60); - try expect(1123 == switch (x) { - MultipleChoice2.A => 1, - MultipleChoice2.B => 2, - MultipleChoice2.C => |v| @as(i32, 1000) + v, - MultipleChoice2.D => 4, - MultipleChoice2.Unspecified1 => 5, - MultipleChoice2.Unspecified2 => 6, - MultipleChoice2.Unspecified3 => 7, - MultipleChoice2.Unspecified4 => 8, - MultipleChoice2.Unspecified5 => 9, - }); -} - -const ExternPtrOrInt = extern union { - ptr: *u8, - int: u64, -}; -test "extern union size" { - comptime try expect(@sizeOf(ExternPtrOrInt) == 8); -} - -const PackedPtrOrInt = packed union { - ptr: *u8, - int: u64, -}; -test "extern union size" { - comptime try expect(@sizeOf(PackedPtrOrInt) == 8); -} - -const ZeroBits = union { - OnlyField: void, -}; -test "union with only 1 field which is void should be zero bits" { - comptime try expect(@sizeOf(ZeroBits) == 0); -} - -const TheTag = enum { - A, - B, - C, -}; -const TheUnion = union(TheTag) { - A: i32, - B: i32, - C: i32, -}; -test "union field access gives the enum values" { - try expect(TheUnion.A == TheTag.A); - try expect(TheUnion.B == TheTag.B); - try expect(TheUnion.C == TheTag.C); -} - -test "cast union to tag type of union" { - try testCastUnionToTag(TheUnion{ .B = 1234 }); - comptime try testCastUnionToTag(TheUnion{ .B = 1234 }); -} - -fn testCastUnionToTag(x: TheUnion) !void { - try expect(@as(TheTag, x) == TheTag.B); -} - -test "cast tag type of union to union" { - var x: Value2 = Letter2.B; - try expect(@as(Letter2, x) == Letter2.B); -} -const Letter2 = enum { - A, - B, - C, -}; -const Value2 = union(Letter2) { - A: i32, - B, - C, -}; - -test "implicit cast union to its tag type" { - var x: Value2 = Letter2.B; - try expect(x == Letter2.B); - try giveMeLetterB(x); -} -fn giveMeLetterB(x: Letter2) !void { - try expect(x == Value2.B); -} - -pub const PackThis = union(enum) { - Invalid: bool, - StringLiteral: u2, -}; - -test "constant packed union" { - try testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }}); -} - -fn testConstPackedUnion(expected_tokens: []const PackThis) !void { - try expect(expected_tokens[0].StringLiteral == 1); -} - -test "switch on union with only 1 field" { - var r: PartialInst = undefined; - r = PartialInst.Compiled; - switch (r) { - PartialInst.Compiled => { - var z: PartialInstWithPayload = undefined; - z = PartialInstWithPayload{ .Compiled = 1234 }; - switch (z) { - PartialInstWithPayload.Compiled => |x| { - try expect(x == 1234); - return; - }, - } - }, - } - unreachable; -} - -const PartialInst = union(enum) { - Compiled, -}; - -const PartialInstWithPayload = union(enum) { - Compiled: i32, -}; - -test "access a member of tagged union with conflicting enum tag name" { - const Bar = union(enum) { - A: A, - B: B, - - const A = u8; - const B = void; - }; - - comptime try expect(Bar.A == u8); -} - -test "tagged union initialization with runtime void" { - try expect(testTaggedUnionInit({})); -} - -const TaggedUnionWithAVoid = union(enum) { - A, - B: i32, -}; - -fn testTaggedUnionInit(x: anytype) bool { - const y = TaggedUnionWithAVoid{ .A = x }; - return @as(Tag(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A; -} - -pub const UnionEnumNoPayloads = union(enum) { - A, - B, -}; - -test "tagged union with no payloads" { - const a = UnionEnumNoPayloads{ .B = {} }; - switch (a) { - Tag(UnionEnumNoPayloads).A => @panic("wrong"), - Tag(UnionEnumNoPayloads).B => {}, - } -} - -test "union with only 1 field casted to its enum type" { - const Literal = union(enum) { - Number: f64, - Bool: bool, - }; - - const Expr = union(enum) { - Literal: Literal, - }; - - var e = Expr{ .Literal = Literal{ .Bool = true } }; - const ExprTag = Tag(Expr); - comptime try expect(Tag(ExprTag) == u0); - var t = @as(ExprTag, e); - try expect(t == Expr.Literal); -} - -test "union with only 1 field casted to its enum type which has enum value specified" { - const Literal = union(enum) { - Number: f64, - Bool: bool, - }; - - const ExprTag = enum(comptime_int) { - Literal = 33, - }; - - const Expr = union(ExprTag) { - Literal: Literal, - }; - - var e = Expr{ .Literal = Literal{ .Bool = true } }; - comptime try expect(Tag(ExprTag) == comptime_int); - var t = @as(ExprTag, e); - try expect(t == Expr.Literal); - try expect(@enumToInt(t) == 33); - comptime try expect(@enumToInt(t) == 33); -} - -test "@enumToInt works on unions" { - const Bar = union(enum) { - A: bool, - B: u8, - C, - }; - - const a = Bar{ .A = true }; - var b = Bar{ .B = undefined }; - var c = Bar.C; - try expect(@enumToInt(a) == 0); - try expect(@enumToInt(b) == 1); - try expect(@enumToInt(c) == 2); -} - -const Attribute = union(enum) { - A: bool, - B: u8, -}; - -fn setAttribute(attr: Attribute) void { - _ = attr; -} - -fn Setter(attr: Attribute) type { - return struct { - fn set() void { - setAttribute(attr); - } - }; -} - -test "comptime union field value equality" { - const a0 = Setter(Attribute{ .A = false }); - const a1 = Setter(Attribute{ .A = true }); - const a2 = Setter(Attribute{ .A = false }); - - const b0 = Setter(Attribute{ .B = 5 }); - const b1 = Setter(Attribute{ .B = 9 }); - const b2 = Setter(Attribute{ .B = 5 }); - - try expect(a0 == a0); - try expect(a1 == a1); - try expect(a0 == a2); - - try expect(b0 == b0); - try expect(b1 == b1); - try expect(b0 == b2); - - try expect(a0 != b0); - try expect(a0 != a1); - try expect(b0 != b1); -} - -test "return union init with void payload" { - const S = struct { - fn entry() !void { - try expect(func().state == State.one); - } - const Outer = union(enum) { - state: State, - }; - const State = union(enum) { - one: void, - two: u32, - }; - fn func() Outer { - return Outer{ .state = State{ .one = {} } }; - } - }; - try S.entry(); - comptime try S.entry(); -} - -test "@unionInit can modify a union type" { - const UnionInitEnum = union(enum) { - Boolean: bool, - Byte: u8, - }; - - var value: UnionInitEnum = undefined; - - value = @unionInit(UnionInitEnum, "Boolean", true); - try expect(value.Boolean == true); - value.Boolean = false; - try expect(value.Boolean == false); - - value = @unionInit(UnionInitEnum, "Byte", 2); - try expect(value.Byte == 2); - value.Byte = 3; - try expect(value.Byte == 3); -} - -test "@unionInit can modify a pointer value" { - const UnionInitEnum = union(enum) { - Boolean: bool, - Byte: u8, - }; - - var value: UnionInitEnum = undefined; - var value_ptr = &value; - - value_ptr.* = @unionInit(UnionInitEnum, "Boolean", true); - try expect(value.Boolean == true); - - value_ptr.* = @unionInit(UnionInitEnum, "Byte", 2); - try expect(value.Byte == 2); -} - -test "union no tag with struct member" { - const Struct = struct {}; - const Union = union { - s: Struct, - pub fn foo(self: *@This()) void { - _ = self; - } - }; - var u = Union{ .s = Struct{} }; - u.foo(); -} - -fn testComparison() !void { - var x = Payload{ .A = 42 }; - try expect(x == .A); - try expect(x != .B); - try expect(x != .C); - try expect((x == .B) == false); - try expect((x == .C) == false); - try expect((x != .A) == false); -} - -test "comparison between union and enum literal" { - try testComparison(); - comptime try testComparison(); -} - -test "packed union generates correctly aligned LLVM type" { - const U = packed union { - f1: fn () error{TestUnexpectedResult}!void, - f2: u32, - }; - var foo = [_]U{ - U{ .f1 = doTest }, - U{ .f2 = 0 }, - }; - try foo[0].f1(); -} - -test "union with one member defaults to u0 tag type" { - const U0 = union(enum) { - X: u32, - }; - comptime try expect(Tag(Tag(U0)) == u0); -} - -test "union with comptime_int tag" { - const Union = union(enum(comptime_int)) { - X: u32, - Y: u16, - Z: u8, - }; - comptime try expect(Tag(Tag(Union)) == comptime_int); -} - -test "extern union doesn't trigger field check at comptime" { - const U = extern union { - x: u32, - y: u8, - }; - - const x = U{ .x = 0x55AAAA55 }; - comptime try expect(x.y == 0x55); -} - -const Foo1 = union(enum) { - f: struct { - x: usize, - }, -}; -var glbl: Foo1 = undefined; - -test "global union with single field is correctly initialized" { - glbl = Foo1{ - .f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 }, - }; - try expect(glbl.f.x == 123); -} - -pub const FooUnion = union(enum) { - U0: usize, - U1: u8, -}; - -var glbl_array: [2]FooUnion = undefined; - -test "initialize global array of union" { - glbl_array[1] = FooUnion{ .U1 = 2 }; - glbl_array[0] = FooUnion{ .U0 = 1 }; - try expect(glbl_array[0].U0 == 1); - try expect(glbl_array[1].U1 == 2); -} - -test "anonymous union literal syntax" { - const S = struct { - const Number = union { - int: i32, - float: f64, - }; - - fn doTheTest() !void { - var i: Number = .{ .int = 42 }; - var f = makeNumber(); - try expect(i.int == 42); - try expect(f.float == 12.34); - } - - fn makeNumber() Number { - return .{ .float = 12.34 }; - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "update the tag value for zero-sized unions" { - const S = union(enum) { - U0: void, - U1: void, - }; - var x = S{ .U0 = {} }; - try expect(x == .U0); - x = S{ .U1 = {} }; - try expect(x == .U1); -} - -test "function call result coerces from tagged union to the tag" { - const S = struct { - const Arch = union(enum) { - One, - Two: usize, - }; - - const ArchTag = Tag(Arch); - - fn doTheTest() !void { - var x: ArchTag = getArch1(); - try expect(x == .One); - - var y: ArchTag = getArch2(); - try expect(y == .Two); - } - - pub fn getArch1() Arch { - return .One; - } - - pub fn getArch2() Arch { - return .{ .Two = 99 }; - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "0-sized extern union definition" { - const U = extern union { - a: void, - const f = 1; - }; - - try expect(U.f == 1); -} - -test "union initializer generates padding only if needed" { - const U = union(enum) { - A: u24, - }; - - var v = U{ .A = 532 }; - try expect(v.A == 532); -} - -test "runtime tag name with single field" { - const U = union(enum) { - A: i32, - }; - - var v = U{ .A = 42 }; - try expect(std.mem.eql(u8, @tagName(v), "A")); -} - -test "cast from anonymous struct to union" { - const S = struct { - const U = union(enum) { - A: u32, - B: []const u8, - C: void, - }; - fn doTheTest() !void { - var y: u32 = 42; - const t0 = .{ .A = 123 }; - const t1 = .{ .B = "foo" }; - const t2 = .{ .C = {} }; - const t3 = .{ .A = y }; - const x0: U = t0; - var x1: U = t1; - const x2: U = t2; - var x3: U = t3; - try expect(x0.A == 123); - try expect(std.mem.eql(u8, x1.B, "foo")); - try expect(x2 == .C); - try expect(x3.A == y); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "cast from pointer to anonymous struct to pointer to union" { - const S = struct { - const U = union(enum) { - A: u32, - B: []const u8, - C: void, - }; - fn doTheTest() !void { - var y: u32 = 42; - const t0 = &.{ .A = 123 }; - const t1 = &.{ .B = "foo" }; - const t2 = &.{ .C = {} }; - const t3 = &.{ .A = y }; - const x0: *const U = t0; - var x1: *const U = t1; - const x2: *const U = t2; - var x3: *const U = t3; - try expect(x0.A == 123); - try expect(std.mem.eql(u8, x1.B, "foo")); - try expect(x2.* == .C); - try expect(x3.A == y); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "method call on an empty union" { - const S = struct { - const MyUnion = union(MyUnionTag) { - pub const MyUnionTag = enum { X1, X2 }; - X1: [0]u8, - X2: [0]u8, - - pub fn useIt(self: *@This()) bool { - _ = self; - return true; - } - }; - - fn doTheTest() !void { - var u = MyUnion{ .X1 = [0]u8{} }; - try expect(u.useIt()); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "switching on non exhaustive union" { - const S = struct { - const E = enum(u8) { - a, - b, - _, - }; - const U = union(E) { - a: i32, - b: u32, - }; - fn doTheTest() !void { - var a = U{ .a = 2 }; - switch (a) { - .a => |val| try expect(val == 2), - .b => unreachable, - } - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "containers with single-field enums" { - const S = struct { - const A = union(enum) { f1 }; - const B = union(enum) { f1: void }; - const C = struct { a: A }; - const D = struct { a: B }; - - fn doTheTest() !void { - var array1 = [1]A{A{ .f1 = {} }}; - var array2 = [1]B{B{ .f1 = {} }}; - try expect(array1[0] == .f1); - try expect(array2[0] == .f1); - - var struct1 = C{ .a = A{ .f1 = {} } }; - var struct2 = D{ .a = B{ .f1 = {} } }; - try expect(struct1.a == .f1); - try expect(struct2.a == .f1); - } - }; - - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "@unionInit on union w/ tag but no fields" { - const S = struct { - const Type = enum(u8) { no_op = 105 }; - - const Data = union(Type) { - no_op: void, - - pub fn decode(buf: []const u8) Data { - _ = buf; - return @unionInit(Data, "no_op", {}); - } - }; - - comptime { - std.debug.assert(@sizeOf(Data) != 0); - } - - fn doTheTest() !void { - var data: Data = .{ .no_op = .{} }; - _ = data; - var o = Data.decode(&[_]u8{}); - try expectEqual(Type.no_op, o); - } - }; - - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "union enum type gets a separate scope" { - const S = struct { - const U = union(enum) { - a: u8, - const foo = 1; - }; - - fn doTheTest() !void { - try expect(!@hasDecl(Tag(U), "foo")); - } - }; - - try S.doTheTest(); -} -test "anytype union field: issue #9233" { - const Baz = union(enum) { bar: anytype }; - _ = Baz; -} diff --git a/test/behavior/union_stage1.zig b/test/behavior/union_stage1.zig new file mode 100644 index 0000000000..086bd981cd --- /dev/null +++ b/test/behavior/union_stage1.zig @@ -0,0 +1,799 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const Tag = std.meta.Tag; + +const Value = union(enum) { + Int: u64, + Array: [9]u8, +}; + +const Agg = struct { + val1: Value, + val2: Value, +}; + +const v1 = Value{ .Int = 1234 }; +const v2 = Value{ .Array = [_]u8{3} ** 9 }; + +const err = @as(anyerror!Agg, Agg{ + .val1 = v1, + .val2 = v2, +}); + +const array = [_]Value{ v1, v2, v1, v2 }; + +test "unions embedded in aggregate types" { + switch (array[1]) { + Value.Array => |arr| try expect(arr[4] == 3), + else => unreachable, + } + switch ((err catch unreachable).val1) { + Value.Int => |x| try expect(x == 1234), + else => unreachable, + } +} + +const Foo = union { + float: f64, + int: i32, +}; + +test "basic unions" { + var foo = Foo{ .int = 1 }; + try expect(foo.int == 1); + foo = Foo{ .float = 12.34 }; + try expect(foo.float == 12.34); +} + +test "comptime union field access" { + comptime { + var foo = Foo{ .int = 0 }; + try expect(foo.int == 0); + + foo = Foo{ .float = 42.42 }; + try expect(foo.float == 42.42); + } +} + +test "init union with runtime value" { + var foo: Foo = undefined; + + setFloat(&foo, 12.34); + try expect(foo.float == 12.34); + + setInt(&foo, 42); + try expect(foo.int == 42); +} + +fn setFloat(foo: *Foo, x: f64) void { + foo.* = Foo{ .float = x }; +} + +fn setInt(foo: *Foo, x: i32) void { + foo.* = Foo{ .int = x }; +} + +const FooExtern = extern union { + float: f64, + int: i32, +}; + +test "basic extern unions" { + var foo = FooExtern{ .int = 1 }; + try expect(foo.int == 1); + foo.float = 12.34; + try expect(foo.float == 12.34); +} + +const Letter = enum { A, B, C }; +const Payload = union(Letter) { + A: i32, + B: f64, + C: bool, +}; + +test "union with specified enum tag" { + try doTest(); + comptime try doTest(); +} + +fn doTest() error{TestUnexpectedResult}!void { + try expect((try bar(Payload{ .A = 1234 })) == -10); +} + +fn bar(value: Payload) error{TestUnexpectedResult}!i32 { + try expect(@as(Letter, value) == Letter.A); + return switch (value) { + Payload.A => |x| return x - 1244, + Payload.B => |x| if (x == 12.34) @as(i32, 20) else 21, + Payload.C => |x| if (x) @as(i32, 30) else 31, + }; +} + +const MultipleChoice = union(enum(u32)) { + A = 20, + B = 40, + C = 60, + D = 1000, +}; +test "simple union(enum(u32))" { + var x = MultipleChoice.C; + try expect(x == MultipleChoice.C); + try expect(@enumToInt(@as(Tag(MultipleChoice), x)) == 60); +} + +const MultipleChoice2 = union(enum(u32)) { + Unspecified1: i32, + A: f32 = 20, + Unspecified2: void, + B: bool = 40, + Unspecified3: i32, + C: i8 = 60, + Unspecified4: void, + D: void = 1000, + Unspecified5: i32, +}; + +test "union(enum(u32)) with specified and unspecified tag values" { + comptime try expect(Tag(Tag(MultipleChoice2)) == u32); + try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 }); + comptime try testEnumWithSpecifiedAndUnspecifiedTagValues(MultipleChoice2{ .C = 123 }); +} + +fn testEnumWithSpecifiedAndUnspecifiedTagValues(x: MultipleChoice2) !void { + try expect(@enumToInt(@as(Tag(MultipleChoice2), x)) == 60); + try expect(1123 == switch (x) { + MultipleChoice2.A => 1, + MultipleChoice2.B => 2, + MultipleChoice2.C => |v| @as(i32, 1000) + v, + MultipleChoice2.D => 4, + MultipleChoice2.Unspecified1 => 5, + MultipleChoice2.Unspecified2 => 6, + MultipleChoice2.Unspecified3 => 7, + MultipleChoice2.Unspecified4 => 8, + MultipleChoice2.Unspecified5 => 9, + }); +} + +const ExternPtrOrInt = extern union { + ptr: *u8, + int: u64, +}; +test "extern union size" { + comptime try expect(@sizeOf(ExternPtrOrInt) == 8); +} + +const PackedPtrOrInt = packed union { + ptr: *u8, + int: u64, +}; +test "extern union size" { + comptime try expect(@sizeOf(PackedPtrOrInt) == 8); +} + +const ZeroBits = union { + OnlyField: void, +}; +test "union with only 1 field which is void should be zero bits" { + comptime try expect(@sizeOf(ZeroBits) == 0); +} + +const TheTag = enum { A, B, C }; +const TheUnion = union(TheTag) { + A: i32, + B: i32, + C: i32, +}; +test "union field access gives the enum values" { + try expect(TheUnion.A == TheTag.A); + try expect(TheUnion.B == TheTag.B); + try expect(TheUnion.C == TheTag.C); +} + +test "cast union to tag type of union" { + try testCastUnionToTag(TheUnion{ .B = 1234 }); + comptime try testCastUnionToTag(TheUnion{ .B = 1234 }); +} + +fn testCastUnionToTag(x: TheUnion) !void { + try expect(@as(TheTag, x) == TheTag.B); +} + +test "cast tag type of union to union" { + var x: Value2 = Letter2.B; + try expect(@as(Letter2, x) == Letter2.B); +} +const Letter2 = enum { A, B, C }; +const Value2 = union(Letter2) { + A: i32, + B, + C, +}; + +test "implicit cast union to its tag type" { + var x: Value2 = Letter2.B; + try expect(x == Letter2.B); + try giveMeLetterB(x); +} +fn giveMeLetterB(x: Letter2) !void { + try expect(x == Value2.B); +} + +// TODO it looks like this test intended to test packed unions, but this is not a packed +// union. go through git history and find out what happened. +pub const PackThis = union(enum) { + Invalid: bool, + StringLiteral: u2, +}; + +test "constant packed union" { + try testConstPackedUnion(&[_]PackThis{PackThis{ .StringLiteral = 1 }}); +} + +fn testConstPackedUnion(expected_tokens: []const PackThis) !void { + try expect(expected_tokens[0].StringLiteral == 1); +} + +test "switch on union with only 1 field" { + var r: PartialInst = undefined; + r = PartialInst.Compiled; + switch (r) { + PartialInst.Compiled => { + var z: PartialInstWithPayload = undefined; + z = PartialInstWithPayload{ .Compiled = 1234 }; + switch (z) { + PartialInstWithPayload.Compiled => |x| { + try expect(x == 1234); + return; + }, + } + }, + } + unreachable; +} + +const PartialInst = union(enum) { + Compiled, +}; + +const PartialInstWithPayload = union(enum) { + Compiled: i32, +}; + +test "access a member of tagged union with conflicting enum tag name" { + const Bar = union(enum) { + A: A, + B: B, + + const A = u8; + const B = void; + }; + + comptime try expect(Bar.A == u8); +} + +test "tagged union initialization with runtime void" { + try expect(testTaggedUnionInit({})); +} + +const TaggedUnionWithAVoid = union(enum) { + A, + B: i32, +}; + +fn testTaggedUnionInit(x: anytype) bool { + const y = TaggedUnionWithAVoid{ .A = x }; + return @as(Tag(TaggedUnionWithAVoid), y) == TaggedUnionWithAVoid.A; +} + +pub const UnionEnumNoPayloads = union(enum) { A, B }; + +test "tagged union with no payloads" { + const a = UnionEnumNoPayloads{ .B = {} }; + switch (a) { + Tag(UnionEnumNoPayloads).A => @panic("wrong"), + Tag(UnionEnumNoPayloads).B => {}, + } +} + +test "union with only 1 field casted to its enum type" { + const Literal = union(enum) { + Number: f64, + Bool: bool, + }; + + const Expr = union(enum) { + Literal: Literal, + }; + + var e = Expr{ .Literal = Literal{ .Bool = true } }; + const ExprTag = Tag(Expr); + comptime try expect(Tag(ExprTag) == u0); + var t = @as(ExprTag, e); + try expect(t == Expr.Literal); +} + +test "union with only 1 field casted to its enum type which has enum value specified" { + const Literal = union(enum) { + Number: f64, + Bool: bool, + }; + + const ExprTag = enum(comptime_int) { + Literal = 33, + }; + + const Expr = union(ExprTag) { + Literal: Literal, + }; + + var e = Expr{ .Literal = Literal{ .Bool = true } }; + comptime try expect(Tag(ExprTag) == comptime_int); + var t = @as(ExprTag, e); + try expect(t == Expr.Literal); + try expect(@enumToInt(t) == 33); + comptime try expect(@enumToInt(t) == 33); +} + +test "@enumToInt works on unions" { + const Bar = union(enum) { + A: bool, + B: u8, + C, + }; + + const a = Bar{ .A = true }; + var b = Bar{ .B = undefined }; + var c = Bar.C; + try expect(@enumToInt(a) == 0); + try expect(@enumToInt(b) == 1); + try expect(@enumToInt(c) == 2); +} + +const Attribute = union(enum) { + A: bool, + B: u8, +}; + +fn setAttribute(attr: Attribute) void { + _ = attr; +} + +fn Setter(attr: Attribute) type { + return struct { + fn set() void { + setAttribute(attr); + } + }; +} + +test "comptime union field value equality" { + const a0 = Setter(Attribute{ .A = false }); + const a1 = Setter(Attribute{ .A = true }); + const a2 = Setter(Attribute{ .A = false }); + + const b0 = Setter(Attribute{ .B = 5 }); + const b1 = Setter(Attribute{ .B = 9 }); + const b2 = Setter(Attribute{ .B = 5 }); + + try expect(a0 == a0); + try expect(a1 == a1); + try expect(a0 == a2); + + try expect(b0 == b0); + try expect(b1 == b1); + try expect(b0 == b2); + + try expect(a0 != b0); + try expect(a0 != a1); + try expect(b0 != b1); +} + +test "return union init with void payload" { + const S = struct { + fn entry() !void { + try expect(func().state == State.one); + } + const Outer = union(enum) { + state: State, + }; + const State = union(enum) { + one: void, + two: u32, + }; + fn func() Outer { + return Outer{ .state = State{ .one = {} } }; + } + }; + try S.entry(); + comptime try S.entry(); +} + +test "@unionInit can modify a union type" { + const UnionInitEnum = union(enum) { + Boolean: bool, + Byte: u8, + }; + + var value: UnionInitEnum = undefined; + + value = @unionInit(UnionInitEnum, "Boolean", true); + try expect(value.Boolean == true); + value.Boolean = false; + try expect(value.Boolean == false); + + value = @unionInit(UnionInitEnum, "Byte", 2); + try expect(value.Byte == 2); + value.Byte = 3; + try expect(value.Byte == 3); +} + +test "@unionInit can modify a pointer value" { + const UnionInitEnum = union(enum) { + Boolean: bool, + Byte: u8, + }; + + var value: UnionInitEnum = undefined; + var value_ptr = &value; + + value_ptr.* = @unionInit(UnionInitEnum, "Boolean", true); + try expect(value.Boolean == true); + + value_ptr.* = @unionInit(UnionInitEnum, "Byte", 2); + try expect(value.Byte == 2); +} + +test "union no tag with struct member" { + const Struct = struct {}; + const Union = union { + s: Struct, + pub fn foo(self: *@This()) void { + _ = self; + } + }; + var u = Union{ .s = Struct{} }; + u.foo(); +} + +fn testComparison() !void { + var x = Payload{ .A = 42 }; + try expect(x == .A); + try expect(x != .B); + try expect(x != .C); + try expect((x == .B) == false); + try expect((x == .C) == false); + try expect((x != .A) == false); +} + +test "comparison between union and enum literal" { + try testComparison(); + comptime try testComparison(); +} + +test "packed union generates correctly aligned LLVM type" { + const U = packed union { + f1: fn () error{TestUnexpectedResult}!void, + f2: u32, + }; + var foo = [_]U{ + U{ .f1 = doTest }, + U{ .f2 = 0 }, + }; + try foo[0].f1(); +} + +test "union with one member defaults to u0 tag type" { + const U0 = union(enum) { + X: u32, + }; + comptime try expect(Tag(Tag(U0)) == u0); +} + +test "union with comptime_int tag" { + const Union = union(enum(comptime_int)) { + X: u32, + Y: u16, + Z: u8, + }; + comptime try expect(Tag(Tag(Union)) == comptime_int); +} + +test "extern union doesn't trigger field check at comptime" { + const U = extern union { + x: u32, + y: u8, + }; + + const x = U{ .x = 0x55AAAA55 }; + comptime try expect(x.y == 0x55); +} + +const Foo1 = union(enum) { + f: struct { + x: usize, + }, +}; +var glbl: Foo1 = undefined; + +test "global union with single field is correctly initialized" { + glbl = Foo1{ + .f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 }, + }; + try expect(glbl.f.x == 123); +} + +pub const FooUnion = union(enum) { + U0: usize, + U1: u8, +}; + +var glbl_array: [2]FooUnion = undefined; + +test "initialize global array of union" { + glbl_array[1] = FooUnion{ .U1 = 2 }; + glbl_array[0] = FooUnion{ .U0 = 1 }; + try expect(glbl_array[0].U0 == 1); + try expect(glbl_array[1].U1 == 2); +} + +test "anonymous union literal syntax" { + const S = struct { + const Number = union { + int: i32, + float: f64, + }; + + fn doTheTest() !void { + var i: Number = .{ .int = 42 }; + var f = makeNumber(); + try expect(i.int == 42); + try expect(f.float == 12.34); + } + + fn makeNumber() Number { + return .{ .float = 12.34 }; + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "update the tag value for zero-sized unions" { + const S = union(enum) { + U0: void, + U1: void, + }; + var x = S{ .U0 = {} }; + try expect(x == .U0); + x = S{ .U1 = {} }; + try expect(x == .U1); +} + +test "function call result coerces from tagged union to the tag" { + const S = struct { + const Arch = union(enum) { + One, + Two: usize, + }; + + const ArchTag = Tag(Arch); + + fn doTheTest() !void { + var x: ArchTag = getArch1(); + try expect(x == .One); + + var y: ArchTag = getArch2(); + try expect(y == .Two); + } + + pub fn getArch1() Arch { + return .One; + } + + pub fn getArch2() Arch { + return .{ .Two = 99 }; + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "0-sized extern union definition" { + const U = extern union { + a: void, + const f = 1; + }; + + try expect(U.f == 1); +} + +test "union initializer generates padding only if needed" { + const U = union(enum) { + A: u24, + }; + + var v = U{ .A = 532 }; + try expect(v.A == 532); +} + +test "runtime tag name with single field" { + const U = union(enum) { + A: i32, + }; + + var v = U{ .A = 42 }; + try expect(std.mem.eql(u8, @tagName(v), "A")); +} + +test "cast from anonymous struct to union" { + const S = struct { + const U = union(enum) { + A: u32, + B: []const u8, + C: void, + }; + fn doTheTest() !void { + var y: u32 = 42; + const t0 = .{ .A = 123 }; + const t1 = .{ .B = "foo" }; + const t2 = .{ .C = {} }; + const t3 = .{ .A = y }; + const x0: U = t0; + var x1: U = t1; + const x2: U = t2; + var x3: U = t3; + try expect(x0.A == 123); + try expect(std.mem.eql(u8, x1.B, "foo")); + try expect(x2 == .C); + try expect(x3.A == y); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "cast from pointer to anonymous struct to pointer to union" { + const S = struct { + const U = union(enum) { + A: u32, + B: []const u8, + C: void, + }; + fn doTheTest() !void { + var y: u32 = 42; + const t0 = &.{ .A = 123 }; + const t1 = &.{ .B = "foo" }; + const t2 = &.{ .C = {} }; + const t3 = &.{ .A = y }; + const x0: *const U = t0; + var x1: *const U = t1; + const x2: *const U = t2; + var x3: *const U = t3; + try expect(x0.A == 123); + try expect(std.mem.eql(u8, x1.B, "foo")); + try expect(x2.* == .C); + try expect(x3.A == y); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "method call on an empty union" { + const S = struct { + const MyUnion = union(MyUnionTag) { + pub const MyUnionTag = enum { X1, X2 }; + X1: [0]u8, + X2: [0]u8, + + pub fn useIt(self: *@This()) bool { + _ = self; + return true; + } + }; + + fn doTheTest() !void { + var u = MyUnion{ .X1 = [0]u8{} }; + try expect(u.useIt()); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "switching on non exhaustive union" { + const S = struct { + const E = enum(u8) { + a, + b, + _, + }; + const U = union(E) { + a: i32, + b: u32, + }; + fn doTheTest() !void { + var a = U{ .a = 2 }; + switch (a) { + .a => |val| try expect(val == 2), + .b => unreachable, + } + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "containers with single-field enums" { + const S = struct { + const A = union(enum) { f1 }; + const B = union(enum) { f1: void }; + const C = struct { a: A }; + const D = struct { a: B }; + + fn doTheTest() !void { + var array1 = [1]A{A{ .f1 = {} }}; + var array2 = [1]B{B{ .f1 = {} }}; + try expect(array1[0] == .f1); + try expect(array2[0] == .f1); + + var struct1 = C{ .a = A{ .f1 = {} } }; + var struct2 = D{ .a = B{ .f1 = {} } }; + try expect(struct1.a == .f1); + try expect(struct2.a == .f1); + } + }; + + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "@unionInit on union w/ tag but no fields" { + const S = struct { + const Type = enum(u8) { no_op = 105 }; + + const Data = union(Type) { + no_op: void, + + pub fn decode(buf: []const u8) Data { + _ = buf; + return @unionInit(Data, "no_op", {}); + } + }; + + comptime { + std.debug.assert(@sizeOf(Data) != 0); + } + + fn doTheTest() !void { + var data: Data = .{ .no_op = .{} }; + _ = data; + var o = Data.decode(&[_]u8{}); + try expectEqual(Type.no_op, o); + } + }; + + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "union enum type gets a separate scope" { + const S = struct { + const U = union(enum) { + a: u8, + const foo = 1; + }; + + fn doTheTest() !void { + try expect(!@hasDecl(Tag(U), "foo")); + } + }; + + try S.doTheTest(); +} +test "anytype union field: issue #9233" { + const Baz = union(enum) { bar: anytype }; + _ = Baz; +} diff --git a/test/behavior/widening.zig b/test/behavior/widening.zig index efcbab9883..9c1694b368 100644 --- a/test/behavior/widening.zig +++ b/test/behavior/widening.zig @@ -17,3 +17,46 @@ test "implicit unsigned integer to signed integer" { var b: i16 = a; try expect(b == 250); } + +test "float widening" { + if (@import("builtin").zig_is_stage2) { + // This test is passing but it depends on compiler-rt symbols, which + // cannot yet be built with stage2 due to + // "TODO implement equality comparison between a union's tag value and an enum literal" + return error.SkipZigTest; + } + var a: f16 = 12.34; + var b: f32 = a; + var c: f64 = b; + var d: f128 = c; + try expect(a == b); + try expect(b == c); + try expect(c == d); +} + +test "float widening f16 to f128" { + if (@import("builtin").zig_is_stage2) { + // This test is passing but it depends on compiler-rt symbols, which + // cannot yet be built with stage2 due to + // "TODO implement equality comparison between a union's tag value and an enum literal" + return error.SkipZigTest; + } + // TODO https://github.com/ziglang/zig/issues/3282 + if (@import("builtin").stage2_arch == .aarch64) return error.SkipZigTest; + if (@import("builtin").stage2_arch == .powerpc64le) return error.SkipZigTest; + + var x: f16 = 12.34; + var y: f128 = x; + try expect(x == y); +} + +test "cast small unsigned to larger signed" { + try expect(castSmallUnsignedToLargerSigned1(200) == @as(i16, 200)); + try expect(castSmallUnsignedToLargerSigned2(9999) == @as(i64, 9999)); +} +fn castSmallUnsignedToLargerSigned1(x: u8) i16 { + return x; +} +fn castSmallUnsignedToLargerSigned2(x: u16) i64 { + return x; +} diff --git a/test/behavior/widening_stage1.zig b/test/behavior/widening_stage1.zig deleted file mode 100644 index 0cec3988cb..0000000000 --- a/test/behavior/widening_stage1.zig +++ /dev/null @@ -1,34 +0,0 @@ -const std = @import("std"); -const expect = std.testing.expect; -const mem = std.mem; - -test "float widening" { - var a: f16 = 12.34; - var b: f32 = a; - var c: f64 = b; - var d: f128 = c; - try expect(a == b); - try expect(b == c); - try expect(c == d); -} - -test "float widening f16 to f128" { - // TODO https://github.com/ziglang/zig/issues/3282 - if (@import("builtin").stage2_arch == .aarch64) return error.SkipZigTest; - if (@import("builtin").stage2_arch == .powerpc64le) return error.SkipZigTest; - - var x: f16 = 12.34; - var y: f128 = x; - try expect(x == y); -} - -test "cast small unsigned to larger signed" { - try expect(castSmallUnsignedToLargerSigned1(200) == @as(i16, 200)); - try expect(castSmallUnsignedToLargerSigned2(9999) == @as(i64, 9999)); -} -fn castSmallUnsignedToLargerSigned1(x: u8) i16 { - return x; -} -fn castSmallUnsignedToLargerSigned2(x: u16) i64 { - return x; -} From 4afe4bdfe7dda638b35a9d388aac9a53d18cc4ba Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Wed, 22 Sep 2021 03:27:56 +0200 Subject: [PATCH 092/160] big ints: 2s complement signed xor --- lib/std/math/big/int.zig | 80 +++++++++++++++++++++++++++++------ lib/std/math/big/int_test.zig | 67 +++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 13 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index d223d3135f..e1b9c94fff 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -584,19 +584,29 @@ pub const Mutable = struct { r.positive = a.positive and b.positive; } - /// r = a ^ b + /// r = a ^ b under 2s complement semantics. /// r may alias with a or b. /// - /// Asserts that r has enough limbs to store the result. Upper bound is `math.max(a.limbs.len, b.limbs.len)`. + /// Asserts that r has enough limbs to store the result. If a and b share the same signedness, the + /// upper bound is `math.max(a.limbs.len, b.limbs.len)`. Otherwise, if either a or b is negative + /// but not both, the upper bound is `math.max(a.limbs.len, b.limbs.len) + 1`. pub fn bitXor(r: *Mutable, a: Const, b: Const) void { - if (a.limbs.len > b.limbs.len) { - llxor(r.limbs[0..], a.limbs[0..a.limbs.len], b.limbs[0..b.limbs.len]); - r.normalize(a.limbs.len); - } else { - llxor(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]); - r.normalize(b.limbs.len); + // Trivial cases, because llsignedxor does not support negative zero. + if (a.eqZero()) { + r.copy(b); + return; + } else if (b.eqZero()) { + r.copy(a); + return; + } + + if (a.limbs.len > b.limbs.len) { + r.positive = llsignedxor(r.limbs, a.limbs, a.positive, b.limbs, b.positive); + r.normalize(a.limbs.len + @boolToInt(a.positive != b.positive)); + } else { + r.positive = llsignedxor(r.limbs, b.limbs, b.positive, a.limbs, a.positive); + r.normalize(b.limbs.len + @boolToInt(a.positive != b.positive)); } - r.positive = a.positive or b.positive; } /// rma may alias x or y. @@ -1845,7 +1855,9 @@ pub const Managed = struct { /// r = a ^ b pub fn bitXor(r: *Managed, a: Managed, b: Managed) !void { - try r.ensureCapacity(math.max(a.len(), b.len())); + var cap = math.max(a.len(), b.len()) + @boolToInt(a.isPositive() != b.isPositive()); + try r.ensureCapacity(cap); + var m = r.toMutable(); m.bitXor(a.toConst(), b.toConst()); r.setMetadata(m.positive, m.len); @@ -2249,17 +2261,59 @@ fn lland(r: []Limb, a: []const Limb, b: []const Limb) void { } } -fn llxor(r: []Limb, a: []const Limb, b: []const Limb) void { +// r = a ^ b with 2s complement semantics. +// r may alias. +// a and b must not be -0. +// Returns `true` when the result is positive. +fn llsignedxor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool { + @setRuntimeSafety(debug_safety); + assert(a.len != 0 and b.len != 0); assert(r.len >= a.len); assert(a.len >= b.len); + // If a and b are positive, the result is positive and r = a ^ b. + // If a negative, b positive, result is negative and we have + // r = --(--a ^ b) + // = --(~(-a - 1) ^ b) + // = -(~(~(-a - 1) ^ b) + 1) + // = -(((-a - 1) ^ b) + 1) + // Same if a is positive and b is negative, sides switched. + // If both a and b are negative, the result is positive and we have + // r = (--a) ^ (--b) + // = ~(-a - 1) ^ ~(-b - 1) + // = (-a - 1) ^ (-b - 1) + // These operations can be made more generic as follows: + // - If a is negative, subtract 1 from |a| before the xor. + // - If b is negative, subtract 1 from |b| before the xor. + // - if the result is supposed to be negative, add 1. + var i: usize = 0; + var a_borrow = @boolToInt(!a_positive); + var b_borrow = @boolToInt(!b_positive); + var r_carry = @boolToInt(a_positive != b_positive); + while (i < b.len) : (i += 1) { - r[i] = a[i] ^ b[i]; + var a_limb: Limb = undefined; + a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb)); + + var b_limb: Limb = undefined; + b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &b_limb)); + + r[i] = a_limb ^ b_limb; + r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); } + while (i < a.len) : (i += 1) { - r[i] = a[i]; + a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &r[i])); + r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); } + + r[i] = r_carry; + + assert(a_borrow == 0); + assert(b_borrow == 0); + + return a_positive == b_positive; } /// r MUST NOT alias x. diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index 757f994f7e..993101c674 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -5,6 +5,7 @@ const Managed = std.math.big.int.Managed; const Mutable = std.math.big.int.Mutable; const Limb = std.math.big.Limb; const DoubleLimb = std.math.big.DoubleLimb; +const SignedDoubleLimb = std.math.big.SignedDoubleLimb; const maxInt = std.math.maxInt; const minInt = std.math.minInt; @@ -1386,6 +1387,72 @@ test "big.int bitwise xor multi-limb" { try testing.expect((try a.to(DoubleLimb)) == (maxInt(Limb) + 1) ^ maxInt(Limb)); } +test "big.int bitwise xor single negative simple" { + var a = try Managed.initSet(testing.allocator, 0x6b03e381328a3154); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -0x45fd3acef9191fad); + defer b.deinit(); + + try a.bitXor(a, b); + + try testing.expect((try a.to(i64)) == -0x2efed94fcb932ef9); +} + +test "big.int bitwise xor single negative zero" { + var a = try Managed.initSet(testing.allocator, 0); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -0); + defer b.deinit(); + + try a.bitXor(a, b); + + try testing.expect(a.eqZero()); +} + +test "big.int bitwise xor single negative multi-limb" { + var a = try Managed.initSet(testing.allocator, -0x9849c6e7a10d66d0e4260d4846254c32); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, 0xf2194e7d1c855272a997fcde16f6d5a8); + defer b.deinit(); + + try a.bitXor(a, b); + + try testing.expect((try a.to(i128)) == -0x6a50889abd8834a24db1f19650d3999a); +} + +test "big.int bitwise xor single negative overflow" { + var a = try Managed.initSet(testing.allocator, maxInt(Limb)); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -1); + defer b.deinit(); + + try a.bitXor(a, b); + + try testing.expect((try a.to(SignedDoubleLimb)) == -(maxInt(Limb) + 1)); +} + +test "big.int bitwise xor double negative simple" { + var a = try Managed.initSet(testing.allocator, -0x8e48bd5f755ef1f3); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -0x4dd4fa576f3046ac); + defer b.deinit(); + + try a.bitXor(a, b); + + try testing.expect((try a.to(u64)) == 0xc39c47081a6eb759); +} + +test "big.int bitwise xor double negative multi-limb" { + var a = try Managed.initSet(testing.allocator, -0x684e5da8f500ec8ca7204c33ccc51c9c); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -0xcb07736a7b62289c78d967c3985eebeb); + defer b.deinit(); + + try a.bitXor(a, b); + + try testing.expect((try a.to(u128)) == 0xa3492ec28e62c410dff92bf0549bf771); +} + test "big.int bitwise or simple" { var a = try Managed.initSet(testing.allocator, 0xffffffff11111111); defer a.deinit(); From e14fcd60cb11d731ca19f97e5b0b6247aa7cf07b Mon Sep 17 00:00:00 2001 From: Coleman Broaddus Date: Wed, 22 Sep 2021 05:09:16 -0400 Subject: [PATCH 093/160] FIX resize() for non u8 element types. (#9806) --- lib/std/mem.zig | 40 +++++++++++++++++++++++++++++++++++++++ lib/std/mem/Allocator.zig | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 95d4c919db..b3d0755adc 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -147,6 +147,46 @@ test "mem.Allocator basics" { try testing.expectError(error.OutOfMemory, failAllocator.allocSentinel(u8, 1, 0)); } +test "Allocator.resize" { + const primitiveIntTypes = .{ + i8, + u8, + i16, + u16, + i32, + u32, + i64, + u64, + i128, + u128, + isize, + usize, + }; + inline for (primitiveIntTypes) |T| { + var values = try testing.allocator.alloc(T, 100); + defer testing.allocator.free(values); + + for (values) |*v, i| v.* = @intCast(T, i); + values = try testing.allocator.resize(values, values.len + 10); + try testing.expect(values.len == 110); + } + + const primitiveFloatTypes = .{ + f16, + f32, + f64, + f128, + }; + inline for (primitiveFloatTypes) |T| { + var values = try testing.allocator.alloc(T, 100); + defer testing.allocator.free(values); + + for (values) |*v, i| v.* = @intToFloat(T, i); + values = try testing.allocator.resize(values, values.len + 10); + try testing.expect(values.len == 110); + } +} + /// Copy all of source into dest at position 0. /// dest.len must be >= source.len. /// If the slices overlap, dest.ptr must be <= src.ptr. diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 9ea7aeb90e..a76c27f5a0 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -313,7 +313,7 @@ pub fn resize(self: *Allocator, old_mem: anytype, new_n: usize) Error!@TypeOf(ol const new_byte_count = math.mul(usize, @sizeOf(T), new_n) catch return Error.OutOfMemory; const rc = try self.resizeFn(self, old_byte_slice, Slice.alignment, new_byte_count, 0, @returnAddress()); assert(rc == new_byte_count); - const new_byte_slice = old_mem.ptr[0..new_byte_count]; + const new_byte_slice = old_byte_slice.ptr[0..new_byte_count]; return mem.bytesAsSlice(T, new_byte_slice); } From 3b09262c1252de0d9f946630e701be65bc5b2fc7 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Mon, 20 Sep 2021 18:00:04 -0700 Subject: [PATCH 094/160] tokenizer: Fix index-out-of-bounds on unfinished unicode escapes before EOF --- lib/std/zig/tokenizer.zig | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index c24d6666f1..3ef6c9a6ba 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -772,6 +772,10 @@ pub const Tokenizer = struct { }, .char_literal_unicode_escape_saw_u => switch (c) { + 0 => { + result.tag = .invalid; + break; + }, '{' => { state = .char_literal_unicode_escape; }, @@ -782,6 +786,10 @@ pub const Tokenizer = struct { }, .char_literal_unicode_escape => switch (c) { + 0 => { + result.tag = .invalid; + break; + }, '0'...'9', 'a'...'f', 'A'...'F' => {}, '}' => { state = .char_literal_end; // too many/few digits handled later @@ -1922,8 +1930,10 @@ test "tokenizer - invalid builtin identifiers" { try testTokenize("@0()", &.{ .invalid, .integer_literal, .l_paren, .r_paren }); } -test "tokenizer - backslash before eof in string literal" { +test "tokenizer - invalid token with unfinished escape right before eof" { try testTokenize("\"\\", &.{.invalid}); + try testTokenize("'\\", &.{.invalid}); + try testTokenize("'\\u", &.{.invalid}); } fn testTokenize(source: [:0]const u8, expected_tokens: []const Token.Tag) !void { From d6e87da47b61ac31fe93a212d4f47eae55fec492 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Wed, 1 Sep 2021 14:32:02 -0500 Subject: [PATCH 095/160] Make stage2 start.zig work on Windows --- lib/std/start.zig | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/std/start.zig b/lib/std/start.zig index 057fde62f6..9e561df5ec 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -28,6 +28,8 @@ comptime { if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) { @export(main2, .{ .name = "main" }); } + } else if (builtin.stage2_os == .windows) { + @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); } else { if (!@hasDecl(root, "_start")) { @export(_start2, .{ .name = "_start" }); @@ -96,6 +98,11 @@ fn callMain2() noreturn { exit2(0); } +fn wWinMainCRTStartup2() callconv(.C) noreturn { + root.main(); + exit2(0); +} + fn exit2(code: usize) noreturn { switch (native_os) { .linux => switch (builtin.stage2_arch) { @@ -148,11 +155,16 @@ fn exit2(code: usize) noreturn { }, else => @compileError("TODO"), }, + .windows => { + ExitProcess(@truncate(u32, code)); + }, else => @compileError("TODO"), } unreachable; } +extern "kernel32" fn ExitProcess(exit_code: u32) callconv(.C) noreturn; + //////////////////////////////////////////////////////////////////////////////// fn _DllMainCRTStartup( From 4782bededa2987f493a6c02a27b0a248d04cb072 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Wed, 22 Sep 2021 14:14:03 -0500 Subject: [PATCH 096/160] Remove reference to stage2_os --- lib/std/start.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 9e561df5ec..cd2cf230af 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -28,7 +28,7 @@ comptime { if (@typeInfo(@TypeOf(root.main)).Fn.calling_convention != .C) { @export(main2, .{ .name = "main" }); } - } else if (builtin.stage2_os == .windows) { + } else if (builtin.os.tag == .windows) { @export(wWinMainCRTStartup2, .{ .name = "wWinMainCRTStartup" }); } else { if (!@hasDecl(root, "_start")) { From d86678778a6bcc31dc3cb92a286c278de1214c38 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Wed, 22 Sep 2021 14:39:02 -0500 Subject: [PATCH 097/160] Fix failing tests and windows link dependencies --- build.zig | 2 ++ test/cases.zig | 2 +- test/stage2/darwin.zig | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index fef9ee786e..11acdec6b5 100644 --- a/build.zig +++ b/build.zig @@ -115,6 +115,7 @@ pub fn build(b: *Builder) !void { if (target.isWindows() and target.getAbi() == .gnu) { // LTO is currently broken on mingw, this can be removed when it's fixed. exe.want_lto = false; + test_stage2.want_lto = false; } const exe_options = b.addOptions(); @@ -501,6 +502,7 @@ fn addStaticLlvmOptionsToExe( if (exe.target.getOs().tag == .windows) { exe.linkSystemLibrary("version"); exe.linkSystemLibrary("uuid"); + exe.linkSystemLibrary("ole32"); } } diff --git a/test/cases.zig b/test/cases.zig index 33fa5b19e2..3a8389f7d4 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -26,7 +26,7 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.exe("hello world with updates", linux_x64); case.addError("", &[_][]const u8{ - ":95:9: error: struct 'tmp.tmp' has no member named 'main'", + ":97:9: error: struct 'tmp.tmp' has no member named 'main'", }); // Incorrect return type diff --git a/test/stage2/darwin.zig b/test/stage2/darwin.zig index 959313f021..90058404d9 100644 --- a/test/stage2/darwin.zig +++ b/test/stage2/darwin.zig @@ -14,7 +14,7 @@ pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exe("darwin hello world with updates", target); case.addError("", &[_][]const u8{ - ":95:9: error: struct 'tmp.tmp' has no member named 'main'", + ":97:9: error: struct 'tmp.tmp' has no member named 'main'", }); // Incorrect return type From e03095f167cf0cd8c1424bff0de3c0a7b5f66b18 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 22 Sep 2021 19:00:43 -0700 Subject: [PATCH 098/160] stage2: remove 2 assertions that were too aggressive * `Type.hasCodeGenBits` this function is used to find out if it ever got sent to a linker backend for lowering. In the case that a struct never has its struct fields resolved, this will be false. In such a case, no corresponding `freeDecl` needs to be issued to the linker backend. So instead of asserting the fields of a struct are resolved, this function now returns `false` for this case. * `Module.clearDecl` there was logic that asserted when there is no outdated_decls map, any dependants of a Decl being cleared had to be in the deletion set. However there is a possible scenario where the dependant is not in the deletion set *yet* because there is a Decl which depends on it, about to be deleted. If it were added to an outdated_decls map, it would be subsequently removed from the map when it gets deleted recursively through its dependency being deleted. These issues were uncovered via unrelated changes which are the two commits immediately preceding this one. --- src/Module.zig | 7 ------- src/type.zig | 6 ++---- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 70c90d9c01..861648d689 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3758,13 +3758,6 @@ pub fn clearDecl( dep.removeDependency(decl); if (outdated_decls) |map| { map.putAssumeCapacity(dep, {}); - } else if (std.debug.runtime_safety) { - // If `outdated_decls` is `null`, it means we're being called from - // `Compilation` after `performAllTheWork` and we cannot queue up any - // more work. `dep` must necessarily be another Decl that is no longer - // being referenced, and will be in the `deletion_set`. Otherwise, - // something has gone wrong. - assert(mod.deletion_set.contains(dep)); } } decl.dependants.clearRetainingCapacity(); diff --git a/src/type.zig b/src/type.zig index d4993151df..f2fe9e4ccb 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1336,6 +1336,8 @@ pub const Type = extern union { } } + /// For structs and unions, if the type does not have their fields resolved + /// this will return `false`. pub fn hasCodeGenBits(self: Type) bool { return switch (self.tag()) { .u1, @@ -1400,14 +1402,10 @@ pub const Type = extern union { => true, .@"struct" => { - // TODO introduce lazy value mechanism const struct_obj = self.castTag(.@"struct").?.data; if (struct_obj.known_has_bits) { return true; } - assert(struct_obj.status == .have_field_types or - struct_obj.status == .layout_wip or - struct_obj.status == .have_layout); for (struct_obj.fields.values()) |value| { if (value.ty.hasCodeGenBits()) return true; From f5c27dd11aeac3bb4647396a0e420c649b30f468 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 23 Sep 2021 04:08:27 +0200 Subject: [PATCH 099/160] big ints: 2s complement signed or --- lib/std/math/big/int.zig | 140 ++++++++++++++++++++++++++++++---- lib/std/math/big/int_test.zig | 66 ++++++++++++++++ 2 files changed, 191 insertions(+), 15 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index e1b9c94fff..8b9cc7168a 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -552,21 +552,29 @@ pub const Mutable = struct { r.positive = a.positive; } - /// r = a | b + /// r = a | b under 2s complement semantics. /// r may alias with a or b. /// /// a and b are zero-extended to the longer of a or b. /// /// Asserts that r has enough limbs to store the result. Upper bound is `math.max(a.limbs.len, b.limbs.len)`. pub fn bitOr(r: *Mutable, a: Const, b: Const) void { - if (a.limbs.len > b.limbs.len) { - llor(r.limbs[0..], a.limbs[0..a.limbs.len], b.limbs[0..b.limbs.len]); - r.len = a.limbs.len; - } else { - llor(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]); - r.len = b.limbs.len; + // Trivial cases, llsignedor does not support zero. + if (a.eqZero()) { + r.copy(b); + return; + } else if (b.eqZero()) { + r.copy(a); + return; + } + + if (a.limbs.len >= b.limbs.len) { + r.positive = llsignedor(r.limbs, a.limbs, a.positive, b.limbs, b.positive); + r.normalize(a.limbs.len); + } else { + r.positive = llsignedor(r.limbs, b.limbs, b.positive, a.limbs, a.positive); + r.normalize(b.limbs.len); } - r.positive = a.positive or b.positive; } /// r = a & b @@ -2236,17 +2244,119 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void { } } -fn llor(r: []Limb, a: []const Limb, b: []const Limb) void { +fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool { @setRuntimeSafety(debug_safety); assert(r.len >= a.len); assert(a.len >= b.len); - var i: usize = 0; - while (i < b.len) : (i += 1) { - r[i] = a[i] | b[i]; - } - while (i < a.len) : (i += 1) { - r[i] = a[i]; + if (a_positive and b_positive) { + // Trivial case, result is positive., + var i: usize = 0; + while (i < b.len) : (i += 1) { + r[i] = a[i] | b[i]; + } + while (i < a.len) : (i += 1) { + r[i] = a[i]; + } + + return true; + } else if (!a_positive and b_positive) { + // r = (--a) | b + // = ~(-a - 1) | b + // = ~(-a - 1) | ~~b + // = ~((-a - 1) & ~b) + // = -(((-a - 1) & ~b) + 1) + // result is negative. + + var i: usize = 0; + var a_borrow: u1 = 1; + var r_carry: u1 = 1; + + while (i < b.len) : (i += 1) { + var a_limb: Limb = undefined; + a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb)); + + r[i] = a_limb & ~b[i]; + r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); + } + + // With b = 0, we get (-a - 1) & ~0 = -a - 1. + while (i < a.len) : (i += 1) { + a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &r[i])); + r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); + } + + assert(a_borrow == 0); // a was 0. + + // Can never overflow because a_borrow would need to equal 1. + assert(r_carry == 0); + + return false; + } else if (a_positive and !b_positive) { + // r = a | (--b) + // = a | ~(-b - 1) + // = ~~a | ~(-b - 1) + // = ~(~a & (-b - 1)) + // = -((~a & (-b - 1)) + 1) + // result is negative. + + var i: usize = 0; + var b_borrow: u1 = 1; + var r_carry: u1 = 1; + + while (i < b.len) : (i += 1) { + var b_limb: Limb = undefined; + b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &b_limb)); + + r[i] = ~a[i] & b_limb; + r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); + } + + // b is at least 1, so this should never underflow. + assert(b_borrow == 0); // b was 0 + + // Can never overflow because in order for b_limb to be maxInt(Limb), + // b_borrow would need to equal 1. + assert(r_carry == 0); + + // With b = 0 and b_borrow = 0, we get ~a & (-0 - 0) = ~a & 0 = 0. + mem.set(Limb, r[i..a.len], 0); + + return false; + } else { + // r = (--a) | (--b) + // = ~(-a - 1) | ~(-b - 1) + // = ~((-a - 1) & (-b - 1)) + // = -(~(~((-a - 1) & (-b - 1))) + 1) + // = -((-a - 1) & (-b - 1) + 1) + // result is negative. + + var i: usize = 0; + var a_borrow: u1 = 1; + var b_borrow: u1 = 1; + var r_carry: u1 = 1; + + while (i < b.len) : (i += 1) { + var a_limb: Limb = undefined; + a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb)); + + var b_limb: Limb = undefined; + b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &b_limb)); + + r[i] = a_limb & b_limb; + r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); + } + + // b is at least 1, so this should never underflow. + assert(b_borrow == 0); // b was 0 + + // Can never overflow because in order for b_limb to be maxInt(Limb), + // b_borrow would need to equal 1. + assert(r_carry == 0); + + // With b = 0 and b_borrow = 0 we get (-a - 1) & (-0 - 0) = (-a - 1) & 0 = 0. + mem.set(Limb, r[i..a.len], 0); + return false; } } diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index 993101c674..b9f7c5c116 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -1476,6 +1476,72 @@ test "big.int bitwise or multi-limb" { try testing.expect((try a.to(DoubleLimb)) == (maxInt(Limb) + 1) + maxInt(Limb)); } +test "big.int bitwise or negative-positive simple" { + var a = try Managed.initSet(testing.allocator, -0xffffffff11111111); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, 0xeeeeeeee22222222); + defer b.deinit(); + + try a.bitOr(a, b); + + try testing.expect((try a.to(i64)) == -0x1111111111111111); +} + +test "big.int bitwise or negative-positive multi-limb" { + var a = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, 1); + defer b.deinit(); + + try a.bitOr(a, b); + + try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb)); +} + +test "big.int bitwise or positive-negative simple" { + var a = try Managed.initSet(testing.allocator, 0xffffffff11111111); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -0xeeeeeeee22222222); + defer b.deinit(); + + try a.bitOr(a, b); + + try testing.expect((try a.to(i64)) == -0x22222221); +} + +test "big.int bitwise or positive-negative multi-limb" { + var a = try Managed.initSet(testing.allocator, maxInt(Limb) + 1); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -1); + defer b.deinit(); + + try a.bitOr(a, b); + + try testing.expect((try a.to(SignedDoubleLimb)) == -1); +} + +test "big.int bitwise or negative-negative simple" { + var a = try Managed.initSet(testing.allocator, -0x0fffffff11111111); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -0x0eeeeeee22222222); + defer b.deinit(); + + try a.bitOr(a, b); + + try testing.expect((try a.to(i64)) == -0xeeeeeee00000001); +} + +test "big.int bitwise or negative-negative multi-limb" { + var a = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -maxInt(Limb)); + defer b.deinit(); + + try a.bitOr(a, b); + + try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb)); +} + test "big.int var args" { var a = try Managed.initSet(testing.allocator, 5); defer a.deinit(); From 736d14fd5fa5feea83a6efce8b606b62bf165033 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 22 Sep 2021 21:02:24 -0700 Subject: [PATCH 100/160] stage2: fix AstGen for some struct syntaxes * AstGen: fix not emitting `struct_init_empty` when an explicit type is present in struct initialization syntax. * AstGen: these two syntaxes now lower to identical ZIR: - `var a = A{ .b = c };` - `var a = @as(A, .{ .b = c });` * Zir: clarify `auto_enum_tag` in the doc comments. * LLVM Backend: fix lowering of function return types when the type has 0 bits. --- src/AstGen.zig | 110 ++-- src/Zir.zig | 8 +- src/codegen/llvm.zig | 7 +- test/behavior.zig | 3 +- test/behavior/struct.zig | 934 +------------------------------- test/behavior/struct_stage1.zig | 928 +++++++++++++++++++++++++++++++ 6 files changed, 1028 insertions(+), 962 deletions(-) create mode 100644 test/behavior/struct_stage1.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 6a533dff11..b33c5aad40 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1357,7 +1357,14 @@ fn structInitExpr( const array_type: Ast.full.ArrayType = switch (node_tags[struct_init.ast.type_expr]) { .array_type => tree.arrayType(struct_init.ast.type_expr), .array_type_sentinel => tree.arrayTypeSentinel(struct_init.ast.type_expr), - else => break :array, + else => { + if (struct_init.ast.fields.len == 0) { + const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); + const result = try gz.addUnNode(.struct_init_empty, ty_inst, node); + return rvalue(gz, rl, result, node); + } + break :array; + }, }; const is_inferred_array_len = node_tags[array_type.ast.elem_count] == .identifier and // This intentionally does not support `@"_"` syntax. @@ -1419,8 +1426,8 @@ fn structInitExpr( const result = try structInitExprRlTy(gz, scope, node, struct_init, inner_ty_inst, .struct_init); return rvalue(gz, rl, result, node); }, - .ptr, .inferred_ptr => |ptr_inst| return structInitExprRlPtr(gz, scope, node, struct_init, ptr_inst), - .block_ptr => |block_gz| return structInitExprRlPtr(gz, scope, node, struct_init, block_gz.rl_ptr), + .ptr, .inferred_ptr => |ptr_inst| return structInitExprRlPtr(gz, scope, rl, node, struct_init, ptr_inst), + .block_ptr => |block_gz| return structInitExprRlPtr(gz, scope, rl, node, struct_init, block_gz.rl_ptr), } } @@ -1459,6 +1466,26 @@ fn structInitExprRlNone( } fn structInitExprRlPtr( + gz: *GenZir, + scope: *Scope, + rl: ResultLoc, + node: Ast.Node.Index, + struct_init: Ast.full.StructInit, + result_ptr: Zir.Inst.Ref, +) InnerError!Zir.Inst.Ref { + if (struct_init.ast.type_expr == 0) { + return structInitExprRlPtrInner(gz, scope, node, struct_init, result_ptr); + } + const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); + + var as_scope = try gz.makeCoercionScope(scope, ty_inst, result_ptr); + defer as_scope.instructions.deinit(gz.astgen.gpa); + + const result = try structInitExprRlPtrInner(&as_scope, scope, node, struct_init, as_scope.rl_ptr); + return as_scope.finishCoercion(gz, rl, node, result, ty_inst); +} + +fn structInitExprRlPtrInner( gz: *GenZir, scope: *Scope, node: Ast.Node.Index, @@ -1472,9 +1499,6 @@ fn structInitExprRlPtr( const field_ptr_list = try gpa.alloc(Zir.Inst.Index, struct_init.ast.fields.len); defer gpa.free(field_ptr_list); - if (struct_init.ast.type_expr != 0) - _ = try typeExpr(gz, scope, struct_init.ast.type_expr); - for (struct_init.ast.fields) |field_init, i| { const name_token = tree.firstToken(field_init) - 2; const str_index = try astgen.identAsString(name_token); @@ -1489,7 +1513,7 @@ fn structInitExprRlPtr( .body_len = @intCast(u32, field_ptr_list.len), }); try astgen.extra.appendSlice(gpa, field_ptr_list); - return .void_value; + return Zir.Inst.Ref.void_value; } fn structInitExprRlTy( @@ -6902,35 +6926,13 @@ fn asRlPtr( operand_node: Ast.Node.Index, dest_type: Zir.Inst.Ref, ) InnerError!Zir.Inst.Ref { - // Detect whether this expr() call goes into rvalue() to store the result into the - // result location. If it does, elide the coerce_result_ptr instruction - // as well as the store instruction, instead passing the result as an rvalue. const astgen = parent_gz.astgen; - var as_scope = parent_gz.makeSubBlock(scope); + var as_scope = try parent_gz.makeCoercionScope(scope, dest_type, result_ptr); defer as_scope.instructions.deinit(astgen.gpa); - as_scope.rl_ptr = try as_scope.addBin(.coerce_result_ptr, dest_type, result_ptr); const result = try reachableExpr(&as_scope, &as_scope.base, .{ .block_ptr = &as_scope }, operand_node, src_node); - const parent_zir = &parent_gz.instructions; - if (as_scope.rvalue_rl_count == 1) { - // Busted! This expression didn't actually need a pointer. - const zir_tags = astgen.instructions.items(.tag); - const zir_datas = astgen.instructions.items(.data); - try parent_zir.ensureUnusedCapacity(astgen.gpa, as_scope.instructions.items.len); - for (as_scope.instructions.items) |src_inst| { - if (indexToRef(src_inst) == as_scope.rl_ptr) continue; - if (zir_tags[src_inst] == .store_to_block_ptr) { - if (zir_datas[src_inst].bin.lhs == as_scope.rl_ptr) continue; - } - parent_zir.appendAssumeCapacity(src_inst); - } - const casted_result = try parent_gz.addBin(.as, dest_type, result); - return rvalue(parent_gz, rl, casted_result, operand_node); - } else { - try parent_zir.appendSlice(astgen.gpa, as_scope.instructions.items); - return result; - } + return as_scope.finishCoercion(parent_gz, rl, operand_node, result, dest_type); } fn bitCast( @@ -9108,6 +9110,52 @@ const GenZir = struct { }; } + fn makeCoercionScope( + parent_gz: *GenZir, + scope: *Scope, + dest_type: Zir.Inst.Ref, + result_ptr: Zir.Inst.Ref, + ) !GenZir { + // Detect whether this expr() call goes into rvalue() to store the result into the + // result location. If it does, elide the coerce_result_ptr instruction + // as well as the store instruction, instead passing the result as an rvalue. + var as_scope = parent_gz.makeSubBlock(scope); + errdefer as_scope.instructions.deinit(parent_gz.astgen.gpa); + as_scope.rl_ptr = try as_scope.addBin(.coerce_result_ptr, dest_type, result_ptr); + + return as_scope; + } + + fn finishCoercion( + as_scope: *GenZir, + parent_gz: *GenZir, + rl: ResultLoc, + src_node: Ast.Node.Index, + result: Zir.Inst.Ref, + dest_type: Zir.Inst.Ref, + ) !Zir.Inst.Ref { + const astgen = as_scope.astgen; + const parent_zir = &parent_gz.instructions; + if (as_scope.rvalue_rl_count == 1) { + // Busted! This expression didn't actually need a pointer. + const zir_tags = astgen.instructions.items(.tag); + const zir_datas = astgen.instructions.items(.data); + try parent_zir.ensureUnusedCapacity(astgen.gpa, as_scope.instructions.items.len); + for (as_scope.instructions.items) |src_inst| { + if (indexToRef(src_inst) == as_scope.rl_ptr) continue; + if (zir_tags[src_inst] == .store_to_block_ptr) { + if (zir_datas[src_inst].bin.lhs == as_scope.rl_ptr) continue; + } + parent_zir.appendAssumeCapacity(src_inst); + } + const casted_result = try parent_gz.addBin(.as, dest_type, result); + return rvalue(parent_gz, rl, casted_result, src_node); + } else { + try parent_zir.appendSlice(astgen.gpa, as_scope.instructions.items); + return result; + } + } + const Label = struct { token: Ast.TokenIndex, block_inst: Zir.Inst.Index, diff --git a/src/Zir.zig b/src/Zir.zig index d3b1678d97..7a07f0aaaa 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2650,8 +2650,12 @@ pub const Inst = struct { has_decls_len: bool, name_strategy: NameStrategy, layout: std.builtin.TypeInfo.ContainerLayout, - /// false: union(tag_type) - /// true: union(enum(tag_type)) + /// has_tag_type | auto_enum_tag | result + /// ------------------------------------- + /// false | false | union { } + /// false | true | union(enum) { } + /// true | true | union(enum(T)) { } + /// true | false | union(T) { } auto_enum_tag: bool, _: u6 = undefined, }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0d3bca2b03..6daf720961 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -563,8 +563,13 @@ pub const DeclGen = struct { } } + const llvm_ret_ty = if (!return_type.hasCodeGenBits()) + self.context.voidType() + else + try self.llvmType(return_type); + const fn_type = llvm.functionType( - try self.llvmType(return_type), + llvm_ret_ty, llvm_param_buffer.ptr, llvm_params_len, .False, diff --git a/test/behavior.zig b/test/behavior.zig index 7fbba99a8c..78f3651b01 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -13,6 +13,7 @@ test { _ = @import("behavior/atomics.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); _ = @import("behavior/translate_c_macros.zig"); + _ = @import("behavior/struct.zig"); _ = @import("behavior/union.zig"); _ = @import("behavior/widening.zig"); @@ -135,7 +136,7 @@ test { _ = @import("behavior/sizeof_and_typeof_stage1.zig"); _ = @import("behavior/slice.zig"); _ = @import("behavior/slice_sentinel_comptime.zig"); - _ = @import("behavior/struct.zig"); + _ = @import("behavior/struct_stage1.zig"); _ = @import("behavior/struct_contains_null_ptr_itself.zig"); _ = @import("behavior/struct_contains_slice_of_itself.zig"); _ = @import("behavior/switch.zig"); diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index d9e1c02aa1..6f00b71057 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -5,949 +5,29 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectEqualSlices = std.testing.expectEqualSlices; const maxInt = std.math.maxInt; + const StructWithNoFields = struct { fn add(a: i32, b: i32) i32 { return a + b; } }; -const empty_global_instance = StructWithNoFields{}; - -top_level_field: i32, - -test "top level fields" { - var instance = @This(){ - .top_level_field = 1234, - }; - instance.top_level_field += 1; - try expectEqual(@as(i32, 1235), instance.top_level_field); -} test "call struct static method" { const result = StructWithNoFields.add(3, 4); try expect(result == 7); } -test "return empty struct instance" { - _ = returnEmptyStructInstance(); -} -fn returnEmptyStructInstance() StructWithNoFields { - return empty_global_instance; -} - const should_be_11 = StructWithNoFields.add(5, 6); test "invoke static method in global scope" { try expect(should_be_11 == 11); } -test "void struct fields" { - const foo = VoidStructFieldsFoo{ - .a = void{}, - .b = 1, - .c = void{}, - }; - try expect(foo.b == 1); - try expect(@sizeOf(VoidStructFieldsFoo) == 4); +const empty_global_instance = StructWithNoFields{}; + +test "return empty struct instance" { + _ = returnEmptyStructInstance(); } -const VoidStructFieldsFoo = struct { - a: void, - b: i32, - c: void, -}; - -test "structs" { - var foo: StructFoo = undefined; - @memset(@ptrCast([*]u8, &foo), 0, @sizeOf(StructFoo)); - foo.a += 1; - foo.b = foo.a == 1; - try testFoo(foo); - testMutation(&foo); - try expect(foo.c == 100); -} -const StructFoo = struct { - a: i32, - b: bool, - c: f32, -}; -fn testFoo(foo: StructFoo) !void { - try expect(foo.b); -} -fn testMutation(foo: *StructFoo) void { - foo.c = 100; -} - -const Node = struct { - val: Val, - next: *Node, -}; - -const Val = struct { - x: i32, -}; - -test "struct point to self" { - var root: Node = undefined; - root.val.x = 1; - - var node: Node = undefined; - node.next = &root; - node.val.x = 2; - - root.next = &node; - - try expect(node.next.next.next.val.x == 1); -} - -test "struct byval assign" { - var foo1: StructFoo = undefined; - var foo2: StructFoo = undefined; - - foo1.a = 1234; - foo2.a = 0; - try expect(foo2.a == 0); - foo2 = foo1; - try expect(foo2.a == 1234); -} - -fn structInitializer() void { - const val = Val{ .x = 42 }; - try expect(val.x == 42); -} - -test "fn call of struct field" { - const Foo = struct { - ptr: fn () i32, - }; - const S = struct { - fn aFunc() i32 { - return 13; - } - - fn callStructField(foo: Foo) i32 { - return foo.ptr(); - } - }; - - try expect(S.callStructField(Foo{ .ptr = S.aFunc }) == 13); -} - -test "store member function in variable" { - const instance = MemberFnTestFoo{ .x = 1234 }; - const memberFn = MemberFnTestFoo.member; - const result = memberFn(instance); - try expect(result == 1234); -} -const MemberFnTestFoo = struct { - x: i32, - fn member(foo: MemberFnTestFoo) i32 { - return foo.x; - } -}; - -test "call member function directly" { - const instance = MemberFnTestFoo{ .x = 1234 }; - const result = MemberFnTestFoo.member(instance); - try expect(result == 1234); -} - -test "member functions" { - const r = MemberFnRand{ .seed = 1234 }; - try expect(r.getSeed() == 1234); -} -const MemberFnRand = struct { - seed: u32, - pub fn getSeed(r: *const MemberFnRand) u32 { - return r.seed; - } -}; - -test "return struct byval from function" { - const bar = makeBar2(1234, 5678); - try expect(bar.y == 5678); -} -const Bar = struct { - x: i32, - y: i32, -}; -fn makeBar2(x: i32, y: i32) Bar { - return Bar{ - .x = x, - .y = y, - }; -} - -test "empty struct method call" { - const es = EmptyStruct{}; - try expect(es.method() == 1234); -} -const EmptyStruct = struct { - fn method(es: *const EmptyStruct) i32 { - _ = es; - return 1234; - } -}; - -test "return empty struct from fn" { - _ = testReturnEmptyStructFromFn(); -} -const EmptyStruct2 = struct {}; -fn testReturnEmptyStructFromFn() EmptyStruct2 { - return EmptyStruct2{}; -} - -test "pass slice of empty struct to fn" { - try expect(testPassSliceOfEmptyStructToFn(&[_]EmptyStruct2{EmptyStruct2{}}) == 1); -} -fn testPassSliceOfEmptyStructToFn(slice: []const EmptyStruct2) usize { - return slice.len; -} - -const APackedStruct = packed struct { - x: u8, - y: u8, -}; - -test "packed struct" { - var foo = APackedStruct{ - .x = 1, - .y = 2, - }; - foo.y += 1; - const four = foo.x + foo.y; - try expect(four == 4); -} - -const BitField1 = packed struct { - a: u3, - b: u3, - c: u2, -}; - -const bit_field_1 = BitField1{ - .a = 1, - .b = 2, - .c = 3, -}; - -test "bit field access" { - var data = bit_field_1; - try expect(getA(&data) == 1); - try expect(getB(&data) == 2); - try expect(getC(&data) == 3); - comptime try expect(@sizeOf(BitField1) == 1); - - data.b += 1; - try expect(data.b == 3); - - data.a += 1; - try expect(data.a == 2); - try expect(data.b == 3); -} - -fn getA(data: *const BitField1) u3 { - return data.a; -} - -fn getB(data: *const BitField1) u3 { - return data.b; -} - -fn getC(data: *const BitField1) u2 { - return data.c; -} - -const Foo24Bits = packed struct { - field: u24, -}; -const Foo96Bits = packed struct { - a: u24, - b: u24, - c: u24, - d: u24, -}; - -test "packed struct 24bits" { - comptime { - try expect(@sizeOf(Foo24Bits) == 4); - if (@sizeOf(usize) == 4) { - try expect(@sizeOf(Foo96Bits) == 12); - } else { - try expect(@sizeOf(Foo96Bits) == 16); - } - } - - var value = Foo96Bits{ - .a = 0, - .b = 0, - .c = 0, - .d = 0, - }; - value.a += 1; - try expect(value.a == 1); - try expect(value.b == 0); - try expect(value.c == 0); - try expect(value.d == 0); - - value.b += 1; - try expect(value.a == 1); - try expect(value.b == 1); - try expect(value.c == 0); - try expect(value.d == 0); - - value.c += 1; - try expect(value.a == 1); - try expect(value.b == 1); - try expect(value.c == 1); - try expect(value.d == 0); - - value.d += 1; - try expect(value.a == 1); - try expect(value.b == 1); - try expect(value.c == 1); - try expect(value.d == 1); -} - -const Foo32Bits = packed struct { - field: u24, - pad: u8, -}; - -const FooArray24Bits = packed struct { - a: u16, - b: [2]Foo32Bits, - c: u16, -}; - -// TODO revisit this test when doing https://github.com/ziglang/zig/issues/1512 -test "packed array 24bits" { - comptime { - try expect(@sizeOf([9]Foo32Bits) == 9 * 4); - try expect(@sizeOf(FooArray24Bits) == 2 + 2 * 4 + 2); - } - - var bytes = [_]u8{0} ** (@sizeOf(FooArray24Bits) + 1); - bytes[bytes.len - 1] = 0xaa; - const ptr = &std.mem.bytesAsSlice(FooArray24Bits, bytes[0 .. bytes.len - 1])[0]; - try expect(ptr.a == 0); - try expect(ptr.b[0].field == 0); - try expect(ptr.b[1].field == 0); - try expect(ptr.c == 0); - - ptr.a = maxInt(u16); - try expect(ptr.a == maxInt(u16)); - try expect(ptr.b[0].field == 0); - try expect(ptr.b[1].field == 0); - try expect(ptr.c == 0); - - ptr.b[0].field = maxInt(u24); - try expect(ptr.a == maxInt(u16)); - try expect(ptr.b[0].field == maxInt(u24)); - try expect(ptr.b[1].field == 0); - try expect(ptr.c == 0); - - ptr.b[1].field = maxInt(u24); - try expect(ptr.a == maxInt(u16)); - try expect(ptr.b[0].field == maxInt(u24)); - try expect(ptr.b[1].field == maxInt(u24)); - try expect(ptr.c == 0); - - ptr.c = maxInt(u16); - try expect(ptr.a == maxInt(u16)); - try expect(ptr.b[0].field == maxInt(u24)); - try expect(ptr.b[1].field == maxInt(u24)); - try expect(ptr.c == maxInt(u16)); - - try expect(bytes[bytes.len - 1] == 0xaa); -} - -const FooStructAligned = packed struct { - a: u8, - b: u8, -}; - -const FooArrayOfAligned = packed struct { - a: [2]FooStructAligned, -}; - -test "aligned array of packed struct" { - comptime { - try expect(@sizeOf(FooStructAligned) == 2); - try expect(@sizeOf(FooArrayOfAligned) == 2 * 2); - } - - var bytes = [_]u8{0xbb} ** @sizeOf(FooArrayOfAligned); - const ptr = &std.mem.bytesAsSlice(FooArrayOfAligned, bytes[0..])[0]; - - try expect(ptr.a[0].a == 0xbb); - try expect(ptr.a[0].b == 0xbb); - try expect(ptr.a[1].a == 0xbb); - try expect(ptr.a[1].b == 0xbb); -} - -test "runtime struct initialization of bitfield" { - const s1 = Nibbles{ - .x = x1, - .y = x1, - }; - const s2 = Nibbles{ - .x = @intCast(u4, x2), - .y = @intCast(u4, x2), - }; - - try expect(s1.x == x1); - try expect(s1.y == x1); - try expect(s2.x == @intCast(u4, x2)); - try expect(s2.y == @intCast(u4, x2)); -} - -var x1 = @as(u4, 1); -var x2 = @as(u8, 2); - -const Nibbles = packed struct { - x: u4, - y: u4, -}; - -const Bitfields = packed struct { - f1: u16, - f2: u16, - f3: u8, - f4: u8, - f5: u4, - f6: u4, - f7: u8, -}; - -test "native bit field understands endianness" { - var all: u64 = if (native_endian != .Little) - 0x1111222233445677 - else - 0x7765443322221111; - var bytes: [8]u8 = undefined; - @memcpy(&bytes, @ptrCast([*]u8, &all), 8); - var bitfields = @ptrCast(*Bitfields, &bytes).*; - - try expect(bitfields.f1 == 0x1111); - try expect(bitfields.f2 == 0x2222); - try expect(bitfields.f3 == 0x33); - try expect(bitfields.f4 == 0x44); - try expect(bitfields.f5 == 0x5); - try expect(bitfields.f6 == 0x6); - try expect(bitfields.f7 == 0x77); -} - -test "align 1 field before self referential align 8 field as slice return type" { - const result = alloc(Expr); - try expect(result.len == 0); -} - -const Expr = union(enum) { - Literal: u8, - Question: *Expr, -}; - -fn alloc(comptime T: type) []T { - return &[_]T{}; -} - -test "call method with mutable reference to struct with no fields" { - const S = struct { - fn doC(s: *const @This()) bool { - _ = s; - return true; - } - fn do(s: *@This()) bool { - _ = s; - return true; - } - }; - - var s = S{}; - try expect(S.doC(&s)); - try expect(s.doC()); - try expect(S.do(&s)); - try expect(s.do()); -} - -test "implicit cast packed struct field to const ptr" { - const LevelUpMove = packed struct { - move_id: u9, - level: u7, - - fn toInt(value: u7) u7 { - return value; - } - }; - - var lup: LevelUpMove = undefined; - lup.level = 12; - const res = LevelUpMove.toInt(lup.level); - try expect(res == 12); -} - -test "pointer to packed struct member in a stack variable" { - const S = packed struct { - a: u2, - b: u2, - }; - - var s = S{ .a = 2, .b = 0 }; - var b_ptr = &s.b; - try expect(s.b == 0); - b_ptr.* = 2; - try expect(s.b == 2); -} - -test "non-byte-aligned array inside packed struct" { - const Foo = packed struct { - a: bool, - b: [0x16]u8, - }; - const S = struct { - fn bar(slice: []const u8) !void { - try expectEqualSlices(u8, slice, "abcdefghijklmnopqurstu"); - } - fn doTheTest() !void { - var foo = Foo{ - .a = true, - .b = "abcdefghijklmnopqurstu".*, - }; - const value = foo.b; - try bar(&value); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "packed struct with u0 field access" { - const S = packed struct { - f0: u0, - }; - var s = S{ .f0 = 0 }; - comptime try expect(s.f0 == 0); -} - -const S0 = struct { - bar: S1, - - pub const S1 = struct { - value: u8, - }; - - fn init() @This() { - return S0{ .bar = S1{ .value = 123 } }; - } -}; - -var g_foo: S0 = S0.init(); - -test "access to global struct fields" { - g_foo.bar.value = 42; - try expect(g_foo.bar.value == 42); -} - -test "packed struct with fp fields" { - const S = packed struct { - data: [3]f32, - - pub fn frob(self: *@This()) void { - self.data[0] += self.data[1] + self.data[2]; - self.data[1] += self.data[0] + self.data[2]; - self.data[2] += self.data[0] + self.data[1]; - } - }; - - var s: S = undefined; - s.data[0] = 1.0; - s.data[1] = 2.0; - s.data[2] = 3.0; - s.frob(); - try expectEqual(@as(f32, 6.0), s.data[0]); - try expectEqual(@as(f32, 11.0), s.data[1]); - try expectEqual(@as(f32, 20.0), s.data[2]); -} - -test "use within struct scope" { - const S = struct { - usingnamespace struct { - pub fn inner() i32 { - return 42; - } - }; - }; - try expectEqual(@as(i32, 42), S.inner()); -} - -test "default struct initialization fields" { - const S = struct { - a: i32 = 1234, - b: i32, - }; - const x = S{ - .b = 5, - }; - var five: i32 = 5; - const y = S{ - .b = five, - }; - if (x.a + x.b != 1239) { - @compileError("it should be comptime known"); - } - try expectEqual(y, x); - try expectEqual(1239, x.a + x.b); -} - -test "fn with C calling convention returns struct by value" { - const S = struct { - fn entry() !void { - var x = makeBar(10); - try expectEqual(@as(i32, 10), x.handle); - } - - const ExternBar = extern struct { - handle: i32, - }; - - fn makeBar(t: i32) callconv(.C) ExternBar { - return ExternBar{ - .handle = t, - }; - } - }; - try S.entry(); - comptime try S.entry(); -} - -test "for loop over pointers to struct, getting field from struct pointer" { - const S = struct { - const Foo = struct { - name: []const u8, - }; - - var ok = true; - - fn eql(a: []const u8) bool { - _ = a; - return true; - } - - const ArrayList = struct { - fn toSlice(self: *ArrayList) []*Foo { - _ = self; - return @as([*]*Foo, undefined)[0..0]; - } - }; - - fn doTheTest() !void { - var objects: ArrayList = undefined; - - for (objects.toSlice()) |obj| { - if (eql(obj.name)) { - ok = false; - } - } - - try expect(ok); - } - }; - try S.doTheTest(); -} - -test "zero-bit field in packed struct" { - const S = packed struct { - x: u10, - y: void, - }; - var x: S = undefined; - _ = x; -} - -test "struct field init with catch" { - const S = struct { - fn doTheTest() !void { - var x: anyerror!isize = 1; - var req = Foo{ - .field = x catch undefined, - }; - try expect(req.field == 1); - } - - pub const Foo = extern struct { - field: isize, - }; - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "packed struct with non-ABI-aligned field" { - const S = packed struct { - x: u9, - y: u183, - }; - var s: S = undefined; - s.x = 1; - s.y = 42; - try expect(s.x == 1); - try expect(s.y == 42); -} - -test "non-packed struct with u128 entry in union" { - const U = union(enum) { - Num: u128, - Void, - }; - - const S = struct { - f1: U, - f2: U, - }; - - var sx: S = undefined; - var s = &sx; - try std.testing.expect(@ptrToInt(&s.f2) - @ptrToInt(&s.f1) == @offsetOf(S, "f2")); - var v2 = U{ .Num = 123 }; - s.f2 = v2; - try std.testing.expect(s.f2.Num == 123); -} - -test "packed struct field passed to generic function" { - const S = struct { - const P = packed struct { - b: u5, - g: u5, - r: u5, - a: u1, - }; - - fn genericReadPackedField(ptr: anytype) u5 { - return ptr.*; - } - }; - - var p: S.P = undefined; - p.b = 29; - var loaded = S.genericReadPackedField(&p.b); - try expect(loaded == 29); -} - -test "anonymous struct literal syntax" { - const S = struct { - const Point = struct { - x: i32, - y: i32, - }; - - fn doTheTest() !void { - var p: Point = .{ - .x = 1, - .y = 2, - }; - try expect(p.x == 1); - try expect(p.y == 2); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "fully anonymous struct" { - const S = struct { - fn doTheTest() !void { - try dump(.{ - .int = @as(u32, 1234), - .float = @as(f64, 12.34), - .b = true, - .s = "hi", - }); - } - fn dump(args: anytype) !void { - try expect(args.int == 1234); - try expect(args.float == 12.34); - try expect(args.b); - try expect(args.s[0] == 'h'); - try expect(args.s[1] == 'i'); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "fully anonymous list literal" { - const S = struct { - fn doTheTest() !void { - try dump(.{ @as(u32, 1234), @as(f64, 12.34), true, "hi" }); - } - fn dump(args: anytype) !void { - try expect(args.@"0" == 1234); - try expect(args.@"1" == 12.34); - try expect(args.@"2"); - try expect(args.@"3"[0] == 'h'); - try expect(args.@"3"[1] == 'i'); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "anonymous struct literal assigned to variable" { - var vec = .{ @as(i32, 22), @as(i32, 55), @as(i32, 99) }; - try expect(vec.@"0" == 22); - try expect(vec.@"1" == 55); - try expect(vec.@"2" == 99); -} - -test "struct with var field" { - const Point = struct { - x: anytype, - y: anytype, - }; - const pt = Point{ - .x = 1, - .y = 2, - }; - try expect(pt.x == 1); - try expect(pt.y == 2); -} - -test "comptime struct field" { - const T = struct { - a: i32, - comptime b: i32 = 1234, - }; - - var foo: T = undefined; - comptime try expect(foo.b == 1234); -} - -test "anon struct literal field value initialized with fn call" { - const S = struct { - fn doTheTest() !void { - var x = .{foo()}; - try expectEqualSlices(u8, x[0], "hi"); - } - fn foo() []const u8 { - return "hi"; - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "self-referencing struct via array member" { - const T = struct { - children: [1]*@This(), - }; - var x: T = undefined; - x = T{ .children = .{&x} }; - try expect(x.children[0] == &x); -} - -test "struct with union field" { - const Value = struct { - ref: u32 = 2, - kind: union(enum) { - None: usize, - Bool: bool, - }, - }; - - var True = Value{ - .kind = .{ .Bool = true }, - }; - try expectEqual(@as(u32, 2), True.ref); - try 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; - try expect(y0.A == 123); - try expect(std.mem.eql(u8, y0.B, "foo")); - try expect(y0.C == {}); - try expect(y0.D.field == 1234); - try expect(y1.A == y); - try expect(std.mem.eql(u8, y1.B, "foo")); - try expect(y1.C == {}); - try expect(y1.D.field == 1234); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "type coercion of pointer to anon struct literal to pointer 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: *const S2 = t0; - var y1: *const S2 = t1; - try expect(y0.A == 123); - try expect(std.mem.eql(u8, y0.B, "foo")); - try expect(y0.C == {}); - try expect(y0.D.field == 1234); - try expect(y1.A == y); - try expect(std.mem.eql(u8, y1.B, "foo")); - try expect(y1.C == {}); - try expect(y1.D.field == 1234); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "packed struct with undefined initializers" { - const S = struct { - const P = packed struct { - a: u3, - _a: u3 = undefined, - b: u3, - _b: u3 = undefined, - c: u3, - _c: u3 = undefined, - }; - - fn doTheTest() !void { - var p: P = undefined; - p = P{ .a = 2, .b = 4, .c = 6 }; - // Make sure the compiler doesn't touch the unprefixed fields. - // Use expect since i386-linux doesn't like expectEqual - try expect(p.a == 2); - try expect(p.b == 4); - try expect(p.c == 6); - } - }; - - try S.doTheTest(); - comptime try S.doTheTest(); +fn returnEmptyStructInstance() StructWithNoFields { + return empty_global_instance; } diff --git a/test/behavior/struct_stage1.zig b/test/behavior/struct_stage1.zig new file mode 100644 index 0000000000..9f084ceb85 --- /dev/null +++ b/test/behavior/struct_stage1.zig @@ -0,0 +1,928 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const native_endian = builtin.target.cpu.arch.endian(); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualSlices = std.testing.expectEqualSlices; +const maxInt = std.math.maxInt; +top_level_field: i32, + +test "top level fields" { + var instance = @This(){ + .top_level_field = 1234, + }; + instance.top_level_field += 1; + try expectEqual(@as(i32, 1235), instance.top_level_field); +} + +test "void struct fields" { + const foo = VoidStructFieldsFoo{ + .a = void{}, + .b = 1, + .c = void{}, + }; + try expect(foo.b == 1); + try expect(@sizeOf(VoidStructFieldsFoo) == 4); +} +const VoidStructFieldsFoo = struct { + a: void, + b: i32, + c: void, +}; + +test "structs" { + var foo: StructFoo = undefined; + @memset(@ptrCast([*]u8, &foo), 0, @sizeOf(StructFoo)); + foo.a += 1; + foo.b = foo.a == 1; + try testFoo(foo); + testMutation(&foo); + try expect(foo.c == 100); +} +const StructFoo = struct { + a: i32, + b: bool, + c: f32, +}; +fn testFoo(foo: StructFoo) !void { + try expect(foo.b); +} +fn testMutation(foo: *StructFoo) void { + foo.c = 100; +} + +const Node = struct { + val: Val, + next: *Node, +}; + +const Val = struct { + x: i32, +}; + +test "struct point to self" { + var root: Node = undefined; + root.val.x = 1; + + var node: Node = undefined; + node.next = &root; + node.val.x = 2; + + root.next = &node; + + try expect(node.next.next.next.val.x == 1); +} + +test "struct byval assign" { + var foo1: StructFoo = undefined; + var foo2: StructFoo = undefined; + + foo1.a = 1234; + foo2.a = 0; + try expect(foo2.a == 0); + foo2 = foo1; + try expect(foo2.a == 1234); +} + +fn structInitializer() void { + const val = Val{ .x = 42 }; + try expect(val.x == 42); +} + +test "fn call of struct field" { + const Foo = struct { + ptr: fn () i32, + }; + const S = struct { + fn aFunc() i32 { + return 13; + } + + fn callStructField(foo: Foo) i32 { + return foo.ptr(); + } + }; + + try expect(S.callStructField(Foo{ .ptr = S.aFunc }) == 13); +} + +test "store member function in variable" { + const instance = MemberFnTestFoo{ .x = 1234 }; + const memberFn = MemberFnTestFoo.member; + const result = memberFn(instance); + try expect(result == 1234); +} +const MemberFnTestFoo = struct { + x: i32, + fn member(foo: MemberFnTestFoo) i32 { + return foo.x; + } +}; + +test "call member function directly" { + const instance = MemberFnTestFoo{ .x = 1234 }; + const result = MemberFnTestFoo.member(instance); + try expect(result == 1234); +} + +test "member functions" { + const r = MemberFnRand{ .seed = 1234 }; + try expect(r.getSeed() == 1234); +} +const MemberFnRand = struct { + seed: u32, + pub fn getSeed(r: *const MemberFnRand) u32 { + return r.seed; + } +}; + +test "return struct byval from function" { + const bar = makeBar2(1234, 5678); + try expect(bar.y == 5678); +} +const Bar = struct { + x: i32, + y: i32, +}; +fn makeBar2(x: i32, y: i32) Bar { + return Bar{ + .x = x, + .y = y, + }; +} + +test "empty struct method call" { + const es = EmptyStruct{}; + try expect(es.method() == 1234); +} +const EmptyStruct = struct { + fn method(es: *const EmptyStruct) i32 { + _ = es; + return 1234; + } +}; + +test "return empty struct from fn" { + _ = testReturnEmptyStructFromFn(); +} +const EmptyStruct2 = struct {}; +fn testReturnEmptyStructFromFn() EmptyStruct2 { + return EmptyStruct2{}; +} + +test "pass slice of empty struct to fn" { + try expect(testPassSliceOfEmptyStructToFn(&[_]EmptyStruct2{EmptyStruct2{}}) == 1); +} +fn testPassSliceOfEmptyStructToFn(slice: []const EmptyStruct2) usize { + return slice.len; +} + +const APackedStruct = packed struct { + x: u8, + y: u8, +}; + +test "packed struct" { + var foo = APackedStruct{ + .x = 1, + .y = 2, + }; + foo.y += 1; + const four = foo.x + foo.y; + try expect(four == 4); +} + +const BitField1 = packed struct { + a: u3, + b: u3, + c: u2, +}; + +const bit_field_1 = BitField1{ + .a = 1, + .b = 2, + .c = 3, +}; + +test "bit field access" { + var data = bit_field_1; + try expect(getA(&data) == 1); + try expect(getB(&data) == 2); + try expect(getC(&data) == 3); + comptime try expect(@sizeOf(BitField1) == 1); + + data.b += 1; + try expect(data.b == 3); + + data.a += 1; + try expect(data.a == 2); + try expect(data.b == 3); +} + +fn getA(data: *const BitField1) u3 { + return data.a; +} + +fn getB(data: *const BitField1) u3 { + return data.b; +} + +fn getC(data: *const BitField1) u2 { + return data.c; +} + +const Foo24Bits = packed struct { + field: u24, +}; +const Foo96Bits = packed struct { + a: u24, + b: u24, + c: u24, + d: u24, +}; + +test "packed struct 24bits" { + comptime { + try expect(@sizeOf(Foo24Bits) == 4); + if (@sizeOf(usize) == 4) { + try expect(@sizeOf(Foo96Bits) == 12); + } else { + try expect(@sizeOf(Foo96Bits) == 16); + } + } + + var value = Foo96Bits{ + .a = 0, + .b = 0, + .c = 0, + .d = 0, + }; + value.a += 1; + try expect(value.a == 1); + try expect(value.b == 0); + try expect(value.c == 0); + try expect(value.d == 0); + + value.b += 1; + try expect(value.a == 1); + try expect(value.b == 1); + try expect(value.c == 0); + try expect(value.d == 0); + + value.c += 1; + try expect(value.a == 1); + try expect(value.b == 1); + try expect(value.c == 1); + try expect(value.d == 0); + + value.d += 1; + try expect(value.a == 1); + try expect(value.b == 1); + try expect(value.c == 1); + try expect(value.d == 1); +} + +const Foo32Bits = packed struct { + field: u24, + pad: u8, +}; + +const FooArray24Bits = packed struct { + a: u16, + b: [2]Foo32Bits, + c: u16, +}; + +// TODO revisit this test when doing https://github.com/ziglang/zig/issues/1512 +test "packed array 24bits" { + comptime { + try expect(@sizeOf([9]Foo32Bits) == 9 * 4); + try expect(@sizeOf(FooArray24Bits) == 2 + 2 * 4 + 2); + } + + var bytes = [_]u8{0} ** (@sizeOf(FooArray24Bits) + 1); + bytes[bytes.len - 1] = 0xaa; + const ptr = &std.mem.bytesAsSlice(FooArray24Bits, bytes[0 .. bytes.len - 1])[0]; + try expect(ptr.a == 0); + try expect(ptr.b[0].field == 0); + try expect(ptr.b[1].field == 0); + try expect(ptr.c == 0); + + ptr.a = maxInt(u16); + try expect(ptr.a == maxInt(u16)); + try expect(ptr.b[0].field == 0); + try expect(ptr.b[1].field == 0); + try expect(ptr.c == 0); + + ptr.b[0].field = maxInt(u24); + try expect(ptr.a == maxInt(u16)); + try expect(ptr.b[0].field == maxInt(u24)); + try expect(ptr.b[1].field == 0); + try expect(ptr.c == 0); + + ptr.b[1].field = maxInt(u24); + try expect(ptr.a == maxInt(u16)); + try expect(ptr.b[0].field == maxInt(u24)); + try expect(ptr.b[1].field == maxInt(u24)); + try expect(ptr.c == 0); + + ptr.c = maxInt(u16); + try expect(ptr.a == maxInt(u16)); + try expect(ptr.b[0].field == maxInt(u24)); + try expect(ptr.b[1].field == maxInt(u24)); + try expect(ptr.c == maxInt(u16)); + + try expect(bytes[bytes.len - 1] == 0xaa); +} + +const FooStructAligned = packed struct { + a: u8, + b: u8, +}; + +const FooArrayOfAligned = packed struct { + a: [2]FooStructAligned, +}; + +test "aligned array of packed struct" { + comptime { + try expect(@sizeOf(FooStructAligned) == 2); + try expect(@sizeOf(FooArrayOfAligned) == 2 * 2); + } + + var bytes = [_]u8{0xbb} ** @sizeOf(FooArrayOfAligned); + const ptr = &std.mem.bytesAsSlice(FooArrayOfAligned, bytes[0..])[0]; + + try expect(ptr.a[0].a == 0xbb); + try expect(ptr.a[0].b == 0xbb); + try expect(ptr.a[1].a == 0xbb); + try expect(ptr.a[1].b == 0xbb); +} + +test "runtime struct initialization of bitfield" { + const s1 = Nibbles{ + .x = x1, + .y = x1, + }; + const s2 = Nibbles{ + .x = @intCast(u4, x2), + .y = @intCast(u4, x2), + }; + + try expect(s1.x == x1); + try expect(s1.y == x1); + try expect(s2.x == @intCast(u4, x2)); + try expect(s2.y == @intCast(u4, x2)); +} + +var x1 = @as(u4, 1); +var x2 = @as(u8, 2); + +const Nibbles = packed struct { + x: u4, + y: u4, +}; + +const Bitfields = packed struct { + f1: u16, + f2: u16, + f3: u8, + f4: u8, + f5: u4, + f6: u4, + f7: u8, +}; + +test "native bit field understands endianness" { + var all: u64 = if (native_endian != .Little) + 0x1111222233445677 + else + 0x7765443322221111; + var bytes: [8]u8 = undefined; + @memcpy(&bytes, @ptrCast([*]u8, &all), 8); + var bitfields = @ptrCast(*Bitfields, &bytes).*; + + try expect(bitfields.f1 == 0x1111); + try expect(bitfields.f2 == 0x2222); + try expect(bitfields.f3 == 0x33); + try expect(bitfields.f4 == 0x44); + try expect(bitfields.f5 == 0x5); + try expect(bitfields.f6 == 0x6); + try expect(bitfields.f7 == 0x77); +} + +test "align 1 field before self referential align 8 field as slice return type" { + const result = alloc(Expr); + try expect(result.len == 0); +} + +const Expr = union(enum) { + Literal: u8, + Question: *Expr, +}; + +fn alloc(comptime T: type) []T { + return &[_]T{}; +} + +test "call method with mutable reference to struct with no fields" { + const S = struct { + fn doC(s: *const @This()) bool { + _ = s; + return true; + } + fn do(s: *@This()) bool { + _ = s; + return true; + } + }; + + var s = S{}; + try expect(S.doC(&s)); + try expect(s.doC()); + try expect(S.do(&s)); + try expect(s.do()); +} + +test "implicit cast packed struct field to const ptr" { + const LevelUpMove = packed struct { + move_id: u9, + level: u7, + + fn toInt(value: u7) u7 { + return value; + } + }; + + var lup: LevelUpMove = undefined; + lup.level = 12; + const res = LevelUpMove.toInt(lup.level); + try expect(res == 12); +} + +test "pointer to packed struct member in a stack variable" { + const S = packed struct { + a: u2, + b: u2, + }; + + var s = S{ .a = 2, .b = 0 }; + var b_ptr = &s.b; + try expect(s.b == 0); + b_ptr.* = 2; + try expect(s.b == 2); +} + +test "non-byte-aligned array inside packed struct" { + const Foo = packed struct { + a: bool, + b: [0x16]u8, + }; + const S = struct { + fn bar(slice: []const u8) !void { + try expectEqualSlices(u8, slice, "abcdefghijklmnopqurstu"); + } + fn doTheTest() !void { + var foo = Foo{ + .a = true, + .b = "abcdefghijklmnopqurstu".*, + }; + const value = foo.b; + try bar(&value); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "packed struct with u0 field access" { + const S = packed struct { + f0: u0, + }; + var s = S{ .f0 = 0 }; + comptime try expect(s.f0 == 0); +} + +const S0 = struct { + bar: S1, + + pub const S1 = struct { + value: u8, + }; + + fn init() @This() { + return S0{ .bar = S1{ .value = 123 } }; + } +}; + +var g_foo: S0 = S0.init(); + +test "access to global struct fields" { + g_foo.bar.value = 42; + try expect(g_foo.bar.value == 42); +} + +test "packed struct with fp fields" { + const S = packed struct { + data: [3]f32, + + pub fn frob(self: *@This()) void { + self.data[0] += self.data[1] + self.data[2]; + self.data[1] += self.data[0] + self.data[2]; + self.data[2] += self.data[0] + self.data[1]; + } + }; + + var s: S = undefined; + s.data[0] = 1.0; + s.data[1] = 2.0; + s.data[2] = 3.0; + s.frob(); + try expectEqual(@as(f32, 6.0), s.data[0]); + try expectEqual(@as(f32, 11.0), s.data[1]); + try expectEqual(@as(f32, 20.0), s.data[2]); +} + +test "use within struct scope" { + const S = struct { + usingnamespace struct { + pub fn inner() i32 { + return 42; + } + }; + }; + try expectEqual(@as(i32, 42), S.inner()); +} + +test "default struct initialization fields" { + const S = struct { + a: i32 = 1234, + b: i32, + }; + const x = S{ + .b = 5, + }; + var five: i32 = 5; + const y = S{ + .b = five, + }; + if (x.a + x.b != 1239) { + @compileError("it should be comptime known"); + } + try expectEqual(y, x); + try expectEqual(1239, x.a + x.b); +} + +test "fn with C calling convention returns struct by value" { + const S = struct { + fn entry() !void { + var x = makeBar(10); + try expectEqual(@as(i32, 10), x.handle); + } + + const ExternBar = extern struct { + handle: i32, + }; + + fn makeBar(t: i32) callconv(.C) ExternBar { + return ExternBar{ + .handle = t, + }; + } + }; + try S.entry(); + comptime try S.entry(); +} + +test "for loop over pointers to struct, getting field from struct pointer" { + const S = struct { + const Foo = struct { + name: []const u8, + }; + + var ok = true; + + fn eql(a: []const u8) bool { + _ = a; + return true; + } + + const ArrayList = struct { + fn toSlice(self: *ArrayList) []*Foo { + _ = self; + return @as([*]*Foo, undefined)[0..0]; + } + }; + + fn doTheTest() !void { + var objects: ArrayList = undefined; + + for (objects.toSlice()) |obj| { + if (eql(obj.name)) { + ok = false; + } + } + + try expect(ok); + } + }; + try S.doTheTest(); +} + +test "zero-bit field in packed struct" { + const S = packed struct { + x: u10, + y: void, + }; + var x: S = undefined; + _ = x; +} + +test "struct field init with catch" { + const S = struct { + fn doTheTest() !void { + var x: anyerror!isize = 1; + var req = Foo{ + .field = x catch undefined, + }; + try expect(req.field == 1); + } + + pub const Foo = extern struct { + field: isize, + }; + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "packed struct with non-ABI-aligned field" { + const S = packed struct { + x: u9, + y: u183, + }; + var s: S = undefined; + s.x = 1; + s.y = 42; + try expect(s.x == 1); + try expect(s.y == 42); +} + +test "non-packed struct with u128 entry in union" { + const U = union(enum) { + Num: u128, + Void, + }; + + const S = struct { + f1: U, + f2: U, + }; + + var sx: S = undefined; + var s = &sx; + try std.testing.expect(@ptrToInt(&s.f2) - @ptrToInt(&s.f1) == @offsetOf(S, "f2")); + var v2 = U{ .Num = 123 }; + s.f2 = v2; + try std.testing.expect(s.f2.Num == 123); +} + +test "packed struct field passed to generic function" { + const S = struct { + const P = packed struct { + b: u5, + g: u5, + r: u5, + a: u1, + }; + + fn genericReadPackedField(ptr: anytype) u5 { + return ptr.*; + } + }; + + var p: S.P = undefined; + p.b = 29; + var loaded = S.genericReadPackedField(&p.b); + try expect(loaded == 29); +} + +test "anonymous struct literal syntax" { + const S = struct { + const Point = struct { + x: i32, + y: i32, + }; + + fn doTheTest() !void { + var p: Point = .{ + .x = 1, + .y = 2, + }; + try expect(p.x == 1); + try expect(p.y == 2); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "fully anonymous struct" { + const S = struct { + fn doTheTest() !void { + try dump(.{ + .int = @as(u32, 1234), + .float = @as(f64, 12.34), + .b = true, + .s = "hi", + }); + } + fn dump(args: anytype) !void { + try expect(args.int == 1234); + try expect(args.float == 12.34); + try expect(args.b); + try expect(args.s[0] == 'h'); + try expect(args.s[1] == 'i'); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "fully anonymous list literal" { + const S = struct { + fn doTheTest() !void { + try dump(.{ @as(u32, 1234), @as(f64, 12.34), true, "hi" }); + } + fn dump(args: anytype) !void { + try expect(args.@"0" == 1234); + try expect(args.@"1" == 12.34); + try expect(args.@"2"); + try expect(args.@"3"[0] == 'h'); + try expect(args.@"3"[1] == 'i'); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "anonymous struct literal assigned to variable" { + var vec = .{ @as(i32, 22), @as(i32, 55), @as(i32, 99) }; + try expect(vec.@"0" == 22); + try expect(vec.@"1" == 55); + try expect(vec.@"2" == 99); +} + +test "struct with var field" { + const Point = struct { + x: anytype, + y: anytype, + }; + const pt = Point{ + .x = 1, + .y = 2, + }; + try expect(pt.x == 1); + try expect(pt.y == 2); +} + +test "comptime struct field" { + const T = struct { + a: i32, + comptime b: i32 = 1234, + }; + + var foo: T = undefined; + comptime try expect(foo.b == 1234); +} + +test "anon struct literal field value initialized with fn call" { + const S = struct { + fn doTheTest() !void { + var x = .{foo()}; + try expectEqualSlices(u8, x[0], "hi"); + } + fn foo() []const u8 { + return "hi"; + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "self-referencing struct via array member" { + const T = struct { + children: [1]*@This(), + }; + var x: T = undefined; + x = T{ .children = .{&x} }; + try expect(x.children[0] == &x); +} + +test "struct with union field" { + const Value = struct { + ref: u32 = 2, + kind: union(enum) { + None: usize, + Bool: bool, + }, + }; + + var True = Value{ + .kind = .{ .Bool = true }, + }; + try expectEqual(@as(u32, 2), True.ref); + try 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; + try expect(y0.A == 123); + try expect(std.mem.eql(u8, y0.B, "foo")); + try expect(y0.C == {}); + try expect(y0.D.field == 1234); + try expect(y1.A == y); + try expect(std.mem.eql(u8, y1.B, "foo")); + try expect(y1.C == {}); + try expect(y1.D.field == 1234); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "type coercion of pointer to anon struct literal to pointer 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: *const S2 = t0; + var y1: *const S2 = t1; + try expect(y0.A == 123); + try expect(std.mem.eql(u8, y0.B, "foo")); + try expect(y0.C == {}); + try expect(y0.D.field == 1234); + try expect(y1.A == y); + try expect(std.mem.eql(u8, y1.B, "foo")); + try expect(y1.C == {}); + try expect(y1.D.field == 1234); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "packed struct with undefined initializers" { + const S = struct { + const P = packed struct { + a: u3, + _a: u3 = undefined, + b: u3, + _b: u3 = undefined, + c: u3, + _c: u3 = undefined, + }; + + fn doTheTest() !void { + var p: P = undefined; + p = P{ .a = 2, .b = 4, .c = 6 }; + // Make sure the compiler doesn't touch the unprefixed fields. + // Use expect since i386-linux doesn't like expectEqual + try expect(p.a == 2); + try expect(p.b == 4); + try expect(p.c == 6); + } + }; + + try S.doTheTest(); + comptime try S.doTheTest(); +} From 351e4f07cebc8299c107bd3de760efe17d184af7 Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 23 Sep 2021 06:19:08 +0200 Subject: [PATCH 101/160] big ints: 2s complement signed and + or fixes --- lib/std/math/big/int.zig | 180 ++++++++++++++++++++++++++++------ lib/std/math/big/int_test.zig | 83 +++++++++++++++- 2 files changed, 230 insertions(+), 33 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 8b9cc7168a..fa8a63b67d 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -570,26 +570,36 @@ pub const Mutable = struct { if (a.limbs.len >= b.limbs.len) { r.positive = llsignedor(r.limbs, a.limbs, a.positive, b.limbs, b.positive); - r.normalize(a.limbs.len); + r.normalize(if (b.positive) a.limbs.len else b.limbs.len); } else { r.positive = llsignedor(r.limbs, b.limbs, b.positive, a.limbs, a.positive); - r.normalize(b.limbs.len); + r.normalize(if (a.positive) b.limbs.len else a.limbs.len); } } - /// r = a & b + /// r = a & b under 2s complement semantics. /// r may alias with a or b. /// - /// Asserts that r has enough limbs to store the result. Upper bound is `math.min(a.limbs.len, b.limbs.len)`. + /// Asserts that r has enough limbs to store the result. + /// If a or b is positive, the upper bound is `math.min(a.limbs.len, b.limbs.len)`. + /// If a and b are negative, the upper bound is `math.max(a.limbs.len, b.limbs.len) + 1`. pub fn bitAnd(r: *Mutable, a: Const, b: Const) void { - if (a.limbs.len > b.limbs.len) { - lland(r.limbs[0..], a.limbs[0..a.limbs.len], b.limbs[0..b.limbs.len]); - r.normalize(b.limbs.len); - } else { - lland(r.limbs[0..], b.limbs[0..b.limbs.len], a.limbs[0..a.limbs.len]); - r.normalize(a.limbs.len); + // Trivial cases, llsignedand does not support zero. + if (a.eqZero()) { + r.copy(a); + return; + } else if (b.eqZero()) { + r.copy(b); + return; + } + + if (a.limbs.len >= b.limbs.len) { + r.positive = llsignedand(r.limbs, a.limbs, a.positive, b.limbs, b.positive); + r.normalize(if (a.positive or b.positive) b.limbs.len else a.limbs.len + 1); + } else { + r.positive = llsignedand(r.limbs, b.limbs, b.positive, a.limbs, a.positive); + r.normalize(if (a.positive or b.positive) a.limbs.len else b.limbs.len + 1); } - r.positive = a.positive and b.positive; } /// r = a ^ b under 2s complement semantics. @@ -1855,7 +1865,11 @@ pub const Managed = struct { /// r = a & b pub fn bitAnd(r: *Managed, a: Managed, b: Managed) !void { - try r.ensureCapacity(math.min(a.len(), b.len())); + const cap = if (a.isPositive() or b.isPositive()) + math.min(a.len(), b.len()) + else + math.max(a.len(), b.len()) + 1; + try r.ensureCapacity(cap); var m = r.toMutable(); m.bitAnd(a.toConst(), b.toConst()); r.setMetadata(m.positive, m.len); @@ -2244,13 +2258,19 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void { } } +// r = a | b with 2s complement semantics. +// r may alias. +// a and b must not be 0. +// Returns `true` when the result is positive. +// When b is positive, r requires at least `a.len` limbs of storage. +// When b is negative, r requires at least `b.len` limbs of storage. fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool { @setRuntimeSafety(debug_safety); assert(r.len >= a.len); assert(a.len >= b.len); if (a_positive and b_positive) { - // Trivial case, result is positive., + // Trivial case, result is positive. var i: usize = 0; while (i < b.len) : (i += 1) { r[i] = a[i] | b[i]; @@ -2261,12 +2281,12 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p return true; } else if (!a_positive and b_positive) { + // Result is negative. // r = (--a) | b // = ~(-a - 1) | b // = ~(-a - 1) | ~~b // = ~((-a - 1) & ~b) // = -(((-a - 1) & ~b) + 1) - // result is negative. var i: usize = 0; var a_borrow: u1 = 1; @@ -2280,25 +2300,29 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); } + // In order for r_carry to be nonzero at this point, ~b[i] would need to be + // all ones, which would require b[i] to be zero. This cannot be when + // b is normalized, so there cannot be a carry here. + // Also, x & ~b can only clear bits, so (x & ~b) <= x, meaning (-a - 1) + 1 never overflows. + assert(r_carry == 0); + // With b = 0, we get (-a - 1) & ~0 = -a - 1. - while (i < a.len) : (i += 1) { + // Note, if a_borrow is zero we do not need to compute anything for + // the higher limbs so we can early return here. + while (i < a.len and a_borrow == 1) : (i += 1) { a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &r[i])); - r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); } assert(a_borrow == 0); // a was 0. - // Can never overflow because a_borrow would need to equal 1. - assert(r_carry == 0); - return false; } else if (a_positive and !b_positive) { + // Result is negative. // r = a | (--b) // = a | ~(-b - 1) // = ~~a | ~(-b - 1) // = ~(~a & (-b - 1)) // = -((~a & (-b - 1)) + 1) - // result is negative. var i: usize = 0; var b_borrow: u1 = 1; @@ -2315,21 +2339,20 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p // b is at least 1, so this should never underflow. assert(b_borrow == 0); // b was 0 - // Can never overflow because in order for b_limb to be maxInt(Limb), - // b_borrow would need to equal 1. + // x & ~a can only clear bits, so (x & ~a) <= x, meaning (-b - 1) + 1 never overflows. assert(r_carry == 0); // With b = 0 and b_borrow = 0, we get ~a & (-0 - 0) = ~a & 0 = 0. - mem.set(Limb, r[i..a.len], 0); + // Omit setting the upper bytes, just deal with those when calling llsignedor. return false; } else { + // Result is negative. // r = (--a) | (--b) // = ~(-a - 1) | ~(-b - 1) // = ~((-a - 1) & (-b - 1)) // = -(~(~((-a - 1) & (-b - 1))) + 1) // = -((-a - 1) & (-b - 1) + 1) - // result is negative. var i: usize = 0; var a_borrow: u1 = 1; @@ -2352,22 +2375,119 @@ fn llsignedor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_p // Can never overflow because in order for b_limb to be maxInt(Limb), // b_borrow would need to equal 1. + + // x & y can only clear bits, meaning x & y <= x and x & y <= y. This implies that + // for x = a - 1 and y = b - 1, the +1 term would never cause an overflow. assert(r_carry == 0); // With b = 0 and b_borrow = 0 we get (-a - 1) & (-0 - 0) = (-a - 1) & 0 = 0. - mem.set(Limb, r[i..a.len], 0); + // Omit setting the upper bytes, just deal with those when calling llsignedor. return false; } } -fn lland(r: []Limb, a: []const Limb, b: []const Limb) void { +// r = a & b with 2s complement semantics. +// r may alias. +// a and b must not be 0. +// Returns `true` when the result is positive. +// When either or both of a and b are positive, r requires at least `b.len` limbs of storage. +// When both a and b are negative, r requires at least `a.limbs.len + 1` limbs of storage. +fn llsignedand(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool { @setRuntimeSafety(debug_safety); - assert(r.len >= b.len); + assert(a.len != 0 and b.len != 0); + assert(r.len >= a.len); assert(a.len >= b.len); - var i: usize = 0; - while (i < b.len) : (i += 1) { - r[i] = a[i] & b[i]; + if (a_positive and b_positive) { + // Trivial case, result is positive. + var i: usize = 0; + while (i < b.len) : (i += 1) { + r[i] = a[i] & b[i]; + } + + // With b = 0 we have a & 0 = 0, so the upper bytes are zero. + // Omit setting them here and simply discard them whenever + // llsignedand is called. + + return true; + } else if (!a_positive and b_positive) { + // Result is positive. + // r = (--a) & b + // = ~(-a - 1) & b + + var i: usize = 0; + var a_borrow: u1 = 1; + + while (i < b.len) : (i += 1) { + var a_limb: Limb = undefined; + a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb)); + r[i] = ~a_limb & b[i]; + } + + // With b = 0 we have ~(a - 1) & 0 = 0, so the upper bytes are zero. + // Omit setting them here and simply discard them whenever + // llsignedand is called. + + return true; + } else if (a_positive and !b_positive) { + // Result is positive. + // r = a & (--b) + // = a & ~(-b - 1) + + var i: usize = 0; + var b_borrow: u1 = 1; + + while (i < b.len) : (i += 1) { + var a_limb: Limb = undefined; + b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &a_limb)); + r[i] = a[i] & ~a_limb; + } + + assert(b_borrow == 0); // b was 0 + + // With b = 0 and b_borrow = 0 we have a & ~(-0 - 0) = a & 0 = 0, so + // the upper bytes are zero. Omit setting them here and simply discard + // them whenever llsignedand is called. + + return true; + } else { + // Result is negative. + // r = (--a) & (--b) + // = ~(-a - 1) & ~(-b - 1) + // = ~((-a - 1) | (-b - 1)) + // = -(((-a - 1) | (-b - 1)) + 1) + + var i: usize = 0; + var a_borrow: u1 = 1; + var b_borrow: u1 = 1; + var r_carry: u1 = 1; + + while (i < b.len) : (i += 1) { + var a_limb: Limb = undefined; + a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &a_limb)); + + var b_limb: Limb = undefined; + b_borrow = @boolToInt(@subWithOverflow(Limb, b[i], b_borrow, &b_limb)); + + r[i] = a_limb | b_limb; + r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); + } + + // b is at least 1, so this should never underflow. + assert(b_borrow == 0); // b was 0 + + // With b = 0 and b_borrow = 0 we get (-a - 1) | (-0 - 0) = (-a - 1) | 0 = -a - 1. + while (i < a.len) : (i += 1) { + a_borrow = @boolToInt(@subWithOverflow(Limb, a[i], a_borrow, &r[i])); + r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); + } + + assert(a_borrow == 0); // a was 0. + + // The final addition can overflow here, so we need to keep that in mind. + r[i] = r_carry; + + return false; } } diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index b9f7c5c116..7b5e14b808 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -1365,6 +1365,83 @@ test "big.int bitwise and multi-limb" { try testing.expect((try a.to(u128)) == 0); } +test "big.int bitwise and negative-positive simple" { + var a = try Managed.initSet(testing.allocator, -0xffffffff11111111); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, 0xeeeeeeee22222222); + defer b.deinit(); + + try a.bitAnd(a, b); + + try testing.expect((try a.to(u64)) == 0x22222222); +} + +test "big.int bitwise and negative-positive multi-limb" { + var a = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, maxInt(Limb)); + defer b.deinit(); + + try a.bitAnd(a, b); + + try testing.expect(a.eqZero()); +} + +test "big.int bitwise and positive-negative simple" { + var a = try Managed.initSet(testing.allocator, 0xffffffff11111111); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -0xeeeeeeee22222222); + defer b.deinit(); + + try a.bitAnd(a, b); + + try testing.expect((try a.to(u64)) == 0x1111111111111110); +} + +test "big.int bitwise and positive-negative multi-limb" { + var a = try Managed.initSet(testing.allocator, maxInt(Limb)); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1); + defer b.deinit(); + + try a.bitAnd(a, b); + + try testing.expect(a.eqZero()); +} + +test "big.int bitwise and negative-negative simple" { + var a = try Managed.initSet(testing.allocator, -0xffffffff11111111); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -0xeeeeeeee22222222); + defer b.deinit(); + + try a.bitAnd(a, b); + + try testing.expect((try a.to(i128)) == -0xffffffff33333332); +} + +test "big.int bitwise and negative-negative multi-limb" { + var a = try Managed.initSet(testing.allocator, -maxInt(Limb) - 1); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -maxInt(Limb) - 2); + defer b.deinit(); + + try a.bitAnd(a, b); + + try testing.expect((try a.to(i128)) == -maxInt(Limb) * 2 - 2); +} + +test "big.int bitwise and negative overflow" { + var a = try Managed.initSet(testing.allocator, -maxInt(Limb)); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, -2); + defer b.deinit(); + + try a.bitAnd(a, b); + + try testing.expect((try a.to(SignedDoubleLimb)) == -maxInt(Limb) - 1); +} + test "big.int bitwise xor simple" { var a = try Managed.initSet(testing.allocator, 0xffffffff11111111); defer a.deinit(); @@ -1521,14 +1598,14 @@ test "big.int bitwise or positive-negative multi-limb" { } test "big.int bitwise or negative-negative simple" { - var a = try Managed.initSet(testing.allocator, -0x0fffffff11111111); + var a = try Managed.initSet(testing.allocator, -0xffffffff11111111); defer a.deinit(); - var b = try Managed.initSet(testing.allocator, -0x0eeeeeee22222222); + var b = try Managed.initSet(testing.allocator, -0xeeeeeeee22222222); defer b.deinit(); try a.bitOr(a, b); - try testing.expect((try a.to(i64)) == -0xeeeeeee00000001); + try testing.expect((try a.to(i128)) == -0xeeeeeeee00000001); } test "big.int bitwise or negative-negative multi-limb" { From f615648d7bdcb5c7ed38ad15169a8fa90bd86ca0 Mon Sep 17 00:00:00 2001 From: Hadrien Dorio Date: Wed, 22 Sep 2021 23:58:50 +0200 Subject: [PATCH 102/160] stage2: enhance `zig init-lib` and `zig init-exe` Stop `src/main.zig` from being overwritten. --- src/main.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main.zig b/src/main.zig index 74bf45b62c..213b9506e7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2800,6 +2800,12 @@ pub fn cmdInit( error.FileNotFound => {}, else => fatal("unable to test existence of build.zig: {s}\n", .{@errorName(err)}), } + if (fs.cwd().access("src" ++ s ++ "main.zig", .{})) |_| { + fatal("existing src" ++ s ++ "main.zig file would be overwritten", .{}); + } else |err| switch (err) { + error.FileNotFound => {}, + else => fatal("unable to test existence of src" ++ s ++ "main.zig: {s}\n", .{@errorName(err)}), + } var src_dir = try fs.cwd().makeOpenPath("src", .{}); defer src_dir.close(); From cd3dcc225b52be981b52b71dcdb34ba176f4081b Mon Sep 17 00:00:00 2001 From: Robin Voetter Date: Thu, 23 Sep 2021 06:22:18 +0200 Subject: [PATCH 103/160] big ints: only write xor overflow if required --- lib/std/math/big/int.zig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index fa8a63b67d..b7ea284004 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -2495,6 +2495,8 @@ fn llsignedand(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_ // r may alias. // a and b must not be -0. // Returns `true` when the result is positive. +// If the sign of a and b is equal, then r requires at least `max(a.len, b.len)` limbs are required. +// Otherwise, r requires at least `max(a.len, b.len) + 1` limbs. fn llsignedxor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_positive: bool) bool { @setRuntimeSafety(debug_safety); assert(a.len != 0 and b.len != 0); @@ -2538,7 +2540,12 @@ fn llsignedxor(r: []Limb, a: []const Limb, a_positive: bool, b: []const Limb, b_ r_carry = @boolToInt(@addWithOverflow(Limb, r[i], r_carry, &r[i])); } - r[i] = r_carry; + // If both inputs don't share the same sign, an extra limb is required. + if (a_positive != b_positive) { + r[i] = r_carry; + } else { + assert(r_carry == 0); + } assert(a_borrow == 0); assert(b_borrow == 0); From a0a847f2e40046387e2e2b8a0b8dae9cb27ca22a Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Thu, 23 Sep 2021 12:17:06 -0500 Subject: [PATCH 104/160] Stage2: Implement comptime closures and the This builtin (#9823) --- src/AstGen.zig | 300 +++++++++++++++++++++++++++++++++-------- src/Module.zig | 141 ++++++++++++++++--- src/Sema.zig | 263 +++++++++++++++++++++++++++--------- src/Zir.zig | 99 +++++++++----- src/print_zir.zig | 53 +++++--- test/behavior.zig | 21 +-- test/behavior/this.zig | 9 +- 7 files changed, 670 insertions(+), 216 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index b33c5aad40..14ad6c94a7 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -124,7 +124,7 @@ pub fn generate(gpa: *Allocator, tree: Ast) Allocator.Error!Zir { container_decl, .Auto, )) |struct_decl_ref| { - astgen.extra.items[@enumToInt(Zir.ExtraIndex.main_struct)] = @enumToInt(struct_decl_ref); + assert(refToIndex(struct_decl_ref).? == 0); } else |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => {}, // Handled via compile_errors below. @@ -2078,9 +2078,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .union_init_ptr, .field_type, .field_type_ref, - .opaque_decl, - .opaque_decl_anon, - .opaque_decl_func, .error_set_decl, .error_set_decl_anon, .error_set_decl_func, @@ -2162,6 +2159,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .await_nosuspend, .ret_err_value_code, .extended, + .closure_get, => break :b false, // ZIR instructions that are always `noreturn`. @@ -2205,6 +2203,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .set_cold, .set_float_mode, .set_runtime_safety, + .closure_capture, => break :b true, } } else switch (maybe_unused_result) { @@ -3534,8 +3533,9 @@ fn structDeclInner( container_decl: Ast.full.ContainerDecl, layout: std.builtin.TypeInfo.ContainerLayout, ) InnerError!Zir.Inst.Ref { + const decl_inst = try gz.reserveInstructionIndex(); + if (container_decl.ast.members.len == 0) { - const decl_inst = try gz.reserveInstructionIndex(); try gz.setStruct(decl_inst, .{ .src_node = node, .layout = layout, @@ -3553,11 +3553,19 @@ fn structDeclInner( const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); + var namespace: Scope.Namespace = .{ + .parent = scope, + .node = node, + .inst = decl_inst, + .declaring_gz = gz, + }; + defer namespace.deinit(gpa); + // The struct_decl instruction introduces a scope in which the decls of the struct // are in scope, so that field types, alignments, and default value expressions // can refer to decls within the struct itself. var block_scope: GenZir = .{ - .parent = scope, + .parent = &namespace.base, .decl_node_index = node, .decl_line = gz.calcLine(node), .astgen = astgen, @@ -3566,9 +3574,6 @@ fn structDeclInner( }; defer block_scope.instructions.deinit(gpa); - var namespace: Scope.Namespace = .{ .parent = scope, .node = node }; - defer namespace.decls.deinit(gpa); - try astgen.scanDecls(&namespace, container_decl.ast.members); var wip_decls: WipDecls = .{}; @@ -3773,7 +3778,6 @@ fn structDeclInner( } } - const decl_inst = try gz.reserveInstructionIndex(); if (block_scope.instructions.items.len != 0) { _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); } @@ -3787,11 +3791,18 @@ fn structDeclInner( .known_has_bits = known_has_bits, }); - try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len + - @boolToInt(field_index != 0) + fields_data.items.len + + // zig fmt: off + try astgen.extra.ensureUnusedCapacity(gpa, + bit_bag.items.len + + @boolToInt(wip_decls.decl_index != 0) + + wip_decls.payload.items.len + block_scope.instructions.items.len + - wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) + - wip_decls.payload.items.len); + wip_decls.bit_bag.items.len + + @boolToInt(field_index != 0) + + fields_data.items.len + ); + // zig fmt: on + astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty. if (wip_decls.decl_index != 0) { astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag); @@ -3818,17 +3829,27 @@ fn unionDeclInner( arg_node: Ast.Node.Index, have_auto_enum: bool, ) InnerError!Zir.Inst.Ref { + const decl_inst = try gz.reserveInstructionIndex(); + const astgen = gz.astgen; const gpa = astgen.gpa; const tree = astgen.tree; const node_tags = tree.nodes.items(.tag); const node_datas = tree.nodes.items(.data); + var namespace: Scope.Namespace = .{ + .parent = scope, + .node = node, + .inst = decl_inst, + .declaring_gz = gz, + }; + defer namespace.deinit(gpa); + // The union_decl instruction introduces a scope in which the decls of the union // are in scope, so that field types, alignments, and default value expressions // can refer to decls within the union itself. var block_scope: GenZir = .{ - .parent = scope, + .parent = &namespace.base, .decl_node_index = node, .decl_line = gz.calcLine(node), .astgen = astgen, @@ -3837,13 +3858,10 @@ fn unionDeclInner( }; defer block_scope.instructions.deinit(gpa); - var namespace: Scope.Namespace = .{ .parent = scope, .node = node }; - defer namespace.decls.deinit(gpa); - try astgen.scanDecls(&namespace, members); const arg_inst: Zir.Inst.Ref = if (arg_node != 0) - try typeExpr(gz, &namespace.base, arg_node) + try typeExpr(&block_scope, &namespace.base, arg_node) else .none; @@ -4056,7 +4074,6 @@ fn unionDeclInner( } } - const decl_inst = try gz.reserveInstructionIndex(); if (block_scope.instructions.items.len != 0) { _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); } @@ -4071,11 +4088,18 @@ fn unionDeclInner( .auto_enum_tag = have_auto_enum, }); - try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len + - 1 + fields_data.items.len + + // zig fmt: off + try astgen.extra.ensureUnusedCapacity(gpa, + bit_bag.items.len + + @boolToInt(wip_decls.decl_index != 0) + + wip_decls.payload.items.len + block_scope.instructions.items.len + - wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) + - wip_decls.payload.items.len); + wip_decls.bit_bag.items.len + + 1 + // cur_bit_bag + fields_data.items.len + ); + // zig fmt: on + astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty. if (wip_decls.decl_index != 0) { astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag); @@ -4238,10 +4262,20 @@ fn containerDecl( // how structs are handled above. const nonexhaustive = counts.nonexhaustive_node != 0; + const decl_inst = try gz.reserveInstructionIndex(); + + var namespace: Scope.Namespace = .{ + .parent = scope, + .node = node, + .inst = decl_inst, + .declaring_gz = gz, + }; + defer namespace.deinit(gpa); + // The enum_decl instruction introduces a scope in which the decls of the enum // are in scope, so that tag values can refer to decls within the enum itself. var block_scope: GenZir = .{ - .parent = scope, + .parent = &namespace.base, .decl_node_index = node, .decl_line = gz.calcLine(node), .astgen = astgen, @@ -4250,13 +4284,10 @@ fn containerDecl( }; defer block_scope.instructions.deinit(gpa); - var namespace: Scope.Namespace = .{ .parent = scope, .node = node }; - defer namespace.decls.deinit(gpa); - try astgen.scanDecls(&namespace, container_decl.ast.members); const arg_inst: Zir.Inst.Ref = if (container_decl.ast.arg != 0) - try comptimeExpr(gz, &namespace.base, .{ .ty = .type_type }, container_decl.ast.arg) + try comptimeExpr(&block_scope, &namespace.base, .{ .ty = .type_type }, container_decl.ast.arg) else .none; @@ -4451,7 +4482,6 @@ fn containerDecl( } } - const decl_inst = try gz.reserveInstructionIndex(); if (block_scope.instructions.items.len != 0) { _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value); } @@ -4465,11 +4495,18 @@ fn containerDecl( .decls_len = @intCast(u32, wip_decls.decl_index), }); - try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len + - 1 + fields_data.items.len + + // zig fmt: off + try astgen.extra.ensureUnusedCapacity(gpa, + bit_bag.items.len + + @boolToInt(wip_decls.decl_index != 0) + + wip_decls.payload.items.len + block_scope.instructions.items.len + - wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) + - wip_decls.payload.items.len); + wip_decls.bit_bag.items.len + + 1 + // cur_bit_bag + fields_data.items.len + ); + // zig fmt: on + astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty. if (wip_decls.decl_index != 0) { astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag); @@ -4486,8 +4523,15 @@ fn containerDecl( .keyword_opaque => { assert(container_decl.ast.arg == 0); - var namespace: Scope.Namespace = .{ .parent = scope, .node = node }; - defer namespace.decls.deinit(gpa); + const decl_inst = try gz.reserveInstructionIndex(); + + var namespace: Scope.Namespace = .{ + .parent = scope, + .node = node, + .inst = decl_inst, + .declaring_gz = gz, + }; + defer namespace.deinit(gpa); try astgen.scanDecls(&namespace, container_decl.ast.members); @@ -4625,21 +4669,20 @@ fn containerDecl( wip_decls.cur_bit_bag >>= @intCast(u5, empty_slot_count * WipDecls.bits_per_field); } } - const tag: Zir.Inst.Tag = switch (gz.anon_name_strategy) { - .parent => .opaque_decl, - .anon => .opaque_decl_anon, - .func => .opaque_decl_func, - }; - const decl_inst = try gz.addBlock(tag, node); - try gz.instructions.append(gpa, decl_inst); - try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.OpaqueDecl).Struct.fields.len + - wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) + - wip_decls.payload.items.len); - const zir_datas = astgen.instructions.items(.data); - zir_datas[decl_inst].pl_node.payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.OpaqueDecl{ + try gz.setOpaque(decl_inst, .{ + .src_node = node, .decls_len = @intCast(u32, wip_decls.decl_index), }); + + // zig fmt: off + try astgen.extra.ensureUnusedCapacity(gpa, + wip_decls.bit_bag.items.len + + @boolToInt(wip_decls.decl_index != 0) + + wip_decls.payload.items.len + ); + // zig fmt: on + astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty. if (wip_decls.decl_index != 0) { astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag); @@ -6380,6 +6423,7 @@ fn identifier( const astgen = gz.astgen; const tree = astgen.tree; + const gpa = astgen.gpa; const main_tokens = tree.nodes.items(.main_token); const ident_token = main_tokens[ident]; @@ -6426,16 +6470,28 @@ fn identifier( const name_str_index = try astgen.identAsString(ident_token); var s = scope; var found_already: ?Ast.Node.Index = null; // we have found a decl with the same name already - var hit_namespace: Ast.Node.Index = 0; + var num_namespaces_out: u32 = 0; + var capturing_namespace: ?*Scope.Namespace = null; while (true) switch (s.tag) { .local_val => { const local_val = s.cast(Scope.LocalVal).?; if (local_val.name == name_str_index) { - local_val.used = true; // Locals cannot shadow anything, so we do not need to look for ambiguous // references in this case. - return rvalue(gz, rl, local_val.inst, ident); + local_val.used = true; + + const value_inst = try tunnelThroughClosure( + gz, + ident, + num_namespaces_out, + capturing_namespace, + local_val.inst, + local_val.token_src, + gpa, + ); + + return rvalue(gz, rl, value_inst, ident); } s = local_val.parent; }, @@ -6443,16 +6499,29 @@ fn identifier( const local_ptr = s.cast(Scope.LocalPtr).?; if (local_ptr.name == name_str_index) { local_ptr.used = true; - if (hit_namespace != 0 and !local_ptr.maybe_comptime) { + + // Can't close over a runtime variable + if (num_namespaces_out != 0 and !local_ptr.maybe_comptime) { return astgen.failNodeNotes(ident, "mutable '{s}' not accessible from here", .{ident_name}, &.{ try astgen.errNoteTok(local_ptr.token_src, "declared mutable here", .{}), - try astgen.errNoteNode(hit_namespace, "crosses namespace boundary here", .{}), + try astgen.errNoteNode(capturing_namespace.?.node, "crosses namespace boundary here", .{}), }); } + + const ptr_inst = try tunnelThroughClosure( + gz, + ident, + num_namespaces_out, + capturing_namespace, + local_ptr.ptr, + local_ptr.token_src, + gpa, + ); + switch (rl) { - .ref, .none_or_ref => return local_ptr.ptr, + .ref, .none_or_ref => return ptr_inst, else => { - const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident); + const loaded = try gz.addUnNode(.load, ptr_inst, ident); return rvalue(gz, rl, loaded, ident); }, } @@ -6473,7 +6542,8 @@ fn identifier( // We found a match but must continue looking for ambiguous references to decls. found_already = i; } - hit_namespace = ns.node; + num_namespaces_out += 1; + capturing_namespace = ns; s = ns.parent; }, .top => break, @@ -6493,6 +6563,37 @@ fn identifier( } } +/// Adds a capture to a namespace, if needed. +/// Returns the index of the closure_capture instruction. +fn tunnelThroughClosure( + gz: *GenZir, + inner_ref_node: Ast.Node.Index, + num_tunnels: u32, + ns: ?*Scope.Namespace, + value: Zir.Inst.Ref, + token: Ast.TokenIndex, + gpa: *Allocator, +) !Zir.Inst.Ref { + // For trivial values, we don't need a tunnel. + // Just return the ref. + if (num_tunnels == 0 or refToIndex(value) == null) { + return value; + } + + // Otherwise we need a tunnel. Check if this namespace + // already has one for this value. + const gop = try ns.?.captures.getOrPut(gpa, refToIndex(value).?); + if (!gop.found_existing) { + // Make a new capture for this value + const capture_ref = try ns.?.declaring_gz.?.addUnTok(.closure_capture, value, token); + gop.value_ptr.* = refToIndex(capture_ref).?; + } + + // Add an instruction to get the value from the closure into + // our current context + return try gz.addInstNode(.closure_get, gop.value_ptr.*, inner_ref_node); +} + fn stringLiteral( gz: *GenZir, rl: ResultLoc, @@ -8961,6 +9062,17 @@ const Scope = struct { return @fieldParentPtr(T, "base", base); } + fn parent(base: *Scope) ?*Scope { + return switch (base.tag) { + .gen_zir => base.cast(GenZir).?.parent, + .local_val => base.cast(LocalVal).?.parent, + .local_ptr => base.cast(LocalPtr).?.parent, + .defer_normal, .defer_error => base.cast(Defer).?.parent, + .namespace => base.cast(Namespace).?.parent, + .top => null, + }; + } + const Tag = enum { gen_zir, local_val, @@ -8986,7 +9098,7 @@ const Scope = struct { const LocalVal = struct { const base_tag: Tag = .local_val; base: Scope = Scope{ .tag = base_tag }, - /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`. + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`. parent: *Scope, gen_zir: *GenZir, inst: Zir.Inst.Ref, @@ -9005,7 +9117,7 @@ const Scope = struct { const LocalPtr = struct { const base_tag: Tag = .local_ptr; base: Scope = Scope{ .tag = base_tag }, - /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`. + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`. parent: *Scope, gen_zir: *GenZir, ptr: Zir.Inst.Ref, @@ -9023,7 +9135,7 @@ const Scope = struct { const Defer = struct { base: Scope, - /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`. + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`. parent: *Scope, defer_node: Ast.Node.Index, }; @@ -9034,11 +9146,27 @@ const Scope = struct { const base_tag: Tag = .namespace; base: Scope = Scope{ .tag = base_tag }, + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`. parent: *Scope, /// Maps string table index to the source location of declaration, /// for the purposes of reporting name shadowing compile errors. decls: std.AutoHashMapUnmanaged(u32, Ast.Node.Index) = .{}, node: Ast.Node.Index, + inst: Zir.Inst.Index, + + /// The astgen scope containing this namespace. + /// Only valid during astgen. + declaring_gz: ?*GenZir, + + /// Map from the raw captured value to the instruction + /// ref of the capture for decls in this namespace + captures: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{}, + + pub fn deinit(self: *Namespace, gpa: *Allocator) void { + self.decls.deinit(gpa); + self.captures.deinit(gpa); + self.* = undefined; + } }; const Top = struct { @@ -9061,6 +9189,7 @@ const GenZir = struct { decl_node_index: Ast.Node.Index, /// The containing decl line index, absolute. decl_line: u32, + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`, `Namespace`. parent: *Scope, /// All `GenZir` scopes for the same ZIR share this. astgen: *AstGen, @@ -9096,6 +9225,12 @@ const GenZir = struct { suspend_node: Ast.Node.Index = 0, nosuspend_node: Ast.Node.Index = 0, + /// Namespace members are lazy. When executing a decl within a namespace, + /// any references to external instructions need to be treated specially. + /// This list tracks those references. See also .closure_capture and .closure_get. + /// Keys are the raw instruction index, values are the closure_capture instruction. + captures: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{}, + fn makeSubBlock(gz: *GenZir, scope: *Scope) GenZir { return .{ .force_comptime = gz.force_comptime, @@ -9810,6 +9945,22 @@ const GenZir = struct { }); } + fn addInstNode( + gz: *GenZir, + tag: Zir.Inst.Tag, + inst: Zir.Inst.Index, + /// Absolute node index. This function does the conversion to offset from Decl. + src_node: Ast.Node.Index, + ) !Zir.Inst.Ref { + return gz.add(.{ + .tag = tag, + .data = .{ .inst_node = .{ + .inst = inst, + .src_node = gz.nodeIndexToRelative(src_node), + } }, + }); + } + fn addNodeExtended( gz: *GenZir, opcode: Zir.Inst.Extended, @@ -10111,6 +10262,37 @@ const GenZir = struct { }); } + fn setOpaque(gz: *GenZir, inst: Zir.Inst.Index, args: struct { + src_node: Ast.Node.Index, + decls_len: u32, + }) !void { + const astgen = gz.astgen; + const gpa = astgen.gpa; + + try astgen.extra.ensureUnusedCapacity(gpa, 2); + const payload_index = @intCast(u32, astgen.extra.items.len); + + if (args.src_node != 0) { + const node_offset = gz.nodeIndexToRelative(args.src_node); + astgen.extra.appendAssumeCapacity(@bitCast(u32, node_offset)); + } + if (args.decls_len != 0) { + astgen.extra.appendAssumeCapacity(args.decls_len); + } + astgen.instructions.set(inst, .{ + .tag = .extended, + .data = .{ .extended = .{ + .opcode = .opaque_decl, + .small = @bitCast(u16, Zir.Inst.OpaqueDecl.Small{ + .has_src_node = args.src_node != 0, + .has_decls_len = args.decls_len != 0, + .name_strategy = gz.anon_name_strategy, + }), + .operand = payload_index, + } }, + }); + } + fn add(gz: *GenZir, inst: Zir.Inst) !Zir.Inst.Ref { return indexToRef(try gz.addAsIndex(inst)); } diff --git a/src/Module.zig b/src/Module.zig index 861648d689..a0e04dd478 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -275,6 +275,56 @@ pub const DeclPlusEmitH = struct { emit_h: EmitH, }; +pub const CaptureScope = struct { + parent: ?*CaptureScope, + + /// Values from this decl's evaluation that will be closed over in + /// child decls. Values stored in the value_arena of the linked decl. + /// During sema, this map is backed by the gpa. Once sema completes, + /// it is reallocated using the value_arena. + captures: std.AutoHashMapUnmanaged(Zir.Inst.Index, TypedValue) = .{}, +}; + +pub const WipCaptureScope = struct { + scope: *CaptureScope, + finalized: bool, + gpa: *Allocator, + perm_arena: *Allocator, + + pub fn init(gpa: *Allocator, perm_arena: *Allocator, parent: ?*CaptureScope) !@This() { + const scope = try perm_arena.create(CaptureScope); + scope.* = .{ .parent = parent }; + return @This(){ + .scope = scope, + .finalized = false, + .gpa = gpa, + .perm_arena = perm_arena, + }; + } + + pub fn finalize(noalias self: *@This()) !void { + assert(!self.finalized); + // use a temp to avoid unintentional aliasing due to RLS + const tmp = try self.scope.captures.clone(self.perm_arena); + self.scope.captures = tmp; + self.finalized = true; + } + + pub fn reset(noalias self: *@This(), parent: ?*CaptureScope) !void { + if (!self.finalized) try self.finalize(); + self.scope = try self.perm_arena.create(CaptureScope); + self.scope.* = .{ .parent = parent }; + self.finalized = false; + } + + pub fn deinit(noalias self: *@This()) void { + if (!self.finalized) { + self.scope.captures.deinit(self.gpa); + } + self.* = undefined; + } +}; + pub const Decl = struct { /// Allocated with Module's allocator; outlives the ZIR code. name: [*:0]const u8, @@ -290,7 +340,7 @@ pub const Decl = struct { linksection_val: Value, /// Populated when `has_tv`. @"addrspace": std.builtin.AddressSpace, - /// The memory for ty, val, align_val, linksection_val. + /// The memory for ty, val, align_val, linksection_val, and captures. /// If this is `null` then there is no memory management needed. value_arena: ?*std.heap.ArenaAllocator.State = null, /// The direct parent namespace of the Decl. @@ -299,6 +349,11 @@ pub const Decl = struct { /// the namespace of the struct, since there is no parent. namespace: *Scope.Namespace, + /// The scope which lexically contains this decl. A decl must depend + /// on its lexical parent, in order to ensure that this pointer is valid. + /// This scope is allocated out of the arena of the parent decl. + src_scope: ?*CaptureScope, + /// An integer that can be checked against the corresponding incrementing /// generation field of Module. This is used to determine whether `complete` status /// represents pre- or post- re-analysis. @@ -959,6 +1014,7 @@ pub const Scope = struct { return @fieldParentPtr(T, "base", base); } + /// Get the decl that is currently being analyzed pub fn ownerDecl(scope: *Scope) ?*Decl { return switch (scope.tag) { .block => scope.cast(Block).?.sema.owner_decl, @@ -967,6 +1023,7 @@ pub const Scope = struct { }; } + /// Get the decl which contains this decl, for the purposes of source reporting pub fn srcDecl(scope: *Scope) ?*Decl { return switch (scope.tag) { .block => scope.cast(Block).?.src_decl, @@ -975,6 +1032,15 @@ pub const Scope = struct { }; } + /// Get the scope which contains this decl, for resolving closure_get instructions. + pub fn srcScope(scope: *Scope) ?*CaptureScope { + return switch (scope.tag) { + .block => scope.cast(Block).?.wip_capture_scope, + .file => null, + .namespace => scope.cast(Namespace).?.getDecl().src_scope, + }; + } + /// Asserts the scope has a parent which is a Namespace and returns it. pub fn namespace(scope: *Scope) *Namespace { switch (scope.tag) { @@ -1311,6 +1377,9 @@ pub const Scope = struct { instructions: ArrayListUnmanaged(Air.Inst.Index), // `param` instructions are collected here to be used by the `func` instruction. params: std.ArrayListUnmanaged(Param) = .{}, + + wip_capture_scope: *CaptureScope, + label: ?*Label = null, inlining: ?*Inlining, /// If runtime_index is not 0 then one of these is guaranteed to be non null. @@ -1372,6 +1441,7 @@ pub const Scope = struct { .sema = parent.sema, .src_decl = parent.src_decl, .instructions = .{}, + .wip_capture_scope = parent.wip_capture_scope, .label = null, .inlining = parent.inlining, .is_comptime = parent.is_comptime, @@ -2901,12 +2971,10 @@ pub fn mapOldZirToNew( var match_stack: std.ArrayListUnmanaged(MatchedZirDecl) = .{}; defer match_stack.deinit(gpa); - const old_main_struct_inst = old_zir.getMainStruct(); - const new_main_struct_inst = new_zir.getMainStruct(); - + // Main struct inst is always the same try match_stack.append(gpa, .{ - .old_inst = old_main_struct_inst, - .new_inst = new_main_struct_inst, + .old_inst = Zir.main_struct_inst, + .new_inst = Zir.main_struct_inst, }); var old_decls = std.ArrayList(Zir.Inst.Index).init(gpa); @@ -3064,6 +3132,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { const struct_obj = try new_decl_arena.allocator.create(Module.Struct); const struct_ty = try Type.Tag.@"struct".create(&new_decl_arena.allocator, struct_obj); const struct_val = try Value.Tag.ty.create(&new_decl_arena.allocator, struct_ty); + const ty_ty = comptime Type.initTag(.type); struct_obj.* = .{ .owner_decl = undefined, // set below .fields = .{}, @@ -3078,7 +3147,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { .file_scope = file, }, }; - const new_decl = try mod.allocateNewDecl(&struct_obj.namespace, 0); + const new_decl = try mod.allocateNewDecl(&struct_obj.namespace, 0, null); file.root_decl = new_decl; struct_obj.owner_decl = new_decl; new_decl.src_line = 0; @@ -3087,7 +3156,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { new_decl.is_exported = false; new_decl.has_align = false; new_decl.has_linksection_or_addrspace = false; - new_decl.ty = struct_ty; + new_decl.ty = ty_ty; new_decl.val = struct_val; new_decl.has_tv = true; new_decl.owns_tv = true; @@ -3097,7 +3166,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { if (file.status == .success_zir) { assert(file.zir_loaded); - const main_struct_inst = file.zir.getMainStruct(); + const main_struct_inst = Zir.main_struct_inst; struct_obj.zir_index = main_struct_inst; var sema_arena = std.heap.ArenaAllocator.init(gpa); @@ -3107,6 +3176,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { .mod = mod, .gpa = gpa, .arena = &sema_arena.allocator, + .perm_arena = &new_decl_arena.allocator, .code = file.zir, .owner_decl = new_decl, .namespace = &struct_obj.namespace, @@ -3115,10 +3185,15 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { .owner_func = null, }; defer sema.deinit(); + + var wip_captures = try WipCaptureScope.init(gpa, &new_decl_arena.allocator, null); + defer wip_captures.deinit(); + var block_scope: Scope.Block = .{ .parent = null, .sema = &sema, .src_decl = new_decl, + .wip_capture_scope = wip_captures.scope, .instructions = .{}, .inlining = null, .is_comptime = true, @@ -3126,6 +3201,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { defer block_scope.instructions.deinit(gpa); if (sema.analyzeStructDecl(new_decl, main_struct_inst, struct_obj)) |_| { + try wip_captures.finalize(); new_decl.analysis = .complete; } else |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -3155,6 +3231,10 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { decl.analysis = .in_progress; + // We need the memory for the Type to go into the arena for the Decl + var decl_arena = std.heap.ArenaAllocator.init(gpa); + errdefer decl_arena.deinit(); + var analysis_arena = std.heap.ArenaAllocator.init(gpa); defer analysis_arena.deinit(); @@ -3162,6 +3242,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { .mod = mod, .gpa = gpa, .arena = &analysis_arena.allocator, + .perm_arena = &decl_arena.allocator, .code = zir, .owner_decl = decl, .namespace = decl.namespace, @@ -3173,7 +3254,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { if (decl.isRoot()) { log.debug("semaDecl root {*} ({s})", .{ decl, decl.name }); - const main_struct_inst = zir.getMainStruct(); + const main_struct_inst = Zir.main_struct_inst; const struct_obj = decl.getStruct().?; // This might not have gotten set in `semaFile` if the first time had // a ZIR failure, so we set it here in case. @@ -3185,10 +3266,14 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { } log.debug("semaDecl {*} ({s})", .{ decl, decl.name }); + var wip_captures = try WipCaptureScope.init(gpa, &decl_arena.allocator, decl.src_scope); + defer wip_captures.deinit(); + var block_scope: Scope.Block = .{ .parent = null, .sema = &sema, .src_decl = decl, + .wip_capture_scope = wip_captures.scope, .instructions = .{}, .inlining = null, .is_comptime = true, @@ -3203,6 +3288,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { const extra = zir.extraData(Zir.Inst.Block, inst_data.payload_index); const body = zir.extra[extra.end..][0..extra.data.body_len]; const break_index = try sema.analyzeBody(&block_scope, body); + try wip_captures.finalize(); const result_ref = zir_datas[break_index].@"break".operand; const src: LazySrcLoc = .{ .node_offset = 0 }; const decl_tv = try sema.resolveInstValue(&block_scope, src, result_ref); @@ -3239,9 +3325,6 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { // not the struct itself. try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty); - // We need the memory for the Type to go into the arena for the Decl - var decl_arena = std.heap.ArenaAllocator.init(gpa); - errdefer decl_arena.deinit(); const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State); if (decl.is_usingnamespace) { @@ -3638,7 +3721,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi // We create a Decl for it regardless of analysis status. const gop = try namespace.decls.getOrPut(gpa, decl_name); if (!gop.found_existing) { - const new_decl = try mod.allocateNewDecl(namespace, decl_node); + const new_decl = try mod.allocateNewDecl(namespace, decl_node, iter.parent_decl.src_scope); if (is_usingnamespace) { namespace.usingnamespace_set.putAssumeCapacity(new_decl, is_pub); } @@ -3898,10 +3981,15 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: *Allocator) Se const gpa = mod.gpa; + // Use the Decl's arena for captured values. + var decl_arena = decl.value_arena.?.promote(gpa); + defer decl.value_arena.?.* = decl_arena.state; + var sema: Sema = .{ .mod = mod, .gpa = gpa, .arena = arena, + .perm_arena = &decl_arena.allocator, .code = decl.namespace.file_scope.zir, .owner_decl = decl, .namespace = decl.namespace, @@ -3916,10 +4004,14 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: *Allocator) Se try sema.air_extra.ensureTotalCapacity(gpa, reserved_count); sema.air_extra.items.len += reserved_count; + var wip_captures = try WipCaptureScope.init(gpa, &decl_arena.allocator, decl.src_scope); + defer wip_captures.deinit(); + var inner_block: Scope.Block = .{ .parent = null, .sema = &sema, .src_decl = decl, + .wip_capture_scope = wip_captures.scope, .instructions = .{}, .inlining = null, .is_comptime = false, @@ -3995,6 +4087,8 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn, arena: *Allocator) Se else => |e| return e, }; + try wip_captures.finalize(); + // Copy the block into place and mark that as the main block. try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len + inner_block.instructions.items.len); @@ -4035,7 +4129,7 @@ fn markOutdatedDecl(mod: *Module, decl: *Decl) !void { decl.analysis = .outdated; } -pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast.Node.Index) !*Decl { +pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast.Node.Index, src_scope: ?*CaptureScope) !*Decl { // If we have emit-h then we must allocate a bigger structure to store the emit-h state. const new_decl: *Decl = if (mod.emit_h != null) blk: { const parent_struct = try mod.gpa.create(DeclPlusEmitH); @@ -4061,6 +4155,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast. .analysis = .unreferenced, .deletion_flag = false, .zir_decl_index = 0, + .src_scope = src_scope, .link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = link.File.Coff.TextBlock.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, @@ -4087,6 +4182,7 @@ pub fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: Ast. .alive = false, .is_usingnamespace = false, }; + return new_decl; } @@ -4191,25 +4287,26 @@ pub fn createAnonymousDeclNamed( typed_value: TypedValue, name: [:0]u8, ) !*Decl { - return mod.createAnonymousDeclFromDeclNamed(scope.ownerDecl().?, typed_value, name); + return mod.createAnonymousDeclFromDeclNamed(scope.ownerDecl().?, scope.srcScope(), typed_value, name); } pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl { - return mod.createAnonymousDeclFromDecl(scope.ownerDecl().?, typed_value); + return mod.createAnonymousDeclFromDecl(scope.ownerDecl().?, scope.srcScope(), typed_value); } -pub fn createAnonymousDeclFromDecl(mod: *Module, owner_decl: *Decl, tv: TypedValue) !*Decl { +pub fn createAnonymousDeclFromDecl(mod: *Module, owner_decl: *Decl, src_scope: ?*CaptureScope, tv: TypedValue) !*Decl { const name_index = mod.getNextAnonNameIndex(); const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{ owner_decl.name, name_index, }); - return mod.createAnonymousDeclFromDeclNamed(owner_decl, tv, name); + return mod.createAnonymousDeclFromDeclNamed(owner_decl, src_scope, tv, name); } /// Takes ownership of `name` even if it returns an error. pub fn createAnonymousDeclFromDeclNamed( mod: *Module, owner_decl: *Decl, + src_scope: ?*CaptureScope, typed_value: TypedValue, name: [:0]u8, ) !*Decl { @@ -4218,7 +4315,7 @@ pub fn createAnonymousDeclFromDeclNamed( const namespace = owner_decl.namespace; try namespace.anon_decls.ensureUnusedCapacity(mod.gpa, 1); - const new_decl = try mod.allocateNewDecl(namespace, owner_decl.src_node); + const new_decl = try mod.allocateNewDecl(namespace, owner_decl.src_node, src_scope); new_decl.name = name; new_decl.src_line = owner_decl.src_line; @@ -4783,7 +4880,7 @@ pub fn populateTestFunctions(mod: *Module) !void { const arena = &new_decl_arena.allocator; const test_fn_vals = try arena.alloc(Value, mod.test_functions.count()); - const array_decl = try mod.createAnonymousDeclFromDecl(decl, .{ + const array_decl = try mod.createAnonymousDeclFromDecl(decl, null, .{ .ty = try Type.Tag.array.create(arena, .{ .len = test_fn_vals.len, .elem_type = try tmp_test_fn_ty.copy(arena), @@ -4796,7 +4893,7 @@ pub fn populateTestFunctions(mod: *Module) !void { var name_decl_arena = std.heap.ArenaAllocator.init(gpa); errdefer name_decl_arena.deinit(); const bytes = try name_decl_arena.allocator.dupe(u8, test_name_slice); - const test_name_decl = try mod.createAnonymousDeclFromDecl(array_decl, .{ + const test_name_decl = try mod.createAnonymousDeclFromDecl(array_decl, null, .{ .ty = try Type.Tag.array_u8.create(&name_decl_arena.allocator, bytes.len), .val = try Value.Tag.bytes.create(&name_decl_arena.allocator, bytes), }); diff --git a/src/Sema.zig b/src/Sema.zig index a0e3250e56..cc7a227ca6 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -8,8 +8,12 @@ mod: *Module, /// Alias to `mod.gpa`. gpa: *Allocator, -/// Points to the arena allocator of the Decl. +/// Points to the temporary arena allocator of the Sema. +/// This arena will be cleared when the sema is destroyed. arena: *Allocator, +/// Points to the arena allocator for the owner_decl. +/// This arena will persist until the decl is invalidated. +perm_arena: *Allocator, code: Zir, air_instructions: std.MultiArrayList(Air.Inst) = .{}, air_extra: std.ArrayListUnmanaged(u32) = .{}, @@ -80,6 +84,8 @@ const Scope = Module.Scope; const CompileError = Module.CompileError; const SemaError = Module.SemaError; const Decl = Module.Decl; +const CaptureScope = Module.CaptureScope; +const WipCaptureScope = Module.WipCaptureScope; const LazySrcLoc = Module.LazySrcLoc; const RangeSet = @import("RangeSet.zig"); const target_util = @import("target.zig"); @@ -129,15 +135,29 @@ pub fn analyzeBody( ) CompileError!Zir.Inst.Index { // No tracy calls here, to avoid interfering with the tail call mechanism. + const parent_capture_scope = block.wip_capture_scope; + + var wip_captures = WipCaptureScope{ + .finalized = true, + .scope = parent_capture_scope, + .perm_arena = sema.perm_arena, + .gpa = sema.gpa, + }; + defer if (wip_captures.scope != parent_capture_scope) { + wip_captures.deinit(); + }; + const map = &block.sema.inst_map; const tags = block.sema.code.instructions.items(.tag); const datas = block.sema.code.instructions.items(.data); + var orig_captures: usize = parent_capture_scope.captures.count(); + // We use a while(true) loop here to avoid a redundant way of breaking out of // the loop. The only way to break out of the loop is with a `noreturn` // instruction. var i: usize = 0; - while (true) { + const result = while (true) { const inst = body[i]; const air_inst: Air.Inst.Ref = switch (tags[inst]) { // zig fmt: off @@ -170,6 +190,7 @@ pub fn analyzeBody( .call_compile_time => try sema.zirCall(block, inst, .compile_time, false), .call_nosuspend => try sema.zirCall(block, inst, .no_async, false), .call_async => try sema.zirCall(block, inst, .async_kw, false), + .closure_get => try sema.zirClosureGet(block, inst), .cmp_lt => try sema.zirCmp(block, inst, .lt), .cmp_lte => try sema.zirCmp(block, inst, .lte), .cmp_eq => try sema.zirCmpEq(block, inst, .eq, .cmp_eq), @@ -343,9 +364,6 @@ pub fn analyzeBody( .trunc => try sema.zirUnaryMath(block, inst), .round => try sema.zirUnaryMath(block, inst), - .opaque_decl => try sema.zirOpaqueDecl(block, inst, .parent), - .opaque_decl_anon => try sema.zirOpaqueDecl(block, inst, .anon), - .opaque_decl_func => try sema.zirOpaqueDecl(block, inst, .func), .error_set_decl => try sema.zirErrorSetDecl(block, inst, .parent), .error_set_decl_anon => try sema.zirErrorSetDecl(block, inst, .anon), .error_set_decl_func => try sema.zirErrorSetDecl(block, inst, .func), @@ -362,13 +380,13 @@ pub fn analyzeBody( // Instructions that we know to *always* be noreturn based solely on their tag. // These functions match the return type of analyzeBody so that we can // tail call them here. - .compile_error => return sema.zirCompileError(block, inst), - .ret_coerce => return sema.zirRetCoerce(block, inst), - .ret_node => return sema.zirRetNode(block, inst), - .ret_load => return sema.zirRetLoad(block, inst), - .ret_err_value => return sema.zirRetErrValue(block, inst), - .@"unreachable" => return sema.zirUnreachable(block, inst), - .panic => return sema.zirPanic(block, inst), + .compile_error => break sema.zirCompileError(block, inst), + .ret_coerce => break sema.zirRetCoerce(block, inst), + .ret_node => break sema.zirRetNode(block, inst), + .ret_load => break sema.zirRetLoad(block, inst), + .ret_err_value => break sema.zirRetErrValue(block, inst), + .@"unreachable" => break sema.zirUnreachable(block, inst), + .panic => break sema.zirPanic(block, inst), // zig fmt: on // Instructions that we know can *never* be noreturn based solely on @@ -503,34 +521,49 @@ pub fn analyzeBody( i += 1; continue; }, + .closure_capture => { + try sema.zirClosureCapture(block, inst); + i += 1; + continue; + }, // Special case instructions to handle comptime control flow. .@"break" => { if (block.is_comptime) { - return inst; // same as break_inline + break inst; // same as break_inline } else { - return sema.zirBreak(block, inst); + break sema.zirBreak(block, inst); } }, - .break_inline => return inst, + .break_inline => break inst, .repeat => { if (block.is_comptime) { // Send comptime control flow back to the beginning of this block. const src: LazySrcLoc = .{ .node_offset = datas[inst].node }; try sema.emitBackwardBranch(block, src); + if (wip_captures.scope.captures.count() != orig_captures) { + try wip_captures.reset(parent_capture_scope); + block.wip_capture_scope = wip_captures.scope; + orig_captures = 0; + } i = 0; continue; } else { const src_node = sema.code.instructions.items(.data)[inst].node; const src: LazySrcLoc = .{ .node_offset = src_node }; try sema.requireRuntimeBlock(block, src); - return always_noreturn; + break always_noreturn; } }, .repeat_inline => { // Send comptime control flow back to the beginning of this block. const src: LazySrcLoc = .{ .node_offset = datas[inst].node }; try sema.emitBackwardBranch(block, src); + if (wip_captures.scope.captures.count() != orig_captures) { + try wip_captures.reset(parent_capture_scope); + block.wip_capture_scope = wip_captures.scope; + orig_captures = 0; + } i = 0; continue; }, @@ -545,7 +578,7 @@ pub fn analyzeBody( if (inst == break_data.block_inst) { break :blk sema.resolveInst(break_data.operand); } else { - return break_inst; + break break_inst; } }, .block => blk: { @@ -559,7 +592,7 @@ pub fn analyzeBody( if (inst == break_data.block_inst) { break :blk sema.resolveInst(break_data.operand); } else { - return break_inst; + break break_inst; } }, .block_inline => blk: { @@ -572,11 +605,11 @@ pub fn analyzeBody( if (inst == break_data.block_inst) { break :blk sema.resolveInst(break_data.operand); } else { - return break_inst; + break break_inst; } }, .condbr => blk: { - if (!block.is_comptime) return sema.zirCondbr(block, inst); + if (!block.is_comptime) break sema.zirCondbr(block, inst); // Same as condbr_inline. TODO https://github.com/ziglang/zig/issues/8220 const inst_data = datas[inst].pl_node; const cond_src: LazySrcLoc = .{ .node_offset_if_cond = inst_data.src_node }; @@ -590,7 +623,7 @@ pub fn analyzeBody( if (inst == break_data.block_inst) { break :blk sema.resolveInst(break_data.operand); } else { - return break_inst; + break break_inst; } }, .condbr_inline => blk: { @@ -606,15 +639,22 @@ pub fn analyzeBody( if (inst == break_data.block_inst) { break :blk sema.resolveInst(break_data.operand); } else { - return break_inst; + break break_inst; } }, }; if (sema.typeOf(air_inst).isNoReturn()) - return always_noreturn; + break always_noreturn; try map.put(sema.gpa, inst, air_inst); i += 1; + } else unreachable; + + if (!wip_captures.finalized) { + try wip_captures.finalize(); + block.wip_capture_scope = parent_capture_scope; } + + return result; } fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -626,6 +666,7 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr .struct_decl => return sema.zirStructDecl( block, extended, inst), .enum_decl => return sema.zirEnumDecl( block, extended), .union_decl => return sema.zirUnionDecl( block, extended, inst), + .opaque_decl => return sema.zirOpaqueDecl( block, extended, inst), .ret_ptr => return sema.zirRetPtr( block, extended), .ret_type => return sema.zirRetType( block, extended), .this => return sema.zirThis( block, extended), @@ -1011,7 +1052,6 @@ fn zirStructDecl( } fn createTypeName(sema: *Sema, block: *Scope.Block, name_strategy: Zir.Inst.NameStrategy) ![:0]u8 { - _ = block; switch (name_strategy) { .anon => { // It would be neat to have "struct:line:column" but this name has @@ -1020,14 +1060,14 @@ fn createTypeName(sema: *Sema, block: *Scope.Block, name_strategy: Zir.Inst.Name // semantically analyzed. const name_index = sema.mod.getNextAnonNameIndex(); return std.fmt.allocPrintZ(sema.gpa, "{s}__anon_{d}", .{ - sema.owner_decl.name, name_index, + block.src_decl.name, name_index, }); }, - .parent => return sema.gpa.dupeZ(u8, mem.spanZ(sema.owner_decl.name)), + .parent => return sema.gpa.dupeZ(u8, mem.spanZ(block.src_decl.name)), .func => { const name_index = sema.mod.getNextAnonNameIndex(); const name = try std.fmt.allocPrintZ(sema.gpa, "{s}__anon_{d}", .{ - sema.owner_decl.name, name_index, + block.src_decl.name, name_index, }); log.warn("TODO: handle NameStrategy.func correctly instead of using anon name '{s}'", .{ name, @@ -1083,17 +1123,6 @@ fn zirEnumDecl( var new_decl_arena = std.heap.ArenaAllocator.init(gpa); errdefer new_decl_arena.deinit(); - const tag_ty = blk: { - if (tag_type_ref != .none) { - // TODO better source location - // TODO (needs AstGen fix too) move this eval to the block so it gets allocated - // in the new decl arena. - break :blk try sema.resolveType(block, src, tag_type_ref); - } - const bits = std.math.log2_int_ceil(usize, fields_len); - break :blk try Type.Tag.int_unsigned.create(&new_decl_arena.allocator, bits); - }; - const enum_obj = try new_decl_arena.allocator.create(Module.EnumFull); const enum_ty_payload = try new_decl_arena.allocator.create(Type.Payload.EnumFull); enum_ty_payload.* = .{ @@ -1112,7 +1141,7 @@ fn zirEnumDecl( enum_obj.* = .{ .owner_decl = new_decl, - .tag_ty = tag_ty, + .tag_ty = Type.initTag(.@"null"), .fields = .{}, .values = .{}, .node_offset = src.node_offset, @@ -1140,16 +1169,6 @@ fn zirEnumDecl( const body_end = extra_index; extra_index += bit_bags_count; - try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len); - const any_values = for (sema.code.extra[body_end..][0..bit_bags_count]) |bag| { - if (bag != 0) break true; - } else false; - if (any_values) { - try enum_obj.values.ensureTotalCapacityContext(&new_decl_arena.allocator, fields_len, .{ - .ty = tag_ty, - }); - } - { // We create a block for the field type instructions because they // may need to reference Decls from inside the enum namespace. @@ -1172,10 +1191,14 @@ fn zirEnumDecl( sema.func = null; defer sema.func = prev_func; + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, new_decl.src_scope); + defer wip_captures.deinit(); + var enum_block: Scope.Block = .{ .parent = null, .sema = sema, .src_decl = new_decl, + .wip_capture_scope = wip_captures.scope, .instructions = .{}, .inlining = null, .is_comptime = true, @@ -1185,7 +1208,30 @@ fn zirEnumDecl( if (body.len != 0) { _ = try sema.analyzeBody(&enum_block, body); } + + try wip_captures.finalize(); + + const tag_ty = blk: { + if (tag_type_ref != .none) { + // TODO better source location + break :blk try sema.resolveType(block, src, tag_type_ref); + } + const bits = std.math.log2_int_ceil(usize, fields_len); + break :blk try Type.Tag.int_unsigned.create(&new_decl_arena.allocator, bits); + }; + enum_obj.tag_ty = tag_ty; } + + try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len); + const any_values = for (sema.code.extra[body_end..][0..bit_bags_count]) |bag| { + if (bag != 0) break true; + } else false; + if (any_values) { + try enum_obj.values.ensureTotalCapacityContext(&new_decl_arena.allocator, fields_len, .{ + .ty = enum_obj.tag_ty, + }); + } + var bit_bag_index: usize = body_end; var cur_bit_bag: u32 = undefined; var field_i: u32 = 0; @@ -1224,10 +1270,10 @@ fn zirEnumDecl( // that points to this default value expression rather than the struct. // But only resolve the source location if we need to emit a compile error. const tag_val = (try sema.resolveInstConst(block, src, tag_val_ref)).val; - enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = tag_ty }); + enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = enum_obj.tag_ty }); } else if (any_values) { const tag_val = try Value.Tag.int_u64.create(&new_decl_arena.allocator, field_i); - enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = tag_ty }); + enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = enum_obj.tag_ty }); } } @@ -1305,20 +1351,14 @@ fn zirUnionDecl( fn zirOpaqueDecl( sema: *Sema, block: *Scope.Block, + extended: Zir.Inst.Extended.InstData, inst: Zir.Inst.Index, - name_strategy: Zir.Inst.NameStrategy, ) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index); - - _ = name_strategy; - _ = inst_data; - _ = src; - _ = extra; + _ = extended; + _ = inst; return sema.mod.fail(&block.base, sema.src, "TODO implement zirOpaqueDecl", .{}); } @@ -2160,6 +2200,7 @@ fn zirCImport(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) Com .parent = parent_block, .sema = sema, .src_decl = parent_block.src_decl, + .wip_capture_scope = parent_block.wip_capture_scope, .instructions = .{}, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, @@ -2214,7 +2255,7 @@ fn zirCImport(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) Com try sema.mod.semaFile(result.file); const file_root_decl = result.file.root_decl.?; try sema.mod.declareDeclDependency(sema.owner_decl, file_root_decl); - return sema.addType(file_root_decl.ty); + return sema.addConstant(file_root_decl.ty, file_root_decl.val); } fn zirSuspendBlock(sema: *Sema, parent_block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -2259,6 +2300,7 @@ fn zirBlock( .parent = parent_block, .sema = sema, .src_decl = parent_block.src_decl, + .wip_capture_scope = parent_block.wip_capture_scope, .instructions = .{}, .label = &label, .inlining = parent_block.inlining, @@ -2866,10 +2908,14 @@ fn analyzeCall( sema.func = module_fn; defer sema.func = parent_func; + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, module_fn.owner_decl.src_scope); + defer wip_captures.deinit(); + var child_block: Scope.Block = .{ .parent = null, .sema = sema, .src_decl = module_fn.owner_decl, + .wip_capture_scope = wip_captures.scope, .instructions = .{}, .label = null, .inlining = &inlining, @@ -3034,6 +3080,9 @@ fn analyzeCall( break :res2 result; }; + + try wip_captures.finalize(); + break :res res2; } else if (func_ty_info.is_generic) res: { const func_val = try sema.resolveConstValue(block, func_src, func); @@ -3116,7 +3165,8 @@ fn analyzeCall( try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); // Create a Decl for the new function. - const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node); + const src_decl = namespace.getDecl(); + const new_decl = try mod.allocateNewDecl(namespace, module_fn.owner_decl.src_node, src_decl.src_scope); // TODO better names for generic function instantiations const name_index = mod.getNextAnonNameIndex(); new_decl.name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ @@ -3147,6 +3197,7 @@ fn analyzeCall( .mod = mod, .gpa = gpa, .arena = sema.arena, + .perm_arena = &new_decl_arena.allocator, .code = fn_zir, .owner_decl = new_decl, .namespace = namespace, @@ -3159,10 +3210,14 @@ fn analyzeCall( }; defer child_sema.deinit(); + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, new_decl.src_scope); + defer wip_captures.deinit(); + var child_block: Scope.Block = .{ .parent = null, .sema = &child_sema, .src_decl = new_decl, + .wip_capture_scope = wip_captures.scope, .instructions = .{}, .inlining = null, .is_comptime = true, @@ -3250,6 +3305,8 @@ fn analyzeCall( arg_i += 1; } + try wip_captures.finalize(); + // Populate the Decl ty/val with the function and its type. new_decl.ty = try child_sema.typeOf(new_func_inst).copy(&new_decl_arena.allocator); new_decl.val = try Value.Tag.function.create(&new_decl_arena.allocator, new_func); @@ -5164,6 +5221,7 @@ fn analyzeSwitch( .parent = block, .sema = sema, .src_decl = block.src_decl, + .wip_capture_scope = block.wip_capture_scope, .instructions = .{}, .label = &label, .inlining = block.inlining, @@ -5268,12 +5326,19 @@ fn analyzeSwitch( const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope); + defer wip_captures.deinit(); + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = wip_captures.scope; + const item = sema.resolveInst(item_ref); // `item` is already guaranteed to be constant known. _ = try sema.analyzeBody(&case_block, body); + try wip_captures.finalize(); + try cases_extra.ensureUnusedCapacity(gpa, 3 + case_block.instructions.items.len); cases_extra.appendAssumeCapacity(1); // items_len cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); @@ -5301,6 +5366,7 @@ fn analyzeSwitch( extra_index += items_len; case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = child_block.wip_capture_scope; var any_ok: Air.Inst.Ref = .none; @@ -5379,11 +5445,18 @@ fn analyzeSwitch( var cond_body = case_block.instructions.toOwnedSlice(gpa); defer gpa.free(cond_body); + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope); + defer wip_captures.deinit(); + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = wip_captures.scope; + const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; _ = try sema.analyzeBody(&case_block, body); + try wip_captures.finalize(); + if (is_first) { is_first = false; first_else_body = cond_body; @@ -5409,9 +5482,16 @@ fn analyzeSwitch( var final_else_body: []const Air.Inst.Index = &.{}; if (special.body.len != 0) { + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, child_block.wip_capture_scope); + defer wip_captures.deinit(); + case_block.instructions.shrinkRetainingCapacity(0); + case_block.wip_capture_scope = wip_captures.scope; + _ = try sema.analyzeBody(&case_block, special.body); + try wip_captures.finalize(); + if (is_first) { final_else_body = case_block.instructions.items; } else { @@ -5693,7 +5773,7 @@ fn zirImport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro try mod.semaFile(result.file); const file_root_decl = result.file.root_decl.?; try sema.mod.declareDeclDependency(sema.owner_decl, file_root_decl); - return sema.addType(file_root_decl.ty); + return sema.addConstant(file_root_decl.ty, file_root_decl.val); } fn zirRetErrValueCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6536,8 +6616,45 @@ fn zirThis( block: *Scope.Block, extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { + const this_decl = block.base.namespace().getDecl(); const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; - return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirThis", .{}); + return sema.analyzeDeclVal(block, src, this_decl); +} + +fn zirClosureCapture( + sema: *Sema, + block: *Scope.Block, + inst: Zir.Inst.Index, +) CompileError!void { + // TODO: Compile error when closed over values are modified + const inst_data = sema.code.instructions.items(.data)[inst].un_tok; + const tv = try sema.resolveInstConst(block, inst_data.src(), inst_data.operand); + try block.wip_capture_scope.captures.putNoClobber(sema.gpa, inst, .{ + .ty = try tv.ty.copy(sema.perm_arena), + .val = try tv.val.copy(sema.perm_arena), + }); +} + +fn zirClosureGet( + sema: *Sema, + block: *Scope.Block, + inst: Zir.Inst.Index, +) CompileError!Air.Inst.Ref { + // TODO CLOSURE: Test this with inline functions + const inst_data = sema.code.instructions.items(.data)[inst].inst_node; + var scope: *CaptureScope = block.src_decl.src_scope.?; + // Note: The target closure must be in this scope list. + // If it's not here, the zir is invalid, or the list is broken. + const tv = while (true) { + // Note: We don't need to add a dependency here, because + // decls always depend on their lexical parents. + if (scope.captures.getPtr(inst_data.inst)) |tv| { + break tv; + } + scope = scope.parent.?; + } else unreachable; + + return sema.addConstant(tv.ty, tv.val); } fn zirRetAddr( @@ -8615,6 +8732,7 @@ fn addSafetyCheck( var fail_block: Scope.Block = .{ .parent = parent_block, .sema = sema, + .wip_capture_scope = parent_block.wip_capture_scope, .src_decl = parent_block.src_decl, .instructions = .{}, .inlining = parent_block.inlining, @@ -8714,7 +8832,7 @@ fn safetyPanic( block: *Scope.Block, src: LazySrcLoc, panic_id: PanicId, -) !Zir.Inst.Index { +) CompileError!Zir.Inst.Index { const msg = switch (panic_id) { .unreach => "reached unreachable code", .unwrap_null => "attempt to use null value", @@ -10666,6 +10784,10 @@ pub fn resolveDeclFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: sema.namespace = &struct_obj.namespace; defer sema.namespace = prev_namespace; + const old_src = block.src_decl; + defer block.src_decl = old_src; + block.src_decl = struct_obj.owner_decl; + struct_obj.status = .field_types_wip; try sema.analyzeStructFields(block, struct_obj); struct_obj.status = .have_field_types; @@ -10684,6 +10806,10 @@ pub fn resolveDeclFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: sema.namespace = &union_obj.namespace; defer sema.namespace = prev_namespace; + const old_src = block.src_decl; + defer block.src_decl = old_src; + block.src_decl = union_obj.owner_decl; + union_obj.status = .field_types_wip; try sema.analyzeUnionFields(block, union_obj); union_obj.status = .have_field_types; @@ -10885,9 +11011,11 @@ fn analyzeUnionFields( const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset }; extra_index += @boolToInt(small.has_src_node); - if (small.has_tag_type) { + const tag_type_ref: Zir.Inst.Ref = if (small.has_tag_type) blk: { + const ty_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); extra_index += 1; - } + break :blk ty_ref; + } else .none; const body_len = if (small.has_body_len) blk: { const body_len = zir.extra[extra_index]; @@ -10996,6 +11124,7 @@ fn analyzeUnionFields( } // TODO resolve the union tag_type_ref + _ = tag_type_ref; } fn getBuiltin( diff --git a/src/Zir.zig b/src/Zir.zig index 7a07f0aaaa..83c43fd7f2 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -49,8 +49,6 @@ pub const Header = extern struct { }; pub const ExtraIndex = enum(u32) { - /// Ref. The main struct decl for this file. - main_struct, /// If this is 0, no compile errors. Otherwise there is a `CompileErrors` /// payload at this index. compile_errors, @@ -61,11 +59,6 @@ pub const ExtraIndex = enum(u32) { _, }; -pub fn getMainStruct(zir: Zir) Inst.Index { - return zir.extra[@enumToInt(ExtraIndex.main_struct)] - - @intCast(u32, Inst.Ref.typed_value_map.len); -} - /// Returns the requested data, as well as the new index which is at the start of the /// trailers for the object. pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, end: usize } { @@ -112,6 +105,10 @@ pub fn deinit(code: *Zir, gpa: *Allocator) void { code.* = undefined; } +/// ZIR is structured so that the outermost "main" struct of any file +/// is always at index 0. +pub const main_struct_inst: Inst.Index = 0; + /// These are untyped instructions generated from an Abstract Syntax Tree. /// The data here is immutable because it is possible to have multiple /// analyses on the same ZIR happening at the same time. @@ -267,11 +264,6 @@ pub const Inst = struct { /// only the taken branch is analyzed. The then block and else block must /// terminate with an "inline" variant of a noreturn instruction. condbr_inline, - /// An opaque type definition. Provides an AST node only. - /// Uses the `pl_node` union field. Payload is `OpaqueDecl`. - opaque_decl, - opaque_decl_anon, - opaque_decl_func, /// An error set type definition. Contains a list of field names. /// Uses the `pl_node` union field. Payload is `ErrorSetDecl`. error_set_decl, @@ -941,6 +933,17 @@ pub const Inst = struct { @"await", await_nosuspend, + /// When a type or function refers to a comptime value from an outer + /// scope, that forms a closure over comptime value. The outer scope + /// will record a capture of that value, which encodes its current state + /// and marks it to persist. Uses `un_tok` field. Operand is the + /// instruction value to capture. + closure_capture, + /// The inner scope of a closure uses closure_get to retrieve the value + /// stored by the outer scope. Uses `inst_node` field. Operand is the + /// closure_capture instruction ref. + closure_get, + /// The ZIR instruction tag is one of the `Extended` ones. /// Uses the `extended` union field. extended, @@ -996,9 +999,6 @@ pub const Inst = struct { .cmp_gt, .cmp_neq, .coerce_result_ptr, - .opaque_decl, - .opaque_decl_anon, - .opaque_decl_func, .error_set_decl, .error_set_decl_anon, .error_set_decl_func, @@ -1191,6 +1191,8 @@ pub const Inst = struct { .await_nosuspend, .ret_err_value_code, .extended, + .closure_get, + .closure_capture, => false, .@"break", @@ -1258,9 +1260,6 @@ pub const Inst = struct { .coerce_result_ptr = .bin, .condbr = .pl_node, .condbr_inline = .pl_node, - .opaque_decl = .pl_node, - .opaque_decl_anon = .pl_node, - .opaque_decl_func = .pl_node, .error_set_decl = .pl_node, .error_set_decl_anon = .pl_node, .error_set_decl_func = .pl_node, @@ -1478,6 +1477,9 @@ pub const Inst = struct { .@"await" = .un_node, .await_nosuspend = .un_node, + .closure_capture = .un_tok, + .closure_get = .inst_node, + .extended = .extended, }); }; @@ -1510,6 +1512,10 @@ pub const Inst = struct { /// `operand` is payload index to `UnionDecl`. /// `small` is `UnionDecl.Small`. union_decl, + /// An opaque type definition. Contains references to decls and captures. + /// `operand` is payload index to `OpaqueDecl`. + /// `small` is `OpaqueDecl.Small`. + opaque_decl, /// Obtains a pointer to the return value. /// `operand` is `src_node: i32`. ret_ptr, @@ -2194,6 +2200,18 @@ pub const Inst = struct { line: u32, column: u32, }, + /// Used for unary operators which reference an inst, + /// with an AST node source location. + inst_node: struct { + /// Offset from Decl AST node index. + src_node: i32, + /// The meaning of this operand depends on the corresponding `Tag`. + inst: Index, + + pub fn src(self: @This()) LazySrcLoc { + return .{ .node_offset = self.src_node }; + } + }, // Make sure we don't accidentally add a field to make this union // bigger than expected. Note that in Debug builds, Zig is allowed @@ -2231,6 +2249,7 @@ pub const Inst = struct { @"break", switch_capture, dbg_stmt, + inst_node, }; }; @@ -2662,13 +2681,15 @@ pub const Inst = struct { }; /// Trailing: - /// 0. decl_bits: u32 // for every 8 decls + /// 0. src_node: i32, // if has_src_node + /// 1. decls_len: u32, // if has_decls_len + /// 2. decl_bits: u32 // for every 8 decls /// - sets of 4 bits: /// 0b000X: whether corresponding decl is pub /// 0b00X0: whether corresponding decl is exported /// 0b0X00: whether corresponding decl has an align expression /// 0bX000: whether corresponding decl has a linksection or an address space expression - /// 1. decl: { // for every decls_len + /// 3. decl: { // for every decls_len /// src_hash: [4]u32, // hash of source bytes /// line: u32, // line number of decl, relative to parent /// name: u32, // null terminated string index @@ -2685,7 +2706,12 @@ pub const Inst = struct { /// } /// } pub const OpaqueDecl = struct { - decls_len: u32, + pub const Small = packed struct { + has_src_node: bool, + has_decls_len: bool, + name_strategy: NameStrategy, + _: u12 = undefined, + }; }; /// Trailing: field_name: u32 // for every field: null terminated string index @@ -2937,15 +2963,6 @@ pub fn declIterator(zir: Zir, decl_inst: u32) DeclIterator { const tags = zir.instructions.items(.tag); const datas = zir.instructions.items(.data); switch (tags[decl_inst]) { - .opaque_decl, - .opaque_decl_anon, - .opaque_decl_func, - => { - const inst_data = datas[decl_inst].pl_node; - const extra = zir.extraData(Inst.OpaqueDecl, inst_data.payload_index); - return declIteratorInner(zir, extra.end, extra.data.decls_len); - }, - // Functions are allowed and yield no iterations. // There is one case matching this in the extended instruction set below. .func, @@ -3000,6 +3017,18 @@ pub fn declIterator(zir: Zir, decl_inst: u32) DeclIterator { return declIteratorInner(zir, extra_index, decls_len); }, + .opaque_decl => { + const small = @bitCast(Inst.OpaqueDecl.Small, extended.small); + var extra_index: usize = extended.operand; + extra_index += @boolToInt(small.has_src_node); + const decls_len = if (small.has_decls_len) decls_len: { + const decls_len = zir.extra[extra_index]; + extra_index += 1; + break :decls_len decls_len; + } else 0; + + return declIteratorInner(zir, extra_index, decls_len); + }, else => unreachable, } }, @@ -3037,13 +3066,6 @@ fn findDeclsInner( const datas = zir.instructions.items(.data); switch (tags[inst]) { - // Decl instructions are interesting but have no body. - // TODO yes they do have a body actually. recurse over them just like block instructions. - .opaque_decl, - .opaque_decl_anon, - .opaque_decl_func, - => return list.append(inst), - // Functions instructions are interesting and have a body. .func, .func_inferred, @@ -3071,9 +3093,12 @@ fn findDeclsInner( return zir.findDeclsBody(list, body); }, + // Decl instructions are interesting but have no body. + // TODO yes they do have a body actually. recurse over them just like block instructions. .struct_decl, .union_decl, .enum_decl, + .opaque_decl, => return list.append(inst), else => return, diff --git a/src/print_zir.zig b/src/print_zir.zig index 4527b62262..9350fd0de3 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -26,7 +26,7 @@ pub fn renderAsTextToFile( .parent_decl_node = 0, }; - const main_struct_inst = scope_file.zir.getMainStruct(); + const main_struct_inst = Zir.main_struct_inst; try fs_file.writer().print("%{d} ", .{main_struct_inst}); try writer.writeInstToStream(fs_file.writer(), main_struct_inst); try fs_file.writeAll("\n"); @@ -171,6 +171,7 @@ const Writer = struct { .ref, .ret_coerce, .ensure_err_payload_void, + .closure_capture, => try self.writeUnTok(stream, inst), .bool_br_and, @@ -307,10 +308,6 @@ const Writer = struct { .condbr_inline, => try self.writePlNodeCondBr(stream, inst), - .opaque_decl => try self.writeOpaqueDecl(stream, inst, .parent), - .opaque_decl_anon => try self.writeOpaqueDecl(stream, inst, .anon), - .opaque_decl_func => try self.writeOpaqueDecl(stream, inst, .func), - .error_set_decl => try self.writeErrorSetDecl(stream, inst, .parent), .error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon), .error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func), @@ -371,6 +368,8 @@ const Writer = struct { .dbg_stmt => try self.writeDbgStmt(stream, inst), + .closure_get => try self.writeInstNode(stream, inst), + .extended => try self.writeExtended(stream, inst), } } @@ -412,6 +411,7 @@ const Writer = struct { .struct_decl => try self.writeStructDecl(stream, extended), .union_decl => try self.writeUnionDecl(stream, extended), .enum_decl => try self.writeEnumDecl(stream, extended), + .opaque_decl => try self.writeOpaqueDecl(stream, extended), .c_undef, .c_include => { const inst_data = self.code.extraData(Zir.Inst.UnNode, extended.operand).data; @@ -745,6 +745,17 @@ const Writer = struct { try self.writeSrc(stream, src); } + fn writeInstNode( + self: *Writer, + stream: anytype, + inst: Zir.Inst.Index, + ) (@TypeOf(stream).Error || error{OutOfMemory})!void { + const inst_data = self.code.instructions.items(.data)[inst].inst_node; + try self.writeInstIndex(stream, inst_data.inst); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writeAsm(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { const extra = self.code.extraData(Zir.Inst.Asm, extended.operand); const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; @@ -1365,26 +1376,36 @@ const Writer = struct { fn writeOpaqueDecl( self: *Writer, stream: anytype, - inst: Zir.Inst.Index, - name_strategy: Zir.Inst.NameStrategy, + extended: Zir.Inst.Extended.InstData, ) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Zir.Inst.OpaqueDecl, inst_data.payload_index); - const decls_len = extra.data.decls_len; + const small = @bitCast(Zir.Inst.OpaqueDecl.Small, extended.small); + var extra_index: usize = extended.operand; - try stream.print("{s}, ", .{@tagName(name_strategy)}); + const src_node: ?i32 = if (small.has_src_node) blk: { + const src_node = @bitCast(i32, self.code.extra[extra_index]); + extra_index += 1; + break :blk src_node; + } else null; + + const decls_len = if (small.has_decls_len) blk: { + const decls_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + + try stream.print("{s}, ", .{@tagName(small.name_strategy)}); if (decls_len == 0) { - try stream.writeAll("}) "); + try stream.writeAll("{})"); } else { - try stream.writeAll("\n"); + try stream.writeAll("{\n"); self.indent += 2; - _ = try self.writeDecls(stream, decls_len, extra.end); + _ = try self.writeDecls(stream, decls_len, extra_index); self.indent -= 2; try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); + try stream.writeAll("})"); } - try self.writeSrc(stream, inst_data.src()); + try self.writeSrcNode(stream, src_node); } fn writeErrorSetDecl( diff --git a/test/behavior.zig b/test/behavior.zig index 78f3651b01..3a5c0fe589 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -1,20 +1,22 @@ const builtin = @import("builtin"); test { - _ = @import("behavior/bool.zig"); - _ = @import("behavior/basic.zig"); - _ = @import("behavior/generics.zig"); - _ = @import("behavior/eval.zig"); - _ = @import("behavior/pointers.zig"); - _ = @import("behavior/if.zig"); - _ = @import("behavior/cast.zig"); + // Tests that pass for both. _ = @import("behavior/array.zig"); - _ = @import("behavior/usingnamespace.zig"); _ = @import("behavior/atomics.zig"); + _ = @import("behavior/basic.zig"); + _ = @import("behavior/bool.zig"); + _ = @import("behavior/cast.zig"); + _ = @import("behavior/eval.zig"); + _ = @import("behavior/generics.zig"); + _ = @import("behavior/if.zig"); + _ = @import("behavior/pointers.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); - _ = @import("behavior/translate_c_macros.zig"); _ = @import("behavior/struct.zig"); + _ = @import("behavior/this.zig"); + _ = @import("behavior/translate_c_macros.zig"); _ = @import("behavior/union.zig"); + _ = @import("behavior/usingnamespace.zig"); _ = @import("behavior/widening.zig"); if (builtin.zig_is_stage2) { @@ -142,7 +144,6 @@ test { _ = @import("behavior/switch.zig"); _ = @import("behavior/switch_prong_err_enum.zig"); _ = @import("behavior/switch_prong_implicit_cast.zig"); - _ = @import("behavior/this.zig"); _ = @import("behavior/truncate.zig"); _ = @import("behavior/try.zig"); _ = @import("behavior/tuple.zig"); diff --git a/test/behavior/this.zig b/test/behavior/this.zig index 086fe2814a..0fcfd5910c 100644 --- a/test/behavior/this.zig +++ b/test/behavior/this.zig @@ -24,11 +24,10 @@ test "this refer to module call private fn" { } test "this refer to container" { - var pt = Point(i32){ - .x = 12, - .y = 34, - }; - pt.addOne(); + var pt: Point(i32) = undefined; + pt.x = 12; + pt.y = 34; + Point(i32).addOne(&pt); try expect(pt.x == 13); try expect(pt.y == 35); } From ef6fbbdab623f8eb6615eb3fb3e42141e5711331 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Thu, 23 Sep 2021 00:33:06 -0500 Subject: [PATCH 105/160] Fix the failing "bad import" test on Windows --- src/Compilation.zig | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 48c907c759..140ed40d99 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2761,16 +2761,12 @@ fn reportRetryableAstGenError( try Module.ErrorMsg.create( gpa, src_loc, - "unable to load '{'}" ++ std.fs.path.sep_str ++ "{'}': {s}", - .{ - std.zig.fmtEscapes(dir_path), - std.zig.fmtEscapes(file.sub_file_path), - @errorName(err), - }, + "unable to load '{s}" ++ std.fs.path.sep_str ++ "{s}': {s}", + .{ dir_path, file.sub_file_path, @errorName(err) }, ) else - try Module.ErrorMsg.create(gpa, src_loc, "unable to load '{'}': {s}", .{ - std.zig.fmtEscapes(file.sub_file_path), @errorName(err), + try Module.ErrorMsg.create(gpa, src_loc, "unable to load '{s}': {s}", .{ + file.sub_file_path, @errorName(err), }); errdefer err_msg.destroy(gpa); From bdbd060cc7a150ff2dfe367f3eb08eff6ab319fd Mon Sep 17 00:00:00 2001 From: Koakuma Date: Fri, 3 Sep 2021 20:29:39 +0700 Subject: [PATCH 106/160] Linux/sparc64 bits: Add missing C type definitions --- lib/std/os/linux/sparc64.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/std/os/linux/sparc64.zig b/lib/std/os/linux/sparc64.zig index 3ae490f5e9..b1f96f144c 100644 --- a/lib/std/os/linux/sparc64.zig +++ b/lib/std/os/linux/sparc64.zig @@ -674,6 +674,10 @@ pub const msghdr_const = extern struct { pub const off_t = i64; pub const ino_t = u64; pub const mode_t = u32; +pub const dev_t = usize; +pub const nlink_t = u32; +pub const blksize_t = isize; +pub const blkcnt_t = isize; // The `stat64` definition used by the kernel. pub const Stat = extern struct { From cc4d38ed574690e0b212fc47431324325edc7921 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Wed, 22 Sep 2021 22:05:06 -0400 Subject: [PATCH 107/160] ci linux: bump qemu-6.1.0.1 closes #8653 --- ci/azure/linux_script | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/azure/linux_script b/ci/azure/linux_script index 96676b928f..d24663160a 100755 --- a/ci/azure/linux_script +++ b/ci/azure/linux_script @@ -20,7 +20,7 @@ cd $HOME wget -nv "https://ziglang.org/deps/$CACHE_BASENAME.tar.xz" tar xf "$CACHE_BASENAME.tar.xz" -QEMUBASE="qemu-linux-x86_64-5.2.0.1" +QEMUBASE="qemu-linux-x86_64-6.1.0.1" wget -nv "https://ziglang.org/deps/$QEMUBASE.tar.xz" tar xf "$QEMUBASE.tar.xz" export PATH="$(pwd)/$QEMUBASE/bin:$PATH" From 418105589a2723ca372596e5893e0e1e030efe87 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 23 Sep 2021 20:01:45 -0700 Subject: [PATCH 108/160] stage2: prepare for building freestanding libc Extracts lib/std/special/c_stage1.zig from lib/std/special/c.zig. When the self-hosted compiler is further along, all the logic from c_stage1.zig will be migrated back c.zig and then c_stage1.zig will be deleted. Until then we have a simpler implementation of c.zig that only uses features already implemented in self-hosted. So far it only contains memcpy and memset, with slightly different (arguably more correct!) implementations that are compatible with self-hosted. Additionally, this commit improves the LLVM backend: * use the more efficient and convenient fnInfo() when lowering function type info. * fix incremental compilation not deleting all basic blocks of a function. * hook up calling conventions * hook up the following function attributes: - noredzone, nounwind, uwtable, minsize, optsize, sanitize_thread --- lib/std/special/c.zig | 1217 ++------------------------------- lib/std/special/c_stage1.zig | 1187 ++++++++++++++++++++++++++++++++ src/codegen/llvm.zig | 106 ++- src/codegen/llvm/bindings.zig | 53 ++ 4 files changed, 1372 insertions(+), 1191 deletions(-) create mode 100644 lib/std/special/c_stage1.zig diff --git a/lib/std/special/c.zig b/lib/std/special/c.zig index d34e7a8a46..a7821643da 100644 --- a/lib/std/special/c.zig +++ b/lib/std/special/c.zig @@ -1,177 +1,36 @@ -// This is Zig's multi-target implementation of libc. -// When builtin.link_libc is true, we need to export all the functions and -// provide an entire C API. -// Otherwise, only the functions which LLVM generates calls to need to be generated, -// such as memcpy, memset, and some math functions. +//! This is Zig's multi-target implementation of libc. +//! When builtin.link_libc is true, we need to export all the functions and +//! provide an entire C API. +//! Otherwise, only the functions which LLVM generates calls to need to be generated, +//! such as memcpy, memset, and some math functions. const std = @import("std"); -const builtin = std.builtin; -const maxInt = std.math.maxInt; -const isNan = std.math.isNan; -const native_arch = std.Target.current.cpu.arch; -const native_abi = std.Target.current.abi; -const native_os = std.Target.current.os.tag; +const builtin = @import("builtin"); +const native_os = builtin.os.tag; -const is_wasm = switch (native_arch) { - .wasm32, .wasm64 => true, - else => false, -}; -const is_msvc = switch (native_abi) { - .msvc => true, - else => false, -}; -const is_freestanding = switch (native_os) { - .freestanding => true, - else => false, -}; comptime { - if (is_freestanding and is_wasm and builtin.link_libc) { - @export(wasm_start, .{ .name = "_start", .linkage = .Strong }); + // When the self-hosted compiler is further along, all the logic from c_stage1.zig will + // be migrated to this file and then c_stage1.zig will be deleted. Until then we have a + // simpler implementation of c.zig that only uses features already implemented in self-hosted. + if (builtin.zig_is_stage2) { + @export(memset, .{ .name = "memset", .linkage = .Strong }); + @export(memcpy, .{ .name = "memcpy", .linkage = .Strong }); + } else { + _ = @import("c_stage1.zig"); } - if (builtin.link_libc) { - @export(strcmp, .{ .name = "strcmp", .linkage = .Strong }); - @export(strncmp, .{ .name = "strncmp", .linkage = .Strong }); - @export(strerror, .{ .name = "strerror", .linkage = .Strong }); - @export(strlen, .{ .name = "strlen", .linkage = .Strong }); - @export(strcpy, .{ .name = "strcpy", .linkage = .Strong }); - @export(strncpy, .{ .name = "strncpy", .linkage = .Strong }); - @export(strcat, .{ .name = "strcat", .linkage = .Strong }); - @export(strncat, .{ .name = "strncat", .linkage = .Strong }); - } else if (is_msvc) { - @export(_fltused, .{ .name = "_fltused", .linkage = .Strong }); - } -} - -var _fltused: c_int = 1; - -extern fn main(argc: c_int, argv: [*:null]?[*:0]u8) c_int; -fn wasm_start() callconv(.C) void { - _ = main(0, undefined); -} - -fn strcpy(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 { - var i: usize = 0; - while (src[i] != 0) : (i += 1) { - dest[i] = src[i]; - } - dest[i] = 0; - - return dest; -} - -test "strcpy" { - var s1: [9:0]u8 = undefined; - - s1[0] = 0; - _ = strcpy(&s1, "foobarbaz"); - try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1)); -} - -fn strncpy(dest: [*:0]u8, src: [*:0]const u8, n: usize) callconv(.C) [*:0]u8 { - var i: usize = 0; - while (i < n and src[i] != 0) : (i += 1) { - dest[i] = src[i]; - } - while (i < n) : (i += 1) { - dest[i] = 0; - } - - return dest; -} - -test "strncpy" { - var s1: [9:0]u8 = undefined; - - s1[0] = 0; - _ = strncpy(&s1, "foobarbaz", @sizeOf(@TypeOf(s1))); - try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1)); -} - -fn strcat(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 { - var dest_end: usize = 0; - while (dest[dest_end] != 0) : (dest_end += 1) {} - - var i: usize = 0; - while (src[i] != 0) : (i += 1) { - dest[dest_end + i] = src[i]; - } - dest[dest_end + i] = 0; - - return dest; -} - -test "strcat" { - var s1: [9:0]u8 = undefined; - - s1[0] = 0; - _ = strcat(&s1, "foo"); - _ = strcat(&s1, "bar"); - _ = strcat(&s1, "baz"); - try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1)); -} - -fn strncat(dest: [*:0]u8, src: [*:0]const u8, avail: usize) callconv(.C) [*:0]u8 { - var dest_end: usize = 0; - while (dest[dest_end] != 0) : (dest_end += 1) {} - - var i: usize = 0; - while (i < avail and src[i] != 0) : (i += 1) { - dest[dest_end + i] = src[i]; - } - dest[dest_end + i] = 0; - - return dest; -} - -test "strncat" { - var s1: [9:0]u8 = undefined; - - s1[0] = 0; - _ = strncat(&s1, "foo1111", 3); - _ = strncat(&s1, "bar1111", 3); - _ = strncat(&s1, "baz1111", 3); - try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1)); -} - -fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) callconv(.C) c_int { - return std.cstr.cmp(s1, s2); -} - -fn strlen(s: [*:0]const u8) callconv(.C) usize { - return std.mem.len(s); -} - -fn strncmp(_l: [*:0]const u8, _r: [*:0]const u8, _n: usize) callconv(.C) c_int { - if (_n == 0) return 0; - var l = _l; - var r = _r; - var n = _n - 1; - while (l[0] != 0 and r[0] != 0 and n != 0 and l[0] == r[0]) { - l += 1; - r += 1; - n -= 1; - } - return @as(c_int, l[0]) - @as(c_int, r[0]); -} - -fn strerror(errnum: c_int) callconv(.C) [*:0]const u8 { - _ = errnum; - return "TODO strerror implementation"; -} - -test "strncmp" { - try std.testing.expect(strncmp("a", "b", 1) == -1); - try std.testing.expect(strncmp("a", "c", 1) == -2); - try std.testing.expect(strncmp("b", "a", 1) == 1); - try std.testing.expect(strncmp("\xff", "\x02", 1) == 253); } // Avoid dragging in the runtime safety mechanisms into this .o file, // unless we're trying to test this file. -pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { +pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn { + @setCold(true); _ = error_return_trace; + if (builtin.zig_is_stage2) { + while (true) { + @breakpoint(); + } + } if (builtin.is_test) { - @setCold(true); std.debug.panic("{s}", .{msg}); } if (native_os != .freestanding and native_os != .other) { @@ -180,1028 +39,38 @@ pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn while (true) {} } -export fn memset(dest: ?[*]u8, c: u8, n: usize) callconv(.C) ?[*]u8 { +fn memset(dest: ?[*]u8, c: u8, len: usize) callconv(.C) ?[*]u8 { @setRuntimeSafety(false); - var index: usize = 0; - while (index != n) : (index += 1) - dest.?[index] = c; - - return dest; -} - -export fn __memset(dest: ?[*]u8, c: u8, n: usize, dest_n: usize) callconv(.C) ?[*]u8 { - if (dest_n < n) - @panic("buffer overflow"); - return memset(dest, c, n); -} - -export fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize) callconv(.C) ?[*]u8 { - @setRuntimeSafety(false); - - var index: usize = 0; - while (index != n) : (index += 1) - dest.?[index] = src.?[index]; - - return dest; -} - -export fn memmove(dest: ?[*]u8, src: ?[*]const u8, n: usize) callconv(.C) ?[*]u8 { - @setRuntimeSafety(false); - - if (@ptrToInt(dest) < @ptrToInt(src)) { - var index: usize = 0; - while (index != n) : (index += 1) { - dest.?[index] = src.?[index]; - } - } else { - var index = n; - while (index != 0) { - index -= 1; - dest.?[index] = src.?[index]; + if (len != 0) { + var d = dest.?; + var n = len; + while (true) { + d.* = c; + n -= 1; + if (n == 0) break; + d += 1; } } return dest; } -export fn memcmp(vl: ?[*]const u8, vr: ?[*]const u8, n: usize) callconv(.C) c_int { +fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, len: usize) callconv(.C) ?[*]u8 { @setRuntimeSafety(false); - var index: usize = 0; - while (index != n) : (index += 1) { - const compare_val = @bitCast(i8, vl.?[index] -% vr.?[index]); - if (compare_val != 0) { - return compare_val; + if (len != 0) { + var d = dest.?; + var s = src.?; + var n = len; + while (true) { + d.* = s.*; + n -= 1; + if (n == 0) break; + d += 1; + s += 1; } } - return 0; -} - -test "memcmp" { - const base_arr = &[_]u8{ 1, 1, 1 }; - const arr1 = &[_]u8{ 1, 1, 1 }; - const arr2 = &[_]u8{ 1, 0, 1 }; - const arr3 = &[_]u8{ 1, 2, 1 }; - - try std.testing.expect(memcmp(base_arr[0..], arr1[0..], base_arr.len) == 0); - try std.testing.expect(memcmp(base_arr[0..], arr2[0..], base_arr.len) > 0); - try std.testing.expect(memcmp(base_arr[0..], arr3[0..], base_arr.len) < 0); -} - -export fn bcmp(vl: [*]allowzero const u8, vr: [*]allowzero const u8, n: usize) callconv(.C) c_int { - @setRuntimeSafety(false); - - var index: usize = 0; - while (index != n) : (index += 1) { - if (vl[index] != vr[index]) { - return 1; - } - } - - return 0; -} - -test "bcmp" { - const base_arr = &[_]u8{ 1, 1, 1 }; - const arr1 = &[_]u8{ 1, 1, 1 }; - const arr2 = &[_]u8{ 1, 0, 1 }; - const arr3 = &[_]u8{ 1, 2, 1 }; - - try std.testing.expect(bcmp(base_arr[0..], arr1[0..], base_arr.len) == 0); - try std.testing.expect(bcmp(base_arr[0..], arr2[0..], base_arr.len) != 0); - try std.testing.expect(bcmp(base_arr[0..], arr3[0..], base_arr.len) != 0); -} - -comptime { - if (native_os == .linux) { - @export(clone, .{ .name = "clone" }); - } -} - -// TODO we should be able to put this directly in std/linux/x86_64.zig but -// it causes a segfault in release mode. this is a workaround of calling it -// across .o file boundaries. fix comptime @ptrCast of nakedcc functions. -fn clone() callconv(.Naked) void { - switch (native_arch) { - .i386 => { - // __clone(func, stack, flags, arg, ptid, tls, ctid) - // +8, +12, +16, +20, +24, +28, +32 - // syscall(SYS_clone, flags, stack, ptid, tls, ctid) - // eax, ebx, ecx, edx, esi, edi - asm volatile ( - \\ push %%ebp - \\ mov %%esp,%%ebp - \\ push %%ebx - \\ push %%esi - \\ push %%edi - \\ // Setup the arguments - \\ mov 16(%%ebp),%%ebx - \\ mov 12(%%ebp),%%ecx - \\ and $-16,%%ecx - \\ sub $20,%%ecx - \\ mov 20(%%ebp),%%eax - \\ mov %%eax,4(%%ecx) - \\ mov 8(%%ebp),%%eax - \\ mov %%eax,0(%%ecx) - \\ mov 24(%%ebp),%%edx - \\ mov 28(%%ebp),%%esi - \\ mov 32(%%ebp),%%edi - \\ mov $120,%%eax - \\ int $128 - \\ test %%eax,%%eax - \\ jnz 1f - \\ pop %%eax - \\ xor %%ebp,%%ebp - \\ call *%%eax - \\ mov %%eax,%%ebx - \\ xor %%eax,%%eax - \\ inc %%eax - \\ int $128 - \\ hlt - \\1: - \\ pop %%edi - \\ pop %%esi - \\ pop %%ebx - \\ pop %%ebp - \\ ret - ); - }, - .x86_64 => { - asm volatile ( - \\ xor %%eax,%%eax - \\ mov $56,%%al // SYS_clone - \\ mov %%rdi,%%r11 - \\ mov %%rdx,%%rdi - \\ mov %%r8,%%rdx - \\ mov %%r9,%%r8 - \\ mov 8(%%rsp),%%r10 - \\ mov %%r11,%%r9 - \\ and $-16,%%rsi - \\ sub $8,%%rsi - \\ mov %%rcx,(%%rsi) - \\ syscall - \\ test %%eax,%%eax - \\ jnz 1f - \\ xor %%ebp,%%ebp - \\ pop %%rdi - \\ call *%%r9 - \\ mov %%eax,%%edi - \\ xor %%eax,%%eax - \\ mov $60,%%al // SYS_exit - \\ syscall - \\ hlt - \\1: ret - \\ - ); - }, - .aarch64 => { - // __clone(func, stack, flags, arg, ptid, tls, ctid) - // x0, x1, w2, x3, x4, x5, x6 - - // syscall(SYS_clone, flags, stack, ptid, tls, ctid) - // x8, x0, x1, x2, x3, x4 - asm volatile ( - \\ // align stack and save func,arg - \\ and x1,x1,#-16 - \\ stp x0,x3,[x1,#-16]! - \\ - \\ // syscall - \\ uxtw x0,w2 - \\ mov x2,x4 - \\ mov x3,x5 - \\ mov x4,x6 - \\ mov x8,#220 // SYS_clone - \\ svc #0 - \\ - \\ cbz x0,1f - \\ // parent - \\ ret - \\ // child - \\1: ldp x1,x0,[sp],#16 - \\ blr x1 - \\ mov x8,#93 // SYS_exit - \\ svc #0 - ); - }, - .arm, .thumb => { - // __clone(func, stack, flags, arg, ptid, tls, ctid) - // r0, r1, r2, r3, +0, +4, +8 - - // syscall(SYS_clone, flags, stack, ptid, tls, ctid) - // r7 r0, r1, r2, r3, r4 - asm volatile ( - \\ stmfd sp!,{r4,r5,r6,r7} - \\ mov r7,#120 - \\ mov r6,r3 - \\ mov r5,r0 - \\ mov r0,r2 - \\ and r1,r1,#-16 - \\ ldr r2,[sp,#16] - \\ ldr r3,[sp,#20] - \\ ldr r4,[sp,#24] - \\ svc 0 - \\ tst r0,r0 - \\ beq 1f - \\ ldmfd sp!,{r4,r5,r6,r7} - \\ bx lr - \\ - \\1: mov r0,r6 - \\ bl 3f - \\2: mov r7,#1 - \\ svc 0 - \\ b 2b - \\3: bx r5 - ); - }, - .riscv64 => { - // __clone(func, stack, flags, arg, ptid, tls, ctid) - // a0, a1, a2, a3, a4, a5, a6 - - // syscall(SYS_clone, flags, stack, ptid, tls, ctid) - // a7 a0, a1, a2, a3, a4 - asm volatile ( - \\ # Save func and arg to stack - \\ addi a1, a1, -16 - \\ sd a0, 0(a1) - \\ sd a3, 8(a1) - \\ - \\ # Call SYS_clone - \\ mv a0, a2 - \\ mv a2, a4 - \\ mv a3, a5 - \\ mv a4, a6 - \\ li a7, 220 # SYS_clone - \\ ecall - \\ - \\ beqz a0, 1f - \\ # Parent - \\ ret - \\ - \\ # Child - \\1: ld a1, 0(sp) - \\ ld a0, 8(sp) - \\ jalr a1 - \\ - \\ # Exit - \\ li a7, 93 # SYS_exit - \\ ecall - ); - }, - .mips, .mipsel => { - // __clone(func, stack, flags, arg, ptid, tls, ctid) - // 3, 4, 5, 6, 7, 8, 9 - - // syscall(SYS_clone, flags, stack, ptid, tls, ctid) - // 2 4, 5, 6, 7, 8 - asm volatile ( - \\ # Save function pointer and argument pointer on new thread stack - \\ and $5, $5, -8 - \\ subu $5, $5, 16 - \\ sw $4, 0($5) - \\ sw $7, 4($5) - \\ # Shuffle (fn,sp,fl,arg,ptid,tls,ctid) to (fl,sp,ptid,tls,ctid) - \\ move $4, $6 - \\ lw $6, 16($sp) - \\ lw $7, 20($sp) - \\ lw $9, 24($sp) - \\ subu $sp, $sp, 16 - \\ sw $9, 16($sp) - \\ li $2, 4120 - \\ syscall - \\ beq $7, $0, 1f - \\ nop - \\ addu $sp, $sp, 16 - \\ jr $ra - \\ subu $2, $0, $2 - \\1: - \\ beq $2, $0, 1f - \\ nop - \\ addu $sp, $sp, 16 - \\ jr $ra - \\ nop - \\1: - \\ lw $25, 0($sp) - \\ lw $4, 4($sp) - \\ jalr $25 - \\ nop - \\ move $4, $2 - \\ li $2, 4001 - \\ syscall - ); - }, - .powerpc => { - // __clone(func, stack, flags, arg, ptid, tls, ctid) - // 3, 4, 5, 6, 7, 8, 9 - - // syscall(SYS_clone, flags, stack, ptid, tls, ctid) - // 0 3, 4, 5, 6, 7 - asm volatile ( - \\# store non-volatile regs r30, r31 on stack in order to put our - \\# start func and its arg there - \\stwu 30, -16(1) - \\stw 31, 4(1) - \\ - \\# save r3 (func) into r30, and r6(arg) into r31 - \\mr 30, 3 - \\mr 31, 6 - \\ - \\# create initial stack frame for new thread - \\clrrwi 4, 4, 4 - \\li 0, 0 - \\stwu 0, -16(4) - \\ - \\#move c into first arg - \\mr 3, 5 - \\#mr 4, 4 - \\mr 5, 7 - \\mr 6, 8 - \\mr 7, 9 - \\ - \\# move syscall number into r0 - \\li 0, 120 - \\ - \\sc - \\ - \\# check for syscall error - \\bns+ 1f # jump to label 1 if no summary overflow. - \\#else - \\neg 3, 3 #negate the result (errno) - \\1: - \\# compare sc result with 0 - \\cmpwi cr7, 3, 0 - \\ - \\# if not 0, jump to end - \\bne cr7, 2f - \\ - \\#else: we're the child - \\#call funcptr: move arg (d) into r3 - \\mr 3, 31 - \\#move r30 (funcptr) into CTR reg - \\mtctr 30 - \\# call CTR reg - \\bctrl - \\# mov SYS_exit into r0 (the exit param is already in r3) - \\li 0, 1 - \\sc - \\ - \\2: - \\ - \\# restore stack - \\lwz 30, 0(1) - \\lwz 31, 4(1) - \\addi 1, 1, 16 - \\ - \\blr - ); - }, - .powerpc64, .powerpc64le => { - // __clone(func, stack, flags, arg, ptid, tls, ctid) - // 3, 4, 5, 6, 7, 8, 9 - - // syscall(SYS_clone, flags, stack, ptid, tls, ctid) - // 0 3, 4, 5, 6, 7 - asm volatile ( - \\ # create initial stack frame for new thread - \\ clrrdi 4, 4, 4 - \\ li 0, 0 - \\ stdu 0,-32(4) - \\ - \\ # save fn and arg to child stack - \\ std 3, 8(4) - \\ std 6, 16(4) - \\ - \\ # shuffle args into correct registers and call SYS_clone - \\ mr 3, 5 - \\ #mr 4, 4 - \\ mr 5, 7 - \\ mr 6, 8 - \\ mr 7, 9 - \\ li 0, 120 # SYS_clone = 120 - \\ sc - \\ - \\ # if error, negate return (errno) - \\ bns+ 1f - \\ neg 3, 3 - \\ - \\1: - \\ # if we're the parent, return - \\ cmpwi cr7, 3, 0 - \\ bnelr cr7 - \\ - \\ # we're the child. call fn(arg) - \\ ld 3, 16(1) - \\ ld 12, 8(1) - \\ mtctr 12 - \\ bctrl - \\ - \\ # call SYS_exit. exit code is already in r3 from fn return value - \\ li 0, 1 # SYS_exit = 1 - \\ sc - ); - }, - .sparcv9 => { - // __clone(func, stack, flags, arg, ptid, tls, ctid) - // i0, i1, i2, i3, i4, i5, sp - // syscall(SYS_clone, flags, stack, ptid, tls, ctid) - // g1 o0, o1, o2, o3, o4 - asm volatile ( - \\ save %%sp, -192, %%sp - \\ # Save the func pointer and the arg pointer - \\ mov %%i0, %%g2 - \\ mov %%i3, %%g3 - \\ # Shuffle the arguments - \\ mov 217, %%g1 - \\ mov %%i2, %%o0 - \\ # Add some extra space for the initial frame - \\ sub %%i1, 176 + 2047, %%o1 - \\ mov %%i4, %%o2 - \\ mov %%i5, %%o3 - \\ ldx [%%fp + 0x8af], %%o4 - \\ t 0x6d - \\ bcs,pn %%xcc, 2f - \\ nop - \\ # The child pid is returned in o0 while o1 tells if this - \\ # process is # the child (=1) or the parent (=0). - \\ brnz %%o1, 1f - \\ nop - \\ # Parent process, return the child pid - \\ mov %%o0, %%i0 - \\ ret - \\ restore - \\1: - \\ # Child process, call func(arg) - \\ mov %%g0, %%fp - \\ call %%g2 - \\ mov %%g3, %%o0 - \\ # Exit - \\ mov 1, %%g1 - \\ t 0x6d - \\2: - \\ # The syscall failed - \\ sub %%g0, %%o0, %%i0 - \\ ret - \\ restore - ); - }, - else => @compileError("Implement clone() for this arch."), - } -} - -const math = std.math; - -export fn fmodf(x: f32, y: f32) f32 { - return generic_fmod(f32, x, y); -} -export fn fmod(x: f64, y: f64) f64 { - return generic_fmod(f64, x, y); -} - -// TODO add intrinsics for these (and probably the double version too) -// and have the math stuff use the intrinsic. same as @mod and @rem -export fn floorf(x: f32) f32 { - return math.floor(x); -} - -export fn ceilf(x: f32) f32 { - return math.ceil(x); -} - -export fn floor(x: f64) f64 { - return math.floor(x); -} - -export fn ceil(x: f64) f64 { - return math.ceil(x); -} - -export fn fma(a: f64, b: f64, c: f64) f64 { - return math.fma(f64, a, b, c); -} - -export fn fmaf(a: f32, b: f32, c: f32) f32 { - return math.fma(f32, a, b, c); -} - -export fn sin(a: f64) f64 { - return math.sin(a); -} - -export fn sinf(a: f32) f32 { - return math.sin(a); -} - -export fn cos(a: f64) f64 { - return math.cos(a); -} - -export fn cosf(a: f32) f32 { - return math.cos(a); -} - -export fn sincos(a: f64, r_sin: *f64, r_cos: *f64) void { - r_sin.* = math.sin(a); - r_cos.* = math.cos(a); -} - -export fn sincosf(a: f32, r_sin: *f32, r_cos: *f32) void { - r_sin.* = math.sin(a); - r_cos.* = math.cos(a); -} - -export fn exp(a: f64) f64 { - return math.exp(a); -} - -export fn expf(a: f32) f32 { - return math.exp(a); -} - -export fn exp2(a: f64) f64 { - return math.exp2(a); -} - -export fn exp2f(a: f32) f32 { - return math.exp2(a); -} - -export fn log(a: f64) f64 { - return math.ln(a); -} - -export fn logf(a: f32) f32 { - return math.ln(a); -} - -export fn log2(a: f64) f64 { - return math.log2(a); -} - -export fn log2f(a: f32) f32 { - return math.log2(a); -} - -export fn log10(a: f64) f64 { - return math.log10(a); -} - -export fn log10f(a: f32) f32 { - return math.log10(a); -} - -export fn fabs(a: f64) f64 { - return math.fabs(a); -} - -export fn fabsf(a: f32) f32 { - return math.fabs(a); -} - -export fn trunc(a: f64) f64 { - return math.trunc(a); -} - -export fn truncf(a: f32) f32 { - return math.trunc(a); -} - -export fn round(a: f64) f64 { - return math.round(a); -} - -export fn roundf(a: f32) f32 { - return math.round(a); -} - -fn generic_fmod(comptime T: type, x: T, y: T) T { - @setRuntimeSafety(false); - - const bits = @typeInfo(T).Float.bits; - const uint = std.meta.Int(.unsigned, bits); - const log2uint = math.Log2Int(uint); - const digits = if (T == f32) 23 else 52; - const exp_bits = if (T == f32) 9 else 12; - const bits_minus_1 = bits - 1; - const mask = if (T == f32) 0xff else 0x7ff; - var ux = @bitCast(uint, x); - var uy = @bitCast(uint, y); - var ex = @intCast(i32, (ux >> digits) & mask); - var ey = @intCast(i32, (uy >> digits) & mask); - const sx = if (T == f32) @intCast(u32, ux & 0x80000000) else @intCast(i32, ux >> bits_minus_1); - var i: uint = undefined; - - if (uy << 1 == 0 or isNan(@bitCast(T, uy)) or ex == mask) - return (x * y) / (x * y); - - if (ux << 1 <= uy << 1) { - if (ux << 1 == uy << 1) - return 0 * x; - return x; - } - - // normalize x and y - if (ex == 0) { - i = ux << exp_bits; - while (i >> bits_minus_1 == 0) : ({ - ex -= 1; - i <<= 1; - }) {} - ux <<= @intCast(log2uint, @bitCast(u32, -ex + 1)); - } else { - ux &= maxInt(uint) >> exp_bits; - ux |= 1 << digits; - } - if (ey == 0) { - i = uy << exp_bits; - while (i >> bits_minus_1 == 0) : ({ - ey -= 1; - i <<= 1; - }) {} - uy <<= @intCast(log2uint, @bitCast(u32, -ey + 1)); - } else { - uy &= maxInt(uint) >> exp_bits; - uy |= 1 << digits; - } - - // x mod y - while (ex > ey) : (ex -= 1) { - i = ux -% uy; - if (i >> bits_minus_1 == 0) { - if (i == 0) - return 0 * x; - ux = i; - } - ux <<= 1; - } - i = ux -% uy; - if (i >> bits_minus_1 == 0) { - if (i == 0) - return 0 * x; - ux = i; - } - while (ux >> digits == 0) : ({ - ux <<= 1; - ex -= 1; - }) {} - - // scale result up - if (ex > 0) { - ux -%= 1 << digits; - ux |= @as(uint, @bitCast(u32, ex)) << digits; - } else { - ux >>= @intCast(log2uint, @bitCast(u32, -ex + 1)); - } - if (T == f32) { - ux |= sx; - } else { - ux |= @intCast(uint, sx) << bits_minus_1; - } - return @bitCast(T, ux); -} - -test "fmod, fmodf" { - inline for ([_]type{ f32, f64 }) |T| { - const nan_val = math.nan(T); - const inf_val = math.inf(T); - - try std.testing.expect(isNan(generic_fmod(T, nan_val, 1.0))); - try std.testing.expect(isNan(generic_fmod(T, 1.0, nan_val))); - try std.testing.expect(isNan(generic_fmod(T, inf_val, 1.0))); - try std.testing.expect(isNan(generic_fmod(T, 0.0, 0.0))); - try std.testing.expect(isNan(generic_fmod(T, 1.0, 0.0))); - - try std.testing.expectEqual(@as(T, 0.0), generic_fmod(T, 0.0, 2.0)); - try std.testing.expectEqual(@as(T, -0.0), generic_fmod(T, -0.0, 2.0)); - - try std.testing.expectEqual(@as(T, -2.0), generic_fmod(T, -32.0, 10.0)); - try std.testing.expectEqual(@as(T, -2.0), generic_fmod(T, -32.0, -10.0)); - try std.testing.expectEqual(@as(T, 2.0), generic_fmod(T, 32.0, 10.0)); - try std.testing.expectEqual(@as(T, 2.0), generic_fmod(T, 32.0, -10.0)); - } -} - -fn generic_fmin(comptime T: type, x: T, y: T) T { - if (isNan(x)) - return y; - if (isNan(y)) - return x; - return if (x < y) x else y; -} - -export fn fminf(x: f32, y: f32) callconv(.C) f32 { - return generic_fmin(f32, x, y); -} - -export fn fmin(x: f64, y: f64) callconv(.C) f64 { - return generic_fmin(f64, x, y); -} - -test "fmin, fminf" { - inline for ([_]type{ f32, f64 }) |T| { - const nan_val = math.nan(T); - - try std.testing.expect(isNan(generic_fmin(T, nan_val, nan_val))); - try std.testing.expectEqual(@as(T, 1.0), generic_fmin(T, nan_val, 1.0)); - try std.testing.expectEqual(@as(T, 1.0), generic_fmin(T, 1.0, nan_val)); - - try std.testing.expectEqual(@as(T, 1.0), generic_fmin(T, 1.0, 10.0)); - try std.testing.expectEqual(@as(T, -1.0), generic_fmin(T, 1.0, -1.0)); - } -} - -fn generic_fmax(comptime T: type, x: T, y: T) T { - if (isNan(x)) - return y; - if (isNan(y)) - return x; - return if (x < y) y else x; -} - -export fn fmaxf(x: f32, y: f32) callconv(.C) f32 { - return generic_fmax(f32, x, y); -} - -export fn fmax(x: f64, y: f64) callconv(.C) f64 { - return generic_fmax(f64, x, y); -} - -test "fmax, fmaxf" { - inline for ([_]type{ f32, f64 }) |T| { - const nan_val = math.nan(T); - - try std.testing.expect(isNan(generic_fmax(T, nan_val, nan_val))); - try std.testing.expectEqual(@as(T, 1.0), generic_fmax(T, nan_val, 1.0)); - try std.testing.expectEqual(@as(T, 1.0), generic_fmax(T, 1.0, nan_val)); - - try std.testing.expectEqual(@as(T, 10.0), generic_fmax(T, 1.0, 10.0)); - try std.testing.expectEqual(@as(T, 1.0), generic_fmax(T, 1.0, -1.0)); - } -} - -// NOTE: The original code is full of implicit signed -> unsigned assumptions and u32 wraparound -// behaviour. Most intermediate i32 values are changed to u32 where appropriate but there are -// potentially some edge cases remaining that are not handled in the same way. -export fn sqrt(x: f64) f64 { - const tiny: f64 = 1.0e-300; - const sign: u32 = 0x80000000; - const u = @bitCast(u64, x); - - var ix0 = @intCast(u32, u >> 32); - var ix1 = @intCast(u32, u & 0xFFFFFFFF); - - // sqrt(nan) = nan, sqrt(+inf) = +inf, sqrt(-inf) = nan - if (ix0 & 0x7FF00000 == 0x7FF00000) { - return x * x + x; - } - - // sqrt(+-0) = +-0 - if (x == 0.0) { - return x; - } - // sqrt(-ve) = snan - if (ix0 & sign != 0) { - return math.snan(f64); - } - - // normalize x - var m = @intCast(i32, ix0 >> 20); - if (m == 0) { - // subnormal - while (ix0 == 0) { - m -= 21; - ix0 |= ix1 >> 11; - ix1 <<= 21; - } - - // subnormal - var i: u32 = 0; - while (ix0 & 0x00100000 == 0) : (i += 1) { - ix0 <<= 1; - } - m -= @intCast(i32, i) - 1; - ix0 |= ix1 >> @intCast(u5, 32 - i); - ix1 <<= @intCast(u5, i); - } - - // unbias exponent - m -= 1023; - ix0 = (ix0 & 0x000FFFFF) | 0x00100000; - if (m & 1 != 0) { - ix0 += ix0 + (ix1 >> 31); - ix1 = ix1 +% ix1; - } - m >>= 1; - - // sqrt(x) bit by bit - ix0 += ix0 + (ix1 >> 31); - ix1 = ix1 +% ix1; - - var q: u32 = 0; - var q1: u32 = 0; - var s0: u32 = 0; - var s1: u32 = 0; - var r: u32 = 0x00200000; - var t: u32 = undefined; - var t1: u32 = undefined; - - while (r != 0) { - t = s0 +% r; - if (t <= ix0) { - s0 = t + r; - ix0 -= t; - q += r; - } - ix0 = ix0 +% ix0 +% (ix1 >> 31); - ix1 = ix1 +% ix1; - r >>= 1; - } - - r = sign; - while (r != 0) { - t1 = s1 +% r; - t = s0; - if (t < ix0 or (t == ix0 and t1 <= ix1)) { - s1 = t1 +% r; - if (t1 & sign == sign and s1 & sign == 0) { - s0 += 1; - } - ix0 -= t; - if (ix1 < t1) { - ix0 -= 1; - } - ix1 = ix1 -% t1; - q1 += r; - } - ix0 = ix0 +% ix0 +% (ix1 >> 31); - ix1 = ix1 +% ix1; - r >>= 1; - } - - // rounding direction - if (ix0 | ix1 != 0) { - var z = 1.0 - tiny; // raise inexact - if (z >= 1.0) { - z = 1.0 + tiny; - if (q1 == 0xFFFFFFFF) { - q1 = 0; - q += 1; - } else if (z > 1.0) { - if (q1 == 0xFFFFFFFE) { - q += 1; - } - q1 += 2; - } else { - q1 += q1 & 1; - } - } - } - - ix0 = (q >> 1) + 0x3FE00000; - ix1 = q1 >> 1; - if (q & 1 != 0) { - ix1 |= 0x80000000; - } - - // NOTE: musl here appears to rely on signed twos-complement wraparound. +% has the same - // behaviour at least. - var iix0 = @intCast(i32, ix0); - iix0 = iix0 +% (m << 20); - - const uz = (@intCast(u64, iix0) << 32) | ix1; - return @bitCast(f64, uz); -} - -test "sqrt" { - const V = [_]f64{ - 0.0, - 4.089288054930154, - 7.538757127071935, - 8.97780793672623, - 5.304443821913729, - 5.682408965311888, - 0.5846878579110049, - 3.650338664297043, - 0.3178091951800732, - 7.1505232436382835, - 3.6589165881946464, - }; - - // Note that @sqrt will either generate the sqrt opcode (if supported by the - // target ISA) or a call to `sqrtf` otherwise. - for (V) |val| - try std.testing.expectEqual(@sqrt(val), sqrt(val)); -} - -test "sqrt special" { - try std.testing.expect(std.math.isPositiveInf(sqrt(std.math.inf(f64)))); - try std.testing.expect(sqrt(0.0) == 0.0); - try std.testing.expect(sqrt(-0.0) == -0.0); - try std.testing.expect(isNan(sqrt(-1.0))); - try std.testing.expect(isNan(sqrt(std.math.nan(f64)))); -} - -export fn sqrtf(x: f32) f32 { - const tiny: f32 = 1.0e-30; - const sign: i32 = @bitCast(i32, @as(u32, 0x80000000)); - var ix: i32 = @bitCast(i32, x); - - if ((ix & 0x7F800000) == 0x7F800000) { - return x * x + x; // sqrt(nan) = nan, sqrt(+inf) = +inf, sqrt(-inf) = snan - } - - // zero - if (ix <= 0) { - if (ix & ~sign == 0) { - return x; // sqrt (+-0) = +-0 - } - if (ix < 0) { - return math.snan(f32); - } - } - - // normalize - var m = ix >> 23; - if (m == 0) { - // subnormal - var i: i32 = 0; - while (ix & 0x00800000 == 0) : (i += 1) { - ix <<= 1; - } - m -= i - 1; - } - - m -= 127; // unbias exponent - ix = (ix & 0x007FFFFF) | 0x00800000; - - if (m & 1 != 0) { // odd m, double x to even - ix += ix; - } - - m >>= 1; // m = [m / 2] - - // sqrt(x) bit by bit - ix += ix; - var q: i32 = 0; // q = sqrt(x) - var s: i32 = 0; - var r: i32 = 0x01000000; // r = moving bit right -> left - - while (r != 0) { - const t = s + r; - if (t <= ix) { - s = t + r; - ix -= t; - q += r; - } - ix += ix; - r >>= 1; - } - - // floating add to find rounding direction - if (ix != 0) { - var z = 1.0 - tiny; // inexact - if (z >= 1.0) { - z = 1.0 + tiny; - if (z > 1.0) { - q += 2; - } else { - if (q & 1 != 0) { - q += 1; - } - } - } - } - - ix = (q >> 1) + 0x3f000000; - ix += m << 23; - return @bitCast(f32, ix); -} - -test "sqrtf" { - const V = [_]f32{ - 0.0, - 4.089288054930154, - 7.538757127071935, - 8.97780793672623, - 5.304443821913729, - 5.682408965311888, - 0.5846878579110049, - 3.650338664297043, - 0.3178091951800732, - 7.1505232436382835, - 3.6589165881946464, - }; - - // Note that @sqrt will either generate the sqrt opcode (if supported by the - // target ISA) or a call to `sqrtf` otherwise. - for (V) |val| - try std.testing.expectEqual(@sqrt(val), sqrtf(val)); -} - -test "sqrtf special" { - try std.testing.expect(std.math.isPositiveInf(sqrtf(std.math.inf(f32)))); - try std.testing.expect(sqrtf(0.0) == 0.0); - try std.testing.expect(sqrtf(-0.0) == -0.0); - try std.testing.expect(isNan(sqrtf(-1.0))); - try std.testing.expect(isNan(sqrtf(std.math.nan(f32)))); + return dest; } diff --git a/lib/std/special/c_stage1.zig b/lib/std/special/c_stage1.zig new file mode 100644 index 0000000000..7ea2b95704 --- /dev/null +++ b/lib/std/special/c_stage1.zig @@ -0,0 +1,1187 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const maxInt = std.math.maxInt; +const isNan = std.math.isNan; +const native_arch = builtin.cpu.arch; +const native_abi = builtin.abi; +const native_os = builtin.os.tag; + +const is_wasm = switch (native_arch) { + .wasm32, .wasm64 => true, + else => false, +}; +const is_msvc = switch (native_abi) { + .msvc => true, + else => false, +}; +const is_freestanding = switch (native_os) { + .freestanding => true, + else => false, +}; +comptime { + if (is_freestanding and is_wasm and builtin.link_libc) { + @export(wasm_start, .{ .name = "_start", .linkage = .Strong }); + } + if (builtin.link_libc) { + @export(strcmp, .{ .name = "strcmp", .linkage = .Strong }); + @export(strncmp, .{ .name = "strncmp", .linkage = .Strong }); + @export(strerror, .{ .name = "strerror", .linkage = .Strong }); + @export(strlen, .{ .name = "strlen", .linkage = .Strong }); + @export(strcpy, .{ .name = "strcpy", .linkage = .Strong }); + @export(strncpy, .{ .name = "strncpy", .linkage = .Strong }); + @export(strcat, .{ .name = "strcat", .linkage = .Strong }); + @export(strncat, .{ .name = "strncat", .linkage = .Strong }); + } else if (is_msvc) { + @export(_fltused, .{ .name = "_fltused", .linkage = .Strong }); + } +} + +var _fltused: c_int = 1; + +extern fn main(argc: c_int, argv: [*:null]?[*:0]u8) c_int; +fn wasm_start() callconv(.C) void { + _ = main(0, undefined); +} + +fn strcpy(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 { + var i: usize = 0; + while (src[i] != 0) : (i += 1) { + dest[i] = src[i]; + } + dest[i] = 0; + + return dest; +} + +test "strcpy" { + var s1: [9:0]u8 = undefined; + + s1[0] = 0; + _ = strcpy(&s1, "foobarbaz"); + try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1)); +} + +fn strncpy(dest: [*:0]u8, src: [*:0]const u8, n: usize) callconv(.C) [*:0]u8 { + var i: usize = 0; + while (i < n and src[i] != 0) : (i += 1) { + dest[i] = src[i]; + } + while (i < n) : (i += 1) { + dest[i] = 0; + } + + return dest; +} + +test "strncpy" { + var s1: [9:0]u8 = undefined; + + s1[0] = 0; + _ = strncpy(&s1, "foobarbaz", @sizeOf(@TypeOf(s1))); + try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1)); +} + +fn strcat(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 { + var dest_end: usize = 0; + while (dest[dest_end] != 0) : (dest_end += 1) {} + + var i: usize = 0; + while (src[i] != 0) : (i += 1) { + dest[dest_end + i] = src[i]; + } + dest[dest_end + i] = 0; + + return dest; +} + +test "strcat" { + var s1: [9:0]u8 = undefined; + + s1[0] = 0; + _ = strcat(&s1, "foo"); + _ = strcat(&s1, "bar"); + _ = strcat(&s1, "baz"); + try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1)); +} + +fn strncat(dest: [*:0]u8, src: [*:0]const u8, avail: usize) callconv(.C) [*:0]u8 { + var dest_end: usize = 0; + while (dest[dest_end] != 0) : (dest_end += 1) {} + + var i: usize = 0; + while (i < avail and src[i] != 0) : (i += 1) { + dest[dest_end + i] = src[i]; + } + dest[dest_end + i] = 0; + + return dest; +} + +test "strncat" { + var s1: [9:0]u8 = undefined; + + s1[0] = 0; + _ = strncat(&s1, "foo1111", 3); + _ = strncat(&s1, "bar1111", 3); + _ = strncat(&s1, "baz1111", 3); + try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1)); +} + +fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) callconv(.C) c_int { + return std.cstr.cmp(s1, s2); +} + +fn strlen(s: [*:0]const u8) callconv(.C) usize { + return std.mem.len(s); +} + +fn strncmp(_l: [*:0]const u8, _r: [*:0]const u8, _n: usize) callconv(.C) c_int { + if (_n == 0) return 0; + var l = _l; + var r = _r; + var n = _n - 1; + while (l[0] != 0 and r[0] != 0 and n != 0 and l[0] == r[0]) { + l += 1; + r += 1; + n -= 1; + } + return @as(c_int, l[0]) - @as(c_int, r[0]); +} + +fn strerror(errnum: c_int) callconv(.C) [*:0]const u8 { + _ = errnum; + return "TODO strerror implementation"; +} + +test "strncmp" { + try std.testing.expect(strncmp("a", "b", 1) == -1); + try std.testing.expect(strncmp("a", "c", 1) == -2); + try std.testing.expect(strncmp("b", "a", 1) == 1); + try std.testing.expect(strncmp("\xff", "\x02", 1) == 253); +} + +export fn memset(dest: ?[*]u8, c: u8, n: usize) callconv(.C) ?[*]u8 { + @setRuntimeSafety(false); + + var index: usize = 0; + while (index != n) : (index += 1) + dest.?[index] = c; + + return dest; +} + +export fn __memset(dest: ?[*]u8, c: u8, n: usize, dest_n: usize) callconv(.C) ?[*]u8 { + if (dest_n < n) + @panic("buffer overflow"); + return memset(dest, c, n); +} + +export fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize) callconv(.C) ?[*]u8 { + @setRuntimeSafety(false); + + var index: usize = 0; + while (index != n) : (index += 1) + dest.?[index] = src.?[index]; + + return dest; +} + +export fn memmove(dest: ?[*]u8, src: ?[*]const u8, n: usize) callconv(.C) ?[*]u8 { + @setRuntimeSafety(false); + + if (@ptrToInt(dest) < @ptrToInt(src)) { + var index: usize = 0; + while (index != n) : (index += 1) { + dest.?[index] = src.?[index]; + } + } else { + var index = n; + while (index != 0) { + index -= 1; + dest.?[index] = src.?[index]; + } + } + + return dest; +} + +export fn memcmp(vl: ?[*]const u8, vr: ?[*]const u8, n: usize) callconv(.C) c_int { + @setRuntimeSafety(false); + + var index: usize = 0; + while (index != n) : (index += 1) { + const compare_val = @bitCast(i8, vl.?[index] -% vr.?[index]); + if (compare_val != 0) { + return compare_val; + } + } + + return 0; +} + +test "memcmp" { + const base_arr = &[_]u8{ 1, 1, 1 }; + const arr1 = &[_]u8{ 1, 1, 1 }; + const arr2 = &[_]u8{ 1, 0, 1 }; + const arr3 = &[_]u8{ 1, 2, 1 }; + + try std.testing.expect(memcmp(base_arr[0..], arr1[0..], base_arr.len) == 0); + try std.testing.expect(memcmp(base_arr[0..], arr2[0..], base_arr.len) > 0); + try std.testing.expect(memcmp(base_arr[0..], arr3[0..], base_arr.len) < 0); +} + +export fn bcmp(vl: [*]allowzero const u8, vr: [*]allowzero const u8, n: usize) callconv(.C) c_int { + @setRuntimeSafety(false); + + var index: usize = 0; + while (index != n) : (index += 1) { + if (vl[index] != vr[index]) { + return 1; + } + } + + return 0; +} + +test "bcmp" { + const base_arr = &[_]u8{ 1, 1, 1 }; + const arr1 = &[_]u8{ 1, 1, 1 }; + const arr2 = &[_]u8{ 1, 0, 1 }; + const arr3 = &[_]u8{ 1, 2, 1 }; + + try std.testing.expect(bcmp(base_arr[0..], arr1[0..], base_arr.len) == 0); + try std.testing.expect(bcmp(base_arr[0..], arr2[0..], base_arr.len) != 0); + try std.testing.expect(bcmp(base_arr[0..], arr3[0..], base_arr.len) != 0); +} + +comptime { + if (native_os == .linux) { + @export(clone, .{ .name = "clone" }); + } +} + +// TODO we should be able to put this directly in std/linux/x86_64.zig but +// it causes a segfault in release mode. this is a workaround of calling it +// across .o file boundaries. fix comptime @ptrCast of nakedcc functions. +fn clone() callconv(.Naked) void { + switch (native_arch) { + .i386 => { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // +8, +12, +16, +20, +24, +28, +32 + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // eax, ebx, ecx, edx, esi, edi + asm volatile ( + \\ push %%ebp + \\ mov %%esp,%%ebp + \\ push %%ebx + \\ push %%esi + \\ push %%edi + \\ // Setup the arguments + \\ mov 16(%%ebp),%%ebx + \\ mov 12(%%ebp),%%ecx + \\ and $-16,%%ecx + \\ sub $20,%%ecx + \\ mov 20(%%ebp),%%eax + \\ mov %%eax,4(%%ecx) + \\ mov 8(%%ebp),%%eax + \\ mov %%eax,0(%%ecx) + \\ mov 24(%%ebp),%%edx + \\ mov 28(%%ebp),%%esi + \\ mov 32(%%ebp),%%edi + \\ mov $120,%%eax + \\ int $128 + \\ test %%eax,%%eax + \\ jnz 1f + \\ pop %%eax + \\ xor %%ebp,%%ebp + \\ call *%%eax + \\ mov %%eax,%%ebx + \\ xor %%eax,%%eax + \\ inc %%eax + \\ int $128 + \\ hlt + \\1: + \\ pop %%edi + \\ pop %%esi + \\ pop %%ebx + \\ pop %%ebp + \\ ret + ); + }, + .x86_64 => { + asm volatile ( + \\ xor %%eax,%%eax + \\ mov $56,%%al // SYS_clone + \\ mov %%rdi,%%r11 + \\ mov %%rdx,%%rdi + \\ mov %%r8,%%rdx + \\ mov %%r9,%%r8 + \\ mov 8(%%rsp),%%r10 + \\ mov %%r11,%%r9 + \\ and $-16,%%rsi + \\ sub $8,%%rsi + \\ mov %%rcx,(%%rsi) + \\ syscall + \\ test %%eax,%%eax + \\ jnz 1f + \\ xor %%ebp,%%ebp + \\ pop %%rdi + \\ call *%%r9 + \\ mov %%eax,%%edi + \\ xor %%eax,%%eax + \\ mov $60,%%al // SYS_exit + \\ syscall + \\ hlt + \\1: ret + \\ + ); + }, + .aarch64 => { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // x0, x1, w2, x3, x4, x5, x6 + + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // x8, x0, x1, x2, x3, x4 + asm volatile ( + \\ // align stack and save func,arg + \\ and x1,x1,#-16 + \\ stp x0,x3,[x1,#-16]! + \\ + \\ // syscall + \\ uxtw x0,w2 + \\ mov x2,x4 + \\ mov x3,x5 + \\ mov x4,x6 + \\ mov x8,#220 // SYS_clone + \\ svc #0 + \\ + \\ cbz x0,1f + \\ // parent + \\ ret + \\ // child + \\1: ldp x1,x0,[sp],#16 + \\ blr x1 + \\ mov x8,#93 // SYS_exit + \\ svc #0 + ); + }, + .arm, .thumb => { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // r0, r1, r2, r3, +0, +4, +8 + + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // r7 r0, r1, r2, r3, r4 + asm volatile ( + \\ stmfd sp!,{r4,r5,r6,r7} + \\ mov r7,#120 + \\ mov r6,r3 + \\ mov r5,r0 + \\ mov r0,r2 + \\ and r1,r1,#-16 + \\ ldr r2,[sp,#16] + \\ ldr r3,[sp,#20] + \\ ldr r4,[sp,#24] + \\ svc 0 + \\ tst r0,r0 + \\ beq 1f + \\ ldmfd sp!,{r4,r5,r6,r7} + \\ bx lr + \\ + \\1: mov r0,r6 + \\ bl 3f + \\2: mov r7,#1 + \\ svc 0 + \\ b 2b + \\3: bx r5 + ); + }, + .riscv64 => { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // a0, a1, a2, a3, a4, a5, a6 + + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // a7 a0, a1, a2, a3, a4 + asm volatile ( + \\ # Save func and arg to stack + \\ addi a1, a1, -16 + \\ sd a0, 0(a1) + \\ sd a3, 8(a1) + \\ + \\ # Call SYS_clone + \\ mv a0, a2 + \\ mv a2, a4 + \\ mv a3, a5 + \\ mv a4, a6 + \\ li a7, 220 # SYS_clone + \\ ecall + \\ + \\ beqz a0, 1f + \\ # Parent + \\ ret + \\ + \\ # Child + \\1: ld a1, 0(sp) + \\ ld a0, 8(sp) + \\ jalr a1 + \\ + \\ # Exit + \\ li a7, 93 # SYS_exit + \\ ecall + ); + }, + .mips, .mipsel => { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // 3, 4, 5, 6, 7, 8, 9 + + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // 2 4, 5, 6, 7, 8 + asm volatile ( + \\ # Save function pointer and argument pointer on new thread stack + \\ and $5, $5, -8 + \\ subu $5, $5, 16 + \\ sw $4, 0($5) + \\ sw $7, 4($5) + \\ # Shuffle (fn,sp,fl,arg,ptid,tls,ctid) to (fl,sp,ptid,tls,ctid) + \\ move $4, $6 + \\ lw $6, 16($sp) + \\ lw $7, 20($sp) + \\ lw $9, 24($sp) + \\ subu $sp, $sp, 16 + \\ sw $9, 16($sp) + \\ li $2, 4120 + \\ syscall + \\ beq $7, $0, 1f + \\ nop + \\ addu $sp, $sp, 16 + \\ jr $ra + \\ subu $2, $0, $2 + \\1: + \\ beq $2, $0, 1f + \\ nop + \\ addu $sp, $sp, 16 + \\ jr $ra + \\ nop + \\1: + \\ lw $25, 0($sp) + \\ lw $4, 4($sp) + \\ jalr $25 + \\ nop + \\ move $4, $2 + \\ li $2, 4001 + \\ syscall + ); + }, + .powerpc => { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // 3, 4, 5, 6, 7, 8, 9 + + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // 0 3, 4, 5, 6, 7 + asm volatile ( + \\# store non-volatile regs r30, r31 on stack in order to put our + \\# start func and its arg there + \\stwu 30, -16(1) + \\stw 31, 4(1) + \\ + \\# save r3 (func) into r30, and r6(arg) into r31 + \\mr 30, 3 + \\mr 31, 6 + \\ + \\# create initial stack frame for new thread + \\clrrwi 4, 4, 4 + \\li 0, 0 + \\stwu 0, -16(4) + \\ + \\#move c into first arg + \\mr 3, 5 + \\#mr 4, 4 + \\mr 5, 7 + \\mr 6, 8 + \\mr 7, 9 + \\ + \\# move syscall number into r0 + \\li 0, 120 + \\ + \\sc + \\ + \\# check for syscall error + \\bns+ 1f # jump to label 1 if no summary overflow. + \\#else + \\neg 3, 3 #negate the result (errno) + \\1: + \\# compare sc result with 0 + \\cmpwi cr7, 3, 0 + \\ + \\# if not 0, jump to end + \\bne cr7, 2f + \\ + \\#else: we're the child + \\#call funcptr: move arg (d) into r3 + \\mr 3, 31 + \\#move r30 (funcptr) into CTR reg + \\mtctr 30 + \\# call CTR reg + \\bctrl + \\# mov SYS_exit into r0 (the exit param is already in r3) + \\li 0, 1 + \\sc + \\ + \\2: + \\ + \\# restore stack + \\lwz 30, 0(1) + \\lwz 31, 4(1) + \\addi 1, 1, 16 + \\ + \\blr + ); + }, + .powerpc64, .powerpc64le => { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // 3, 4, 5, 6, 7, 8, 9 + + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // 0 3, 4, 5, 6, 7 + asm volatile ( + \\ # create initial stack frame for new thread + \\ clrrdi 4, 4, 4 + \\ li 0, 0 + \\ stdu 0,-32(4) + \\ + \\ # save fn and arg to child stack + \\ std 3, 8(4) + \\ std 6, 16(4) + \\ + \\ # shuffle args into correct registers and call SYS_clone + \\ mr 3, 5 + \\ #mr 4, 4 + \\ mr 5, 7 + \\ mr 6, 8 + \\ mr 7, 9 + \\ li 0, 120 # SYS_clone = 120 + \\ sc + \\ + \\ # if error, negate return (errno) + \\ bns+ 1f + \\ neg 3, 3 + \\ + \\1: + \\ # if we're the parent, return + \\ cmpwi cr7, 3, 0 + \\ bnelr cr7 + \\ + \\ # we're the child. call fn(arg) + \\ ld 3, 16(1) + \\ ld 12, 8(1) + \\ mtctr 12 + \\ bctrl + \\ + \\ # call SYS_exit. exit code is already in r3 from fn return value + \\ li 0, 1 # SYS_exit = 1 + \\ sc + ); + }, + .sparcv9 => { + // __clone(func, stack, flags, arg, ptid, tls, ctid) + // i0, i1, i2, i3, i4, i5, sp + // syscall(SYS_clone, flags, stack, ptid, tls, ctid) + // g1 o0, o1, o2, o3, o4 + asm volatile ( + \\ save %%sp, -192, %%sp + \\ # Save the func pointer and the arg pointer + \\ mov %%i0, %%g2 + \\ mov %%i3, %%g3 + \\ # Shuffle the arguments + \\ mov 217, %%g1 + \\ mov %%i2, %%o0 + \\ # Add some extra space for the initial frame + \\ sub %%i1, 176 + 2047, %%o1 + \\ mov %%i4, %%o2 + \\ mov %%i5, %%o3 + \\ ldx [%%fp + 0x8af], %%o4 + \\ t 0x6d + \\ bcs,pn %%xcc, 2f + \\ nop + \\ # The child pid is returned in o0 while o1 tells if this + \\ # process is # the child (=1) or the parent (=0). + \\ brnz %%o1, 1f + \\ nop + \\ # Parent process, return the child pid + \\ mov %%o0, %%i0 + \\ ret + \\ restore + \\1: + \\ # Child process, call func(arg) + \\ mov %%g0, %%fp + \\ call %%g2 + \\ mov %%g3, %%o0 + \\ # Exit + \\ mov 1, %%g1 + \\ t 0x6d + \\2: + \\ # The syscall failed + \\ sub %%g0, %%o0, %%i0 + \\ ret + \\ restore + ); + }, + else => @compileError("Implement clone() for this arch."), + } +} + +const math = std.math; + +export fn fmodf(x: f32, y: f32) f32 { + return generic_fmod(f32, x, y); +} +export fn fmod(x: f64, y: f64) f64 { + return generic_fmod(f64, x, y); +} + +// TODO add intrinsics for these (and probably the double version too) +// and have the math stuff use the intrinsic. same as @mod and @rem +export fn floorf(x: f32) f32 { + return math.floor(x); +} + +export fn ceilf(x: f32) f32 { + return math.ceil(x); +} + +export fn floor(x: f64) f64 { + return math.floor(x); +} + +export fn ceil(x: f64) f64 { + return math.ceil(x); +} + +export fn fma(a: f64, b: f64, c: f64) f64 { + return math.fma(f64, a, b, c); +} + +export fn fmaf(a: f32, b: f32, c: f32) f32 { + return math.fma(f32, a, b, c); +} + +export fn sin(a: f64) f64 { + return math.sin(a); +} + +export fn sinf(a: f32) f32 { + return math.sin(a); +} + +export fn cos(a: f64) f64 { + return math.cos(a); +} + +export fn cosf(a: f32) f32 { + return math.cos(a); +} + +export fn sincos(a: f64, r_sin: *f64, r_cos: *f64) void { + r_sin.* = math.sin(a); + r_cos.* = math.cos(a); +} + +export fn sincosf(a: f32, r_sin: *f32, r_cos: *f32) void { + r_sin.* = math.sin(a); + r_cos.* = math.cos(a); +} + +export fn exp(a: f64) f64 { + return math.exp(a); +} + +export fn expf(a: f32) f32 { + return math.exp(a); +} + +export fn exp2(a: f64) f64 { + return math.exp2(a); +} + +export fn exp2f(a: f32) f32 { + return math.exp2(a); +} + +export fn log(a: f64) f64 { + return math.ln(a); +} + +export fn logf(a: f32) f32 { + return math.ln(a); +} + +export fn log2(a: f64) f64 { + return math.log2(a); +} + +export fn log2f(a: f32) f32 { + return math.log2(a); +} + +export fn log10(a: f64) f64 { + return math.log10(a); +} + +export fn log10f(a: f32) f32 { + return math.log10(a); +} + +export fn fabs(a: f64) f64 { + return math.fabs(a); +} + +export fn fabsf(a: f32) f32 { + return math.fabs(a); +} + +export fn trunc(a: f64) f64 { + return math.trunc(a); +} + +export fn truncf(a: f32) f32 { + return math.trunc(a); +} + +export fn round(a: f64) f64 { + return math.round(a); +} + +export fn roundf(a: f32) f32 { + return math.round(a); +} + +fn generic_fmod(comptime T: type, x: T, y: T) T { + @setRuntimeSafety(false); + + const bits = @typeInfo(T).Float.bits; + const uint = std.meta.Int(.unsigned, bits); + const log2uint = math.Log2Int(uint); + const digits = if (T == f32) 23 else 52; + const exp_bits = if (T == f32) 9 else 12; + const bits_minus_1 = bits - 1; + const mask = if (T == f32) 0xff else 0x7ff; + var ux = @bitCast(uint, x); + var uy = @bitCast(uint, y); + var ex = @intCast(i32, (ux >> digits) & mask); + var ey = @intCast(i32, (uy >> digits) & mask); + const sx = if (T == f32) @intCast(u32, ux & 0x80000000) else @intCast(i32, ux >> bits_minus_1); + var i: uint = undefined; + + if (uy << 1 == 0 or isNan(@bitCast(T, uy)) or ex == mask) + return (x * y) / (x * y); + + if (ux << 1 <= uy << 1) { + if (ux << 1 == uy << 1) + return 0 * x; + return x; + } + + // normalize x and y + if (ex == 0) { + i = ux << exp_bits; + while (i >> bits_minus_1 == 0) : ({ + ex -= 1; + i <<= 1; + }) {} + ux <<= @intCast(log2uint, @bitCast(u32, -ex + 1)); + } else { + ux &= maxInt(uint) >> exp_bits; + ux |= 1 << digits; + } + if (ey == 0) { + i = uy << exp_bits; + while (i >> bits_minus_1 == 0) : ({ + ey -= 1; + i <<= 1; + }) {} + uy <<= @intCast(log2uint, @bitCast(u32, -ey + 1)); + } else { + uy &= maxInt(uint) >> exp_bits; + uy |= 1 << digits; + } + + // x mod y + while (ex > ey) : (ex -= 1) { + i = ux -% uy; + if (i >> bits_minus_1 == 0) { + if (i == 0) + return 0 * x; + ux = i; + } + ux <<= 1; + } + i = ux -% uy; + if (i >> bits_minus_1 == 0) { + if (i == 0) + return 0 * x; + ux = i; + } + while (ux >> digits == 0) : ({ + ux <<= 1; + ex -= 1; + }) {} + + // scale result up + if (ex > 0) { + ux -%= 1 << digits; + ux |= @as(uint, @bitCast(u32, ex)) << digits; + } else { + ux >>= @intCast(log2uint, @bitCast(u32, -ex + 1)); + } + if (T == f32) { + ux |= sx; + } else { + ux |= @intCast(uint, sx) << bits_minus_1; + } + return @bitCast(T, ux); +} + +test "fmod, fmodf" { + inline for ([_]type{ f32, f64 }) |T| { + const nan_val = math.nan(T); + const inf_val = math.inf(T); + + try std.testing.expect(isNan(generic_fmod(T, nan_val, 1.0))); + try std.testing.expect(isNan(generic_fmod(T, 1.0, nan_val))); + try std.testing.expect(isNan(generic_fmod(T, inf_val, 1.0))); + try std.testing.expect(isNan(generic_fmod(T, 0.0, 0.0))); + try std.testing.expect(isNan(generic_fmod(T, 1.0, 0.0))); + + try std.testing.expectEqual(@as(T, 0.0), generic_fmod(T, 0.0, 2.0)); + try std.testing.expectEqual(@as(T, -0.0), generic_fmod(T, -0.0, 2.0)); + + try std.testing.expectEqual(@as(T, -2.0), generic_fmod(T, -32.0, 10.0)); + try std.testing.expectEqual(@as(T, -2.0), generic_fmod(T, -32.0, -10.0)); + try std.testing.expectEqual(@as(T, 2.0), generic_fmod(T, 32.0, 10.0)); + try std.testing.expectEqual(@as(T, 2.0), generic_fmod(T, 32.0, -10.0)); + } +} + +fn generic_fmin(comptime T: type, x: T, y: T) T { + if (isNan(x)) + return y; + if (isNan(y)) + return x; + return if (x < y) x else y; +} + +export fn fminf(x: f32, y: f32) callconv(.C) f32 { + return generic_fmin(f32, x, y); +} + +export fn fmin(x: f64, y: f64) callconv(.C) f64 { + return generic_fmin(f64, x, y); +} + +test "fmin, fminf" { + inline for ([_]type{ f32, f64 }) |T| { + const nan_val = math.nan(T); + + try std.testing.expect(isNan(generic_fmin(T, nan_val, nan_val))); + try std.testing.expectEqual(@as(T, 1.0), generic_fmin(T, nan_val, 1.0)); + try std.testing.expectEqual(@as(T, 1.0), generic_fmin(T, 1.0, nan_val)); + + try std.testing.expectEqual(@as(T, 1.0), generic_fmin(T, 1.0, 10.0)); + try std.testing.expectEqual(@as(T, -1.0), generic_fmin(T, 1.0, -1.0)); + } +} + +fn generic_fmax(comptime T: type, x: T, y: T) T { + if (isNan(x)) + return y; + if (isNan(y)) + return x; + return if (x < y) y else x; +} + +export fn fmaxf(x: f32, y: f32) callconv(.C) f32 { + return generic_fmax(f32, x, y); +} + +export fn fmax(x: f64, y: f64) callconv(.C) f64 { + return generic_fmax(f64, x, y); +} + +test "fmax, fmaxf" { + inline for ([_]type{ f32, f64 }) |T| { + const nan_val = math.nan(T); + + try std.testing.expect(isNan(generic_fmax(T, nan_val, nan_val))); + try std.testing.expectEqual(@as(T, 1.0), generic_fmax(T, nan_val, 1.0)); + try std.testing.expectEqual(@as(T, 1.0), generic_fmax(T, 1.0, nan_val)); + + try std.testing.expectEqual(@as(T, 10.0), generic_fmax(T, 1.0, 10.0)); + try std.testing.expectEqual(@as(T, 1.0), generic_fmax(T, 1.0, -1.0)); + } +} + +// NOTE: The original code is full of implicit signed -> unsigned assumptions and u32 wraparound +// behaviour. Most intermediate i32 values are changed to u32 where appropriate but there are +// potentially some edge cases remaining that are not handled in the same way. +export fn sqrt(x: f64) f64 { + const tiny: f64 = 1.0e-300; + const sign: u32 = 0x80000000; + const u = @bitCast(u64, x); + + var ix0 = @intCast(u32, u >> 32); + var ix1 = @intCast(u32, u & 0xFFFFFFFF); + + // sqrt(nan) = nan, sqrt(+inf) = +inf, sqrt(-inf) = nan + if (ix0 & 0x7FF00000 == 0x7FF00000) { + return x * x + x; + } + + // sqrt(+-0) = +-0 + if (x == 0.0) { + return x; + } + // sqrt(-ve) = snan + if (ix0 & sign != 0) { + return math.snan(f64); + } + + // normalize x + var m = @intCast(i32, ix0 >> 20); + if (m == 0) { + // subnormal + while (ix0 == 0) { + m -= 21; + ix0 |= ix1 >> 11; + ix1 <<= 21; + } + + // subnormal + var i: u32 = 0; + while (ix0 & 0x00100000 == 0) : (i += 1) { + ix0 <<= 1; + } + m -= @intCast(i32, i) - 1; + ix0 |= ix1 >> @intCast(u5, 32 - i); + ix1 <<= @intCast(u5, i); + } + + // unbias exponent + m -= 1023; + ix0 = (ix0 & 0x000FFFFF) | 0x00100000; + if (m & 1 != 0) { + ix0 += ix0 + (ix1 >> 31); + ix1 = ix1 +% ix1; + } + m >>= 1; + + // sqrt(x) bit by bit + ix0 += ix0 + (ix1 >> 31); + ix1 = ix1 +% ix1; + + var q: u32 = 0; + var q1: u32 = 0; + var s0: u32 = 0; + var s1: u32 = 0; + var r: u32 = 0x00200000; + var t: u32 = undefined; + var t1: u32 = undefined; + + while (r != 0) { + t = s0 +% r; + if (t <= ix0) { + s0 = t + r; + ix0 -= t; + q += r; + } + ix0 = ix0 +% ix0 +% (ix1 >> 31); + ix1 = ix1 +% ix1; + r >>= 1; + } + + r = sign; + while (r != 0) { + t1 = s1 +% r; + t = s0; + if (t < ix0 or (t == ix0 and t1 <= ix1)) { + s1 = t1 +% r; + if (t1 & sign == sign and s1 & sign == 0) { + s0 += 1; + } + ix0 -= t; + if (ix1 < t1) { + ix0 -= 1; + } + ix1 = ix1 -% t1; + q1 += r; + } + ix0 = ix0 +% ix0 +% (ix1 >> 31); + ix1 = ix1 +% ix1; + r >>= 1; + } + + // rounding direction + if (ix0 | ix1 != 0) { + var z = 1.0 - tiny; // raise inexact + if (z >= 1.0) { + z = 1.0 + tiny; + if (q1 == 0xFFFFFFFF) { + q1 = 0; + q += 1; + } else if (z > 1.0) { + if (q1 == 0xFFFFFFFE) { + q += 1; + } + q1 += 2; + } else { + q1 += q1 & 1; + } + } + } + + ix0 = (q >> 1) + 0x3FE00000; + ix1 = q1 >> 1; + if (q & 1 != 0) { + ix1 |= 0x80000000; + } + + // NOTE: musl here appears to rely on signed twos-complement wraparound. +% has the same + // behaviour at least. + var iix0 = @intCast(i32, ix0); + iix0 = iix0 +% (m << 20); + + const uz = (@intCast(u64, iix0) << 32) | ix1; + return @bitCast(f64, uz); +} + +test "sqrt" { + const V = [_]f64{ + 0.0, + 4.089288054930154, + 7.538757127071935, + 8.97780793672623, + 5.304443821913729, + 5.682408965311888, + 0.5846878579110049, + 3.650338664297043, + 0.3178091951800732, + 7.1505232436382835, + 3.6589165881946464, + }; + + // Note that @sqrt will either generate the sqrt opcode (if supported by the + // target ISA) or a call to `sqrtf` otherwise. + for (V) |val| + try std.testing.expectEqual(@sqrt(val), sqrt(val)); +} + +test "sqrt special" { + try std.testing.expect(std.math.isPositiveInf(sqrt(std.math.inf(f64)))); + try std.testing.expect(sqrt(0.0) == 0.0); + try std.testing.expect(sqrt(-0.0) == -0.0); + try std.testing.expect(isNan(sqrt(-1.0))); + try std.testing.expect(isNan(sqrt(std.math.nan(f64)))); +} + +export fn sqrtf(x: f32) f32 { + const tiny: f32 = 1.0e-30; + const sign: i32 = @bitCast(i32, @as(u32, 0x80000000)); + var ix: i32 = @bitCast(i32, x); + + if ((ix & 0x7F800000) == 0x7F800000) { + return x * x + x; // sqrt(nan) = nan, sqrt(+inf) = +inf, sqrt(-inf) = snan + } + + // zero + if (ix <= 0) { + if (ix & ~sign == 0) { + return x; // sqrt (+-0) = +-0 + } + if (ix < 0) { + return math.snan(f32); + } + } + + // normalize + var m = ix >> 23; + if (m == 0) { + // subnormal + var i: i32 = 0; + while (ix & 0x00800000 == 0) : (i += 1) { + ix <<= 1; + } + m -= i - 1; + } + + m -= 127; // unbias exponent + ix = (ix & 0x007FFFFF) | 0x00800000; + + if (m & 1 != 0) { // odd m, double x to even + ix += ix; + } + + m >>= 1; // m = [m / 2] + + // sqrt(x) bit by bit + ix += ix; + var q: i32 = 0; // q = sqrt(x) + var s: i32 = 0; + var r: i32 = 0x01000000; // r = moving bit right -> left + + while (r != 0) { + const t = s + r; + if (t <= ix) { + s = t + r; + ix -= t; + q += r; + } + ix += ix; + r >>= 1; + } + + // floating add to find rounding direction + if (ix != 0) { + var z = 1.0 - tiny; // inexact + if (z >= 1.0) { + z = 1.0 + tiny; + if (z > 1.0) { + q += 2; + } else { + if (q & 1 != 0) { + q += 1; + } + } + } + } + + ix = (q >> 1) + 0x3f000000; + ix += m << 23; + return @bitCast(f32, ix); +} + +test "sqrtf" { + const V = [_]f32{ + 0.0, + 4.089288054930154, + 7.538757127071935, + 8.97780793672623, + 5.304443821913729, + 5.682408965311888, + 0.5846878579110049, + 3.650338664297043, + 0.3178091951800732, + 7.1505232436382835, + 3.6589165881946464, + }; + + // Note that @sqrt will either generate the sqrt opcode (if supported by the + // target ISA) or a call to `sqrtf` otherwise. + for (V) |val| + try std.testing.expectEqual(@sqrt(val), sqrtf(val)); +} + +test "sqrtf special" { + try std.testing.expect(std.math.isPositiveInf(sqrtf(std.math.inf(f32)))); + try std.testing.expect(sqrtf(0.0) == 0.0); + try std.testing.expect(sqrtf(-0.0) == -0.0); + try std.testing.expect(isNan(sqrtf(-1.0))); + try std.testing.expect(isNan(sqrtf(std.math.nan(f32)))); +} diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 6daf720961..4b7185d1ea 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -370,17 +370,16 @@ pub const Object = struct { } // This gets the LLVM values from the function and stores them in `dg.args`. - const fn_param_len = decl.ty.fnParamLen(); - var args = try dg.gpa.alloc(*const llvm.Value, fn_param_len); + const fn_info = decl.ty.fnInfo(); + var args = try dg.gpa.alloc(*const llvm.Value, fn_info.param_types.len); for (args) |*arg, i| { arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i)); } - // We remove all the basic blocks of a function to support incremental - // compilation! - // TODO: remove all basic blocks if functions can have more than one - if (llvm_func.getFirstBasicBlock()) |bb| { + // Remove all the basic blocks of a function in order to start over, generating + // LLVM IR from an empty function body. + while (llvm_func.getFirstBasicBlock()) |bb| { bb.deleteBasicBlock(); } @@ -545,20 +544,16 @@ pub const DeclGen = struct { assert(decl.has_tv); const zig_fn_type = decl.ty; - const return_type = zig_fn_type.fnReturnType(); - const fn_param_len = zig_fn_type.fnParamLen(); + const fn_info = zig_fn_type.fnInfo(); + const return_type = fn_info.return_type; - const fn_param_types = try self.gpa.alloc(Type, fn_param_len); - defer self.gpa.free(fn_param_types); - zig_fn_type.fnParamTypes(fn_param_types); - - const llvm_param_buffer = try self.gpa.alloc(*const llvm.Type, fn_param_len); + const llvm_param_buffer = try self.gpa.alloc(*const llvm.Type, fn_info.param_types.len); defer self.gpa.free(llvm_param_buffer); var llvm_params_len: c_uint = 0; - for (fn_param_types) |fn_param| { - if (fn_param.hasCodeGenBits()) { - llvm_param_buffer[llvm_params_len] = try self.llvmType(fn_param); + for (fn_info.param_types) |param_ty| { + if (param_ty.hasCodeGenBits()) { + llvm_param_buffer[llvm_params_len] = try self.llvmType(param_ty); llvm_params_len += 1; } } @@ -583,8 +578,85 @@ pub const DeclGen = struct { llvm_fn.setUnnamedAddr(.True); } - // TODO: calling convention, linkage, tsan, etc. see codegen.cpp `make_fn_llvm_value`. + // TODO: more attributes. see codegen.cpp `make_fn_llvm_value`. + const target = self.module.getTarget(); + switch (fn_info.cc) { + .Unspecified, .Inline, .Async => { + llvm_fn.setFunctionCallConv(.Fast); + }, + .C => { + llvm_fn.setFunctionCallConv(.C); + }, + .Naked => { + self.addFnAttr(llvm_fn, "naked"); + }, + .Stdcall => { + llvm_fn.setFunctionCallConv(.X86_StdCall); + }, + .Fastcall => { + llvm_fn.setFunctionCallConv(.X86_FastCall); + }, + .Vectorcall => { + switch (target.cpu.arch) { + .i386, .x86_64 => { + llvm_fn.setFunctionCallConv(.X86_VectorCall); + }, + .aarch64, .aarch64_be, .aarch64_32 => { + llvm_fn.setFunctionCallConv(.AArch64_VectorCall); + }, + else => unreachable, + } + }, + .Thiscall => { + llvm_fn.setFunctionCallConv(.X86_ThisCall); + }, + .APCS => { + llvm_fn.setFunctionCallConv(.ARM_APCS); + }, + .AAPCS => { + llvm_fn.setFunctionCallConv(.ARM_AAPCS); + }, + .AAPCSVFP => { + llvm_fn.setFunctionCallConv(.ARM_AAPCS_VFP); + }, + .Interrupt => { + switch (target.cpu.arch) { + .i386, .x86_64 => { + llvm_fn.setFunctionCallConv(.X86_INTR); + }, + .avr => { + llvm_fn.setFunctionCallConv(.AVR_INTR); + }, + .msp430 => { + llvm_fn.setFunctionCallConv(.MSP430_INTR); + }, + else => unreachable, + } + }, + .Signal => { + llvm_fn.setFunctionCallConv(.AVR_SIGNAL); + }, + .SysV => { + llvm_fn.setFunctionCallConv(.X86_64_SysV); + }, + } + // Function attributes that are independent of analysis results of the function body. + if (!self.module.comp.bin_file.options.red_zone) { + self.addFnAttr(llvm_fn, "noredzone"); + } + self.addFnAttr(llvm_fn, "nounwind"); + if (self.module.comp.unwind_tables) { + self.addFnAttr(llvm_fn, "uwtable"); + } + if (self.module.comp.bin_file.options.optimize_mode == .ReleaseSmall) { + self.addFnAttr(llvm_fn, "minsize"); + self.addFnAttr(llvm_fn, "optsize"); + } + if (self.module.comp.bin_file.options.tsan) { + self.addFnAttr(llvm_fn, "sanitize_thread"); + } + // TODO add target-cpu and target-features fn attributes if (return_type.isNoReturn()) { self.addFnAttr(llvm_fn, "noreturn"); } diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index bf951fa67e..be597949e2 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -145,6 +145,12 @@ pub const Value = opaque { pub const setAlignment = LLVMSetAlignment; extern fn LLVMSetAlignment(V: *const Value, Bytes: c_uint) void; + + pub const getFunctionCallConv = LLVMGetFunctionCallConv; + extern fn LLVMGetFunctionCallConv(Fn: *const Value) CallConv; + + pub const setFunctionCallConv = LLVMSetFunctionCallConv; + extern fn LLVMSetFunctionCallConv(Fn: *const Value, CC: CallConv) void; }; pub const Type = opaque { @@ -1028,6 +1034,53 @@ pub const TypeKind = enum(c_int) { X86_AMX, }; +pub const CallConv = enum(c_uint) { + C = 0, + Fast = 8, + Cold = 9, + GHC = 10, + HiPE = 11, + WebKit_JS = 12, + AnyReg = 13, + PreserveMost = 14, + PreserveAll = 15, + Swift = 16, + CXX_FAST_TLS = 17, + + X86_StdCall = 64, + X86_FastCall = 65, + ARM_APCS = 66, + ARM_AAPCS = 67, + ARM_AAPCS_VFP = 68, + MSP430_INTR = 69, + X86_ThisCall = 70, + PTX_Kernel = 71, + PTX_Device = 72, + SPIR_FUNC = 75, + SPIR_KERNEL = 76, + Intel_OCL_BI = 77, + X86_64_SysV = 78, + Win64 = 79, + X86_VectorCall = 80, + HHVM = 81, + HHVM_C = 82, + X86_INTR = 83, + AVR_INTR = 84, + AVR_SIGNAL = 85, + AVR_BUILTIN = 86, + AMDGPU_VS = 87, + AMDGPU_GS = 88, + AMDGPU_PS = 89, + AMDGPU_CS = 90, + AMDGPU_KERNEL = 91, + X86_RegCall = 92, + AMDGPU_HS = 93, + MSP430_BUILTIN = 94, + AMDGPU_LS = 95, + AMDGPU_ES = 96, + AArch64_VectorCall = 97, +}; + pub const address_space = struct { pub const default: c_uint = 0; From f215d98043ef948a996ac036609f4b71fa9c3c13 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 23 Sep 2021 23:39:58 -0700 Subject: [PATCH 109/160] stage2: LLVM backend: improved naming and exporting Introduce an explicit decl_map for *Decl to LLVMValueRef. Doc comment reproduced here: Ideally we would use `llvm_module.getNamedFunction` to go from *Decl to LLVM function, but that has some downsides: * we have to compute the fully qualified name every time we want to do the lookup * for externally linked functions, the name is not fully qualified, but when a Decl goes from exported to not exported and vice-versa, we would use the wrong version of the name and incorrectly get function not found in the llvm module. * it works for functions not all globals. Therefore, this table keeps track of the mapping. Non-exported functions now use fully-qualified symbol names. `Module.Decl.getFullyQualifiedName` now returns a sentinel-terminated slice which is useful to pass to LLVMAddFunction. Instead of using aliases for all external symbols, now the LLVM backend takes advantage of LLVMSetValueName to rename functions that become exported. Aliases are still used for the second and remaining exports. freeDecl is now handled properly in the LLVM backend, deleting the LLVMValueRef corresponding to the Decl being deleted. The linker backends for ELF, COFF, Mach-O, and Wasm had to be updated to forward the freeDecl call to the LLVM backend. --- src/Module.zig | 4 +- src/codegen/llvm.zig | 105 +++++++++++++++++++++++----------- src/codegen/llvm/bindings.zig | 9 +++ src/link/Coff.zig | 4 +- src/link/Elf.zig | 4 +- src/link/MachO.zig | 3 + src/link/Wasm.zig | 4 ++ src/stage1/codegen.cpp | 4 +- 8 files changed, 96 insertions(+), 41 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index a0e04dd478..a6a9225d75 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -619,11 +619,11 @@ pub const Decl = struct { return decl.namespace.renderFullyQualifiedName(unqualified_name, writer); } - pub fn getFullyQualifiedName(decl: Decl, gpa: *Allocator) ![]u8 { + pub fn getFullyQualifiedName(decl: Decl, gpa: *Allocator) ![:0]u8 { var buffer = std.ArrayList(u8).init(gpa); defer buffer.deinit(); try decl.renderFullyQualifiedName(buffer.writer()); - return buffer.toOwnedSlice(); + return buffer.toOwnedSliceSentinel(0); } pub fn typedValue(decl: Decl) error{AnalysisFail}!TypedValue { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4b7185d1ea..92524a09b7 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -155,6 +155,15 @@ pub const Object = struct { llvm_module: *const llvm.Module, context: *const llvm.Context, target_machine: *const llvm.TargetMachine, + /// Ideally we would use `llvm_module.getNamedFunction` to go from *Decl to LLVM function, + /// but that has some downsides: + /// * we have to compute the fully qualified name every time we want to do the lookup + /// * for externally linked functions, the name is not fully qualified, but when + /// a Decl goes from exported to not exported and vice-versa, we would use the wrong + /// version of the name and incorrectly get function not found in the llvm module. + /// * it works for functions not all globals. + /// Therefore, this table keeps track of the mapping. + decl_map: std.AutoHashMapUnmanaged(*const Module.Decl, *const llvm.Value), pub fn create(gpa: *Allocator, options: link.Options) !*Object { const obj = try gpa.create(Object); @@ -241,18 +250,20 @@ pub const Object = struct { .llvm_module = llvm_module, .context = context, .target_machine = target_machine, + .decl_map = .{}, }; } - pub fn deinit(self: *Object) void { + pub fn deinit(self: *Object, gpa: *Allocator) void { self.target_machine.dispose(); self.llvm_module.dispose(); self.context.dispose(); + self.decl_map.deinit(gpa); self.* = undefined; } pub fn destroy(self: *Object, gpa: *Allocator) void { - self.deinit(); + self.deinit(gpa); gpa.destroy(self); } @@ -450,41 +461,62 @@ pub const Object = struct { ) !void { // If the module does not already have the function, we ignore this function call // because we call `updateDeclExports` at the end of `updateFunc` and `updateDecl`. - const llvm_fn = self.llvm_module.getNamedFunction(decl.name) orelse return; + const llvm_fn = self.decl_map.get(decl) orelse return; const is_extern = decl.val.tag() == .extern_fn; - if (is_extern or exports.len != 0) { - llvm_fn.setLinkage(.External); + if (is_extern) { + llvm_fn.setValueName(decl.name); llvm_fn.setUnnamedAddr(.False); + llvm_fn.setLinkage(.External); + } else if (exports.len != 0) { + const exp_name = exports[0].options.name; + llvm_fn.setValueName2(exp_name.ptr, exp_name.len); + llvm_fn.setUnnamedAddr(.False); + switch (exports[0].options.linkage) { + .Internal => unreachable, + .Strong => llvm_fn.setLinkage(.External), + .Weak => llvm_fn.setLinkage(.WeakODR), + .LinkOnce => llvm_fn.setLinkage(.LinkOnceODR), + } + // If a Decl is exported more than one time (which is rare), + // we add aliases for all but the first export. + // TODO LLVM C API does not support deleting aliases. We need to + // patch it to support this or figure out how to wrap the C++ API ourselves. + // Until then we iterate over existing aliases and make them point + // to the correct decl, or otherwise add a new alias. Old aliases are leaked. + for (exports[1..]) |exp| { + const exp_name_z = try module.gpa.dupeZ(u8, exp.options.name); + defer module.gpa.free(exp_name_z); + + if (self.llvm_module.getNamedGlobalAlias(exp_name_z.ptr, exp_name_z.len)) |alias| { + alias.setAliasee(llvm_fn); + } else { + const alias = self.llvm_module.addAlias(llvm_fn.typeOf(), llvm_fn, exp_name_z); + switch (exp.options.linkage) { + .Internal => alias.setLinkage(.Internal), + .Strong => alias.setLinkage(.External), + .Weak => { + if (is_extern) { + alias.setLinkage(.ExternalWeak); + } else { + alias.setLinkage(.WeakODR); + } + }, + .LinkOnce => alias.setLinkage(.LinkOnceODR), + } + } + } } else { + const fqn = try decl.getFullyQualifiedName(module.gpa); + defer module.gpa.free(fqn); + llvm_fn.setValueName2(fqn.ptr, fqn.len); llvm_fn.setLinkage(.Internal); llvm_fn.setUnnamedAddr(.True); } - // TODO LLVM C API does not support deleting aliases. We need to - // patch it to support this or figure out how to wrap the C++ API ourselves. - // Until then we iterate over existing aliases and make them point - // to the correct decl, or otherwise add a new alias. Old aliases are leaked. - for (exports) |exp| { - const exp_name_z = try module.gpa.dupeZ(u8, exp.options.name); - defer module.gpa.free(exp_name_z); + } - if (self.llvm_module.getNamedGlobalAlias(exp_name_z.ptr, exp_name_z.len)) |alias| { - alias.setAliasee(llvm_fn); - } else { - const alias = self.llvm_module.addAlias(llvm_fn.typeOf(), llvm_fn, exp_name_z); - switch (exp.options.linkage) { - .Internal => alias.setLinkage(.Internal), - .Strong => alias.setLinkage(.External), - .Weak => { - if (is_extern) { - alias.setLinkage(.ExternalWeak); - } else { - alias.setLinkage(.WeakODR); - } - }, - .LinkOnce => alias.setLinkage(.LinkOnceODR), - } - } - } + pub fn freeDecl(self: *Object, decl: *Module.Decl) void { + const llvm_value = self.decl_map.get(decl) orelse return; + llvm_value.deleteGlobal(); } }; @@ -493,9 +525,8 @@ pub const DeclGen = struct { object: *Object, module: *Module, decl: *Module.Decl, - err_msg: ?*Module.ErrorMsg, - gpa: *Allocator, + err_msg: ?*Module.ErrorMsg, fn todo(self: *DeclGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { @setCold(true); @@ -540,7 +571,8 @@ pub const DeclGen = struct { /// Note that this can be called before the function's semantic analysis has /// completed, so if any attributes rely on that, they must be done in updateFunc, not here. fn resolveLlvmFunction(self: *DeclGen, decl: *Module.Decl) !*const llvm.Value { - if (self.llvmModule().getNamedFunction(decl.name)) |llvm_fn| return llvm_fn; + const gop = try self.object.decl_map.getOrPut(self.gpa, decl); + if (gop.found_existing) return gop.value_ptr.*; assert(decl.has_tv); const zig_fn_type = decl.ty; @@ -570,7 +602,12 @@ pub const DeclGen = struct { .False, ); const llvm_addrspace = self.llvmAddressSpace(decl.@"addrspace"); - const llvm_fn = self.llvmModule().addFunctionInAddressSpace(decl.name, fn_type, llvm_addrspace); + + const fqn = try decl.getFullyQualifiedName(self.gpa); + defer self.gpa.free(fqn); + + const llvm_fn = self.llvmModule().addFunctionInAddressSpace(fqn, fn_type, llvm_addrspace); + gop.value_ptr.* = llvm_fn; const is_extern = decl.val.tag() == .extern_fn; if (!is_extern) { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index be597949e2..c53ac08fdd 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -151,6 +151,15 @@ pub const Value = opaque { pub const setFunctionCallConv = LLVMSetFunctionCallConv; extern fn LLVMSetFunctionCallConv(Fn: *const Value, CC: CallConv) void; + + pub const setValueName = LLVMSetValueName; + extern fn LLVMSetValueName(Val: *const Value, Name: [*:0]const u8) void; + + pub const setValueName2 = LLVMSetValueName2; + extern fn LLVMSetValueName2(Val: *const Value, Name: [*]const u8, NameLen: usize) void; + + pub const deleteFunction = LLVMDeleteFunction; + extern fn LLVMDeleteFunction(Fn: *const Value) void; }; pub const Type = opaque { diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 41b88881c4..a0e79eba20 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -770,7 +770,9 @@ fn finishUpdateDecl(self: *Coff, module: *Module, decl: *Module.Decl, code: []co } pub fn freeDecl(self: *Coff, decl: *Module.Decl) void { - if (self.llvm_object) |_| return; + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl); + } // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. self.freeTextBlock(&decl.link.coff); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 98eb0815a7..afa976d741 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -2147,7 +2147,9 @@ pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void { } pub fn freeDecl(self: *Elf, decl: *Module.Decl) void { - if (self.llvm_object) |_| return; + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl); + } // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. self.freeTextBlock(&decl.link.elf); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 93b3cc7e93..5551c31632 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3501,6 +3501,9 @@ pub fn deleteExport(self: *MachO, exp: Export) void { } pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl); + } log.debug("freeDecl {*}", .{decl}); _ = self.decls.swapRemove(decl); // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e3ec0bf255..2f5620e47a 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -339,6 +339,10 @@ pub fn updateDeclExports( } pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void { + if (build_options.have_llvm) { + if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl); + } + if (self.getFuncidx(decl)) |func_idx| { switch (decl.val.tag()) { .function => _ = self.funcs.swapRemove(func_idx), diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 1304dcc004..f84847a9fe 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -487,9 +487,7 @@ static LLVMValueRef make_fn_llvm_value(CodeGen *g, ZigFn *fn) { if (mangled_symbol_buf) buf_destroy(mangled_symbol_buf); } } else { - if (llvm_fn == nullptr) { - llvm_fn = LLVMAddFunction(g->module, symbol_name, fn_llvm_type); - } + llvm_fn = LLVMAddFunction(g->module, symbol_name, fn_llvm_type); for (size_t i = 1; i < fn->export_list.length; i += 1) { GlobalExport *fn_export = &fn->export_list.items[i]; From ef7fa76001f873824b0f64dfc2172ed2f304c348 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 24 Sep 2021 00:54:44 -0700 Subject: [PATCH 110/160] stage2: enable building freestanding libc with LLVM backend * LLVM backend: respect `sub_path` just like the other stage2 backends do. * Compilation has some new logic to only emit work queue jobs for building stuff when it believes itself to be capable. The linker backends no longer have duplicate logic; instead they respect the optional bit on the respective asset. --- src/Compilation.zig | 40 ++++++++++++++++++++++------------------ src/codegen/llvm.zig | 28 +++++++++++++--------------- src/link.zig | 3 +++ src/link/Coff.zig | 28 +++++++++++++--------------- src/link/Elf.zig | 18 +++++++----------- src/link/MachO.zig | 2 +- src/link/Wasm.zig | 2 +- 7 files changed, 60 insertions(+), 61 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 140ed40d99..00a737c2be 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1574,25 +1574,29 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { // also test the use case of `build-obj -fcompiler-rt` with the self-hosted compiler // and make sure the compiler-rt symbols are emitted. Currently this is hooked up for // stage1 but not stage2. - if (comp.bin_file.options.use_stage1) { - if (comp.bin_file.options.include_compiler_rt) { - if (is_exe_or_dyn_lib) { - try comp.work_queue.writeItem(.{ .compiler_rt_lib = {} }); - } else if (options.output_mode != .Obj) { - // If build-obj with -fcompiler-rt is requested, that is handled specially - // elsewhere. In this case we are making a static library, so we ask - // for a compiler-rt object to put in it. - try comp.work_queue.writeItem(.{ .compiler_rt_obj = {} }); - } + const capable_of_building_compiler_rt = comp.bin_file.options.use_stage1; + const capable_of_building_ssp = comp.bin_file.options.use_stage1; + const capable_of_building_zig_libc = comp.bin_file.options.use_stage1 or + comp.bin_file.options.use_llvm; + + if (comp.bin_file.options.include_compiler_rt and capable_of_building_compiler_rt) { + if (is_exe_or_dyn_lib) { + try comp.work_queue.writeItem(.{ .compiler_rt_lib = {} }); + } else if (options.output_mode != .Obj) { + // If build-obj with -fcompiler-rt is requested, that is handled specially + // elsewhere. In this case we are making a static library, so we ask + // for a compiler-rt object to put in it. + try comp.work_queue.writeItem(.{ .compiler_rt_obj = {} }); } - if (needs_c_symbols) { - // MinGW provides no libssp, use our own implementation. - if (comp.getTarget().isMinGW()) { - try comp.work_queue.writeItem(.{ .libssp = {} }); - } - if (!comp.bin_file.options.link_libc) { - try comp.work_queue.writeItem(.{ .zig_libc = {} }); - } + } + if (needs_c_symbols) { + // MinGW provides no libssp, use our own implementation. + if (comp.getTarget().isMinGW() and capable_of_building_ssp) { + try comp.work_queue.writeItem(.{ .libssp = {} }); + } + + if (!comp.bin_file.options.link_libc and capable_of_building_zig_libc) { + try comp.work_queue.writeItem(.{ .zig_libc = {} }); } } } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 92524a09b7..650628d8c2 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -164,15 +164,17 @@ pub const Object = struct { /// * it works for functions not all globals. /// Therefore, this table keeps track of the mapping. decl_map: std.AutoHashMapUnmanaged(*const Module.Decl, *const llvm.Value), + /// Where to put the output object file, relative to bin_file.options.emit directory. + sub_path: []const u8, - pub fn create(gpa: *Allocator, options: link.Options) !*Object { + pub fn create(gpa: *Allocator, sub_path: []const u8, options: link.Options) !*Object { const obj = try gpa.create(Object); errdefer gpa.destroy(obj); - obj.* = try Object.init(gpa, options); + obj.* = try Object.init(gpa, sub_path, options); return obj; } - pub fn init(gpa: *Allocator, options: link.Options) !Object { + pub fn init(gpa: *Allocator, sub_path: []const u8, options: link.Options) !Object { const context = llvm.Context.create(); errdefer context.dispose(); @@ -251,6 +253,7 @@ pub const Object = struct { .context = context, .target_machine = target_machine, .decl_map = .{}, + .sub_path = sub_path, }; } @@ -301,23 +304,18 @@ pub const Object = struct { const mod = comp.bin_file.options.module.?; const cache_dir = mod.zig_cache_artifact_directory; - const emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit != null) blk: { - const obj_basename = try std.zig.binNameAlloc(arena, .{ - .root_name = comp.bin_file.options.root_name, - .target = comp.bin_file.options.target, - .output_mode = .Obj, - }); - if (cache_dir.joinZ(arena, &[_][]const u8{obj_basename})) |p| { - break :blk p.ptr; - } else |err| { - return err; - } - } else null; + const emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit) |emit| + try emit.directory.joinZ(arena, &[_][]const u8{self.sub_path}) + else + null; const emit_asm_path = try locPath(arena, comp.emit_asm, cache_dir); const emit_llvm_ir_path = try locPath(arena, comp.emit_llvm_ir, cache_dir); const emit_llvm_bc_path = try locPath(arena, comp.emit_llvm_bc, cache_dir); + const debug_emit_path = emit_bin_path orelse "(none)"; + log.debug("emit LLVM object to {s}", .{debug_emit_path}); + var error_message: [*:0]const u8 = undefined; if (self.target_machine.emitToFile( self.llvm_module, diff --git a/src/link.zig b/src/link.zig index 4f21a10d18..fe233e060f 100644 --- a/src/link.zig +++ b/src/link.zig @@ -245,6 +245,9 @@ pub const File = struct { }; if (use_lld) { + // TODO this intermediary_basename isn't enough; in the case of `zig build-exe`, + // we also want to put the intermediary object file in the cache while the + // main emit directory is the cwd. file.intermediary_basename = sub_path; } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index a0e79eba20..fa234f608b 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -132,7 +132,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_object = try LlvmObject.create(allocator, options); + self.llvm_object = try LlvmObject.create(allocator, sub_path, options); return self; } @@ -884,11 +884,8 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { // If there is no Zig code to compile, then we should skip flushing the output file because it // will not be part of the linker line anyway. const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { - // Both stage1 and stage2 LLVM backend put the object file in the cache directory. - if (self.base.options.use_llvm) { - // Stage2 has to call flushModule since that outputs the LLVM object file. - if (!build_options.is_stage1 or !self.base.options.use_stage1) try self.flushModule(comp); - + const use_stage1 = build_options.is_stage1 and self.base.options.use_stage1; + if (use_stage1) { const obj_basename = try std.zig.binNameAlloc(arena, .{ .root_name = self.base.options.root_name, .target = self.base.options.target, @@ -1269,22 +1266,23 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { try argv.append(comp.libunwind_static_lib.?.full_object_path); } - // TODO: remove when stage2 can build compiler_rt.zig, c.zig and ssp.zig - // compiler-rt, libc and libssp - if (is_exe_or_dyn_lib and - !self.base.options.skip_linker_dependencies and - build_options.is_stage1 and self.base.options.use_stage1) - { + if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies) { if (!self.base.options.link_libc) { - try argv.append(comp.libc_static_lib.?.full_object_path); + if (comp.libc_static_lib) |lib| { + try argv.append(lib.full_object_path); + } } // MinGW doesn't provide libssp symbols if (target.abi.isGnu()) { - try argv.append(comp.libssp_static_lib.?.full_object_path); + if (comp.libssp_static_lib) |lib| { + try argv.append(lib.full_object_path); + } } // MSVC compiler_rt is missing some stuff, so we build it unconditionally but // and rely on weak linkage to allow MSVC compiler_rt functions to override ours. - try argv.append(comp.compiler_rt_static_lib.?.full_object_path); + if (comp.compiler_rt_static_lib) |lib| { + try argv.append(lib.full_object_path); + } } try argv.ensureUnusedCapacity(self.base.options.system_libs.count()); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index afa976d741..ec047ced35 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -235,7 +235,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_object = try LlvmObject.create(allocator, options); + self.llvm_object = try LlvmObject.create(allocator, sub_path, options); return self; } @@ -1254,11 +1254,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { // If there is no Zig code to compile, then we should skip flushing the output file because it // will not be part of the linker line anyway. const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: { - // Both stage1 and stage2 LLVM backend put the object file in the cache directory. - if (self.base.options.use_llvm) { - // Stage2 has to call flushModule since that outputs the LLVM object file. - if (!build_options.is_stage1 or !self.base.options.use_stage1) try self.flushModule(comp); - + // stage1 puts the object file in the cache directory. + if (self.base.options.use_stage1) { const obj_basename = try std.zig.binNameAlloc(arena, .{ .root_name = self.base.options.root_name, .target = self.base.options.target, @@ -1621,14 +1618,13 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { } // libc - // TODO: enable when stage2 can build c.zig if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies and - !self.base.options.link_libc and - build_options.is_stage1 and - self.base.options.use_stage1) + !self.base.options.link_libc) { - try argv.append(comp.libc_static_lib.?.full_object_path); + if (comp.libc_static_lib) |lib| { + try argv.append(lib.full_object_path); + } } // compiler-rt diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 5551c31632..e69b85ca7f 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -290,7 +290,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_object = try LlvmObject.create(allocator, options); + self.llvm_object = try LlvmObject.create(allocator, sub_path, options); return self; } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 2f5620e47a..3de1fb49cc 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -121,7 +121,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio const self = try createEmpty(allocator, options); errdefer self.base.destroy(); - self.llvm_object = try LlvmObject.create(allocator, options); + self.llvm_object = try LlvmObject.create(allocator, sub_path, options); return self; } From 664941bf14cad3e62b453f83153ca4b65606707b Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Fri, 24 Sep 2021 13:39:20 -0400 Subject: [PATCH 111/160] Spelling corrections (#9833) Signed-off-by: Josh Soref Co-authored-by: Josh Soref --- ci/azure/windows_msvc_script.bat | 2 +- doc/langref.html.in | 24 ++++++++++++------------ src/AstGen.zig | 4 ++-- src/Compilation.zig | 2 +- src/Liveness.zig | 2 +- src/Module.zig | 14 +++++++------- src/Sema.zig | 6 +++--- src/codegen.zig | 14 +++++++------- src/codegen/arm.zig | 2 +- src/codegen/llvm.zig | 2 +- src/codegen/spirv.zig | 4 ++-- src/codegen/wasm.zig | 6 +++--- src/link/Coff.zig | 6 +++--- src/link/MachO.zig | 8 ++++---- src/link/MachO/Atom.zig | 2 +- src/link/MachO/Trie.zig | 2 +- src/link/Plan9/aout.zig | 2 +- src/link/SpirV.zig | 4 ++-- src/link/Wasm.zig | 6 +++--- src/stage1/analyze.cpp | 2 +- src/stage1/ir.cpp | 6 +++--- src/stage1/ir_print.cpp | 4 ++-- src/stage1/parser.cpp | 2 +- src/type.zig | 2 +- src/windows_com.hpp | 2 +- src/zig_clang.cpp | 8 ++++---- src/zig_clang.h | 8 ++++---- src/zig_llvm-ar.cpp | 2 +- test/behavior/array_stage1.zig | 2 +- test/behavior/bugs/1735.zig | 2 +- test/behavior/eval_stage1.zig | 2 +- test/behavior/switch.zig | 14 +++++++------- test/behavior/vector.zig | 2 +- test/compile_errors.zig | 12 ++++++------ test/run_translated_c.zig | 4 ++-- test/stage2/darwin.zig | 2 +- test/tests.zig | 2 +- test/translate_c.zig | 4 ++-- 38 files changed, 97 insertions(+), 97 deletions(-) diff --git a/ci/azure/windows_msvc_script.bat b/ci/azure/windows_msvc_script.bat index a073650717..84b19ef2fa 100644 --- a/ci/azure/windows_msvc_script.bat +++ b/ci/azure/windows_msvc_script.bat @@ -1,7 +1,7 @@ @echo on SET "SRCROOT=%cd%" SET "PREVPATH=%PATH%" -SET "PREVMSYSEM=%MSYSTEM%" +SET "PREVMSYSTEM=%MSYSTEM%" set "PATH=%CD:~0,2%\msys64\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem" SET "MSYSTEM=MINGW64" diff --git a/doc/langref.html.in b/doc/langref.html.in index 97503aed72..561065bc0d 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2125,7 +2125,7 @@ fn dump(args: anytype) !void { {#header_open|Multidimensional Arrays#}

    - Mutlidimensional arrays can be created by nesting arrays: + Multidimensional arrays can be created by nesting arrays:

    {#code_begin|test|multidimensional#} const std = @import("std"); @@ -2898,7 +2898,7 @@ fn bar(x: *const u3) u3 { } {#code_end#}

    - In this case, the function {#syntax#}bar{#endsyntax#} cannot be called becuse the pointer + In this case, the function {#syntax#}bar{#endsyntax#} cannot be called because the pointer to the non-ABI-aligned field mentions the bit offset, but the function expects an ABI-aligned pointer.

    @@ -5549,7 +5549,7 @@ test "coerce to optionals" { } {#code_end#}

    It works nested inside the {#link|Error Union Type#}, too:

    - {#code_begin|test|test_corerce_optional_wrapped_error_union#} + {#code_begin|test|test_coerce_optional_wrapped_error_union#} const std = @import("std"); const expect = std.testing.expect; @@ -7669,7 +7669,7 @@ test "main" { } {#code_end#}

    - will ouput: + will output:

    If all {#syntax#}@compileLog{#endsyntax#} calls are removed or @@ -7786,7 +7786,7 @@ test "main" { the tag value is used as the enumeration value.

    - If there is only one possible enum value, the resut is a {#syntax#}comptime_int{#endsyntax#} + If there is only one possible enum value, the result is a {#syntax#}comptime_int{#endsyntax#} known at {#link|comptime#}.

    {#see_also|@intToEnum#} @@ -8736,7 +8736,7 @@ fn doTheTest() !void { {#header_open|@sin#}
    {#syntax#}@sin(value: anytype) @TypeOf(value){#endsyntax#}

    - Sine trigometric function on a floating point number. Uses a dedicated hardware instruction + Sine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available.

    @@ -8747,7 +8747,7 @@ fn doTheTest() !void { {#header_open|@cos#}

    {#syntax#}@cos(value: anytype) @TypeOf(value){#endsyntax#}

    - Cosine trigometric function on a floating point number. Uses a dedicated hardware instruction + Cosine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available.

    @@ -10325,7 +10325,7 @@ pub fn main() void {

    Some C constructs cannot be translated to Zig - for example, goto, structs with bitfields, and token-pasting macros. Zig employs demotion to allow translation - to continue in the face of non-translateable entities. + to continue in the face of non-translatable entities.

    Demotion comes in three varieties - {#link|opaque#}, extern, and @@ -10335,13 +10335,13 @@ pub fn main() void { Functions that contain opaque types or code constructs that cannot be translated will be demoted to {#syntax#}extern{#endsyntax#} declarations. - Thus, non-translateable types can still be used as pointers, and non-translateable functions + Thus, non-translatable types can still be used as pointers, and non-translatable functions can be called so long as the linker is aware of the compiled function.

    {#syntax#}@compileError{#endsyntax#} is used when top-level definitions (global variables, function prototypes, macros) cannot be translated or demoted. Since Zig uses lazy analysis for - top-level declarations, untranslateable entities will not cause a compile error in your code unless + top-level declarations, untranslatable entities will not cause a compile error in your code unless you actually use them.

    {#see_also|opaque|extern|@compileError#} @@ -10353,7 +10353,7 @@ pub fn main() void { can be translated to Zig. Macros that cannot be translated will be be demoted to {#syntax#}@compileError{#endsyntax#}. Note that C code which uses macros will be translated without any additional issues (since Zig operates on the pre-processed source - with macros expanded). It is merely the macros themselves which may not be translateable to + with macros expanded). It is merely the macros themselves which may not be translatable to Zig.

    Consider the following example:

    @@ -10373,7 +10373,7 @@ pub export fn foo() c_int { } pub const MAKELOCAL = @compileError("unable to translate C expr: unexpected token .Equal"); // macro.c:1:9 {#code_end#} -

    Note that {#syntax#}foo{#endsyntax#} was translated correctly despite using a non-translateable +

    Note that {#syntax#}foo{#endsyntax#} was translated correctly despite using a non-translatable macro. {#syntax#}MAKELOCAL{#endsyntax#} was demoted to {#syntax#}@compileError{#endsyntax#} since it cannot be expressed as a Zig function; this simply means that you cannot directly use {#syntax#}MAKELOCAL{#endsyntax#} from Zig. diff --git a/src/AstGen.zig b/src/AstGen.zig index 14ad6c94a7..416584bee9 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -197,7 +197,7 @@ pub const ResultLoc = union(enum) { none_or_ref, /// The expression will be coerced into this type, but it will be evaluated as an rvalue. ty: Zir.Inst.Ref, - /// Same as `ty` but it is guaranteed that Sema will additionall perform the coercion, + /// Same as `ty` but it is guaranteed that Sema will additionally perform the coercion, /// so no `as` instruction needs to be emitted. coerced_ty: Zir.Inst.Ref, /// The expression must store its result into this typed pointer. The result instruction @@ -479,7 +479,7 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins return expr(gz, scope, .ref, node); } -/// Turn Zig AST into untyped ZIR istructions. +/// Turn Zig AST into untyped ZIR instructions. /// When `rl` is discard, ptr, inferred_ptr, or inferred_ptr, the /// result instruction can be used to inspect whether it is isNoReturn() but that is it, /// it must otherwise not be used. diff --git a/src/Compilation.zig b/src/Compilation.zig index 00a737c2be..ef762dae1e 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -812,7 +812,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const needs_c_symbols = !options.skip_linker_dependencies and is_exe_or_dyn_lib; - // WASI-only. Resolve the optinal exec-model option, defaults to command. + // WASI-only. Resolve the optional exec-model option, defaults to command. const wasi_exec_model = if (options.target.os.tag != .wasi) undefined else options.wasi_exec_model orelse .command; const comp: *Compilation = comp: { diff --git a/src/Liveness.zig b/src/Liveness.zig index dd0899e745..4da5eaa284 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -30,7 +30,7 @@ tomb_bits: []usize, /// The main tomb bits are still used and the extra ones are starting with the lsb of the /// value here. special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32), -/// Auxilliary data. The way this data is interpreted is determined contextually. +/// Auxiliary data. The way this data is interpreted is determined contextually. extra: []const u32, /// Trailing is the set of instructions whose lifetimes end at the start of the then branch, diff --git a/src/Module.zig b/src/Module.zig index a6a9225d75..33d8f6b715 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2188,39 +2188,39 @@ pub const LazySrcLoc = union(enum) { node_offset_bin_op: i32, /// The source location points to the LHS of a binary expression, found /// by taking this AST node index offset from the containing Decl AST node, - /// which points to a binary expression AST node. Next, nagivate to the LHS. + /// which points to a binary expression AST node. Next, navigate to the LHS. /// The Decl is determined contextually. node_offset_bin_lhs: i32, /// The source location points to the RHS of a binary expression, found /// by taking this AST node index offset from the containing Decl AST node, - /// which points to a binary expression AST node. Next, nagivate to the RHS. + /// which points to a binary expression AST node. Next, navigate to the RHS. /// The Decl is determined contextually. node_offset_bin_rhs: i32, /// The source location points to the operand of a switch expression, found /// by taking this AST node index offset from the containing Decl AST node, - /// which points to a switch expression AST node. Next, nagivate to the operand. + /// which points to a switch expression AST node. Next, navigate to the operand. /// The Decl is determined contextually. node_offset_switch_operand: i32, /// The source location points to the else/`_` prong of a switch expression, found /// by taking this AST node index offset from the containing Decl AST node, - /// which points to a switch expression AST node. Next, nagivate to the else/`_` prong. + /// which points to a switch expression AST node. Next, navigate to the else/`_` prong. /// The Decl is determined contextually. node_offset_switch_special_prong: i32, /// The source location points to all the ranges of a switch expression, found /// by taking this AST node index offset from the containing Decl AST node, - /// which points to a switch expression AST node. Next, nagivate to any of the + /// which points to a switch expression AST node. Next, navigate to any of the /// range nodes. The error applies to all of them. /// The Decl is determined contextually. node_offset_switch_range: i32, /// The source location points to the calling convention of a function type /// expression, found by taking this AST node index offset from the containing - /// Decl AST node, which points to a function type AST node. Next, nagivate to + /// Decl AST node, which points to a function type AST node. Next, navigate to /// the calling convention node. /// The Decl is determined contextually. node_offset_fn_type_cc: i32, /// The source location points to the return type of a function type /// expression, found by taking this AST node index offset from the containing - /// Decl AST node, which points to a function type AST node. Next, nagivate to + /// Decl AST node, which points to a function type AST node. Next, navigate to /// the return type node. /// The Decl is determined contextually. node_offset_fn_type_ret_ty: i32, diff --git a/src/Sema.zig b/src/Sema.zig index cc7a227ca6..2fa12baca6 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11164,7 +11164,7 @@ fn getBuiltinType( } /// There is another implementation of this in `Type.onePossibleValue`. This one -/// in `Sema` is for calling during semantic analysis, and peforms field resolution +/// in `Sema` is for calling during semantic analysis, and performs field resolution /// to get the answer. The one in `Type` is for calling during codegen and asserts /// that the types are already resolved. fn typeHasOnePossibleValue( @@ -11541,7 +11541,7 @@ fn analyzeComptimeAlloc( /// The places where a user can specify an address space attribute pub const AddressSpaceContext = enum { - /// A function is specificed to be placed in a certain address space. + /// A function is specified to be placed in a certain address space. function, /// A (global) variable is specified to be placed in a certain address space. @@ -11553,7 +11553,7 @@ pub const AddressSpaceContext = enum { /// In contrast to .variable, values placed in this address space are not required to be mutable. constant, - /// A pointer is ascripted to point into a certian address space. + /// A pointer is ascripted to point into a certain address space. pointer, }; diff --git a/src/codegen.zig b/src/codegen.zig index 06b520c9dd..56580f91e1 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -985,7 +985,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // increasing the pc const d_pc_p9 = @intCast(i64, delta_pc) - quant; if (d_pc_p9 > 0) { - // minus one becaue if its the last one, we want to leave space to change the line which is one quanta + // minus one because if its the last one, we want to leave space to change the line which is one quanta try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant); if (dbg_out.pcop_change_index.*) |pci| dbg_out.dbg_line.items[pci] += 1; @@ -1919,15 +1919,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, .shl => { assert(!swap_lhs_and_rhs); - const shift_amout = switch (operand) { + const shift_amount = switch (operand) { .Register => |reg_op| Instruction.ShiftAmount.reg(@intToEnum(Register, reg_op.rm)), .Immediate => |imm_op| Instruction.ShiftAmount.imm(@intCast(u5, imm_op.imm)), }; - writeInt(u32, try self.code.addManyAsArray(4), Instruction.lsl(.al, dst_reg, op1, shift_amout).toU32()); + writeInt(u32, try self.code.addManyAsArray(4), Instruction.lsl(.al, dst_reg, op1, shift_amount).toU32()); }, .shr => { assert(!swap_lhs_and_rhs); - const shift_amout = switch (operand) { + const shift_amount = switch (operand) { .Register => |reg_op| Instruction.ShiftAmount.reg(@intToEnum(Register, reg_op.rm)), .Immediate => |imm_op| Instruction.ShiftAmount.imm(@intCast(u5, imm_op.imm)), }; @@ -1936,7 +1936,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .signed => Instruction.asr, .unsigned => Instruction.lsr, }; - writeInt(u32, try self.code.addManyAsArray(4), shr(.al, dst_reg, op1, shift_amout).toU32()); + writeInt(u32, try self.code.addManyAsArray(4), shr(.al, dst_reg, op1, shift_amount).toU32()); }, else => unreachable, // not a binary instruction } @@ -3618,7 +3618,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { try self.blocks.putNoClobber(self.gpa, inst, .{ // A block is a setup to be able to jump to the end. .relocs = .{}, - // It also acts as a receptical for break operands. + // It also acts as a receptacle for break operands. // Here we use `MCValue.none` to represent a null value so that the first // break instruction will choose a MCValue for the block result and overwrite // this field. Following break instructions will use that MCValue to put their @@ -3672,7 +3672,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.fail("TODO: enable larger branch offset", .{}); } }, - else => unreachable, // attempting to perfrom an ARM relocation on a non-ARM target arch + else => unreachable, // attempting to perform an ARM relocation on a non-ARM target arch } }, } diff --git a/src/codegen/arm.zig b/src/codegen/arm.zig index ec9152f96b..279ce58005 100644 --- a/src/codegen/arm.zig +++ b/src/codegen/arm.zig @@ -2,7 +2,7 @@ const std = @import("std"); const DW = std.dwarf; const testing = std.testing; -/// The condition field specifies the flags neccessary for an +/// The condition field specifies the flags necessary for an /// Instruction to be executed pub const Condition = enum(u4) { /// equal diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 650628d8c2..b2b87b97d8 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1161,7 +1161,7 @@ pub const FuncGen = struct { /// in other instructions. This table is cleared before every function is generated. func_inst_table: std.AutoHashMapUnmanaged(Air.Inst.Index, *const llvm.Value), - /// These fields are used to refer to the LLVM value of the function paramaters + /// These fields are used to refer to the LLVM value of the function parameters /// in an Arg instruction. args: []*const llvm.Value, arg_index: usize, diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index f5796b06bc..5826daa5a5 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -260,7 +260,7 @@ pub const DeclGen = struct { }; } - /// Generate the code for `decl`. If a reportable error occured during code generation, + /// Generate the code for `decl`. If a reportable error occurred during code generation, /// a message is returned by this function. Callee owns the memory. If this function /// returns such a reportable error, it is valid to be called again for a different decl. pub fn gen(self: *DeclGen, decl: *Decl, air: Air, liveness: Liveness) !?*Module.ErrorMsg { @@ -565,7 +565,7 @@ pub const DeclGen = struct { } }, // When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType. - .Pointer => return self.fail("Cannot create pointer with unkown storage class", .{}), + .Pointer => return self.fail("Cannot create pointer with unknown storage class", .{}), .Vector => { // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations // which work on them), so simply use those. diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index bb05567236..9bd80f7d84 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -1005,7 +1005,7 @@ pub const Context = struct { const rhs = self.resolveInst(bin_op.rhs); // it's possible for both lhs and/or rhs to return an offset as well, - // in which case we return the first offset occurance we find. + // in which case we return the first offset occurrence we find. const offset = blk: { if (lhs == .code_offset) break :blk lhs.code_offset; if (rhs == .code_offset) break :blk rhs.code_offset; @@ -1031,7 +1031,7 @@ pub const Context = struct { const rhs = self.resolveInst(bin_op.rhs); // it's possible for both lhs and/or rhs to return an offset as well, - // in which case we return the first offset occurance we find. + // in which case we return the first offset occurrence we find. const offset = blk: { if (lhs == .code_offset) break :blk lhs.code_offset; if (rhs == .code_offset) break :blk rhs.code_offset; @@ -1395,7 +1395,7 @@ pub const Context = struct { } // We map every block to its block index. - // We then determine how far we have to jump to it by substracting it from current block depth + // We then determine how far we have to jump to it by subtracting it from current block depth const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?; const writer = self.code.writer(); try writer.writeByte(wasm.opcode(.br)); diff --git a/src/link/Coff.zig b/src/link/Coff.zig index fa234f608b..fd009ca9f8 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -50,7 +50,7 @@ last_text_block: ?*TextBlock = null, section_table_offset: u32 = 0, /// Section data file pointer. section_data_offset: u32 = 0, -/// Optiona header file pointer. +/// Optional header file pointer. optional_header_offset: u32 = 0, /// Absolute virtual address of the offset table when the executable is loaded in memory. @@ -602,7 +602,7 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void { const current_virtual_size = mem.alignForwardGeneric(u32, self.offset_table_size, section_alignment); const new_virtual_size = mem.alignForwardGeneric(u32, new_raw_size, section_alignment); // If we had to move in the virtual address space, we need to fix the VAs in the offset table, as well as the virtual address of the `.text` section - // and the virutal size of the `.got` section + // and the virtual size of the `.got` section if (new_virtual_size != current_virtual_size) { log.debug("growing offset table from virtual size {} to {}\n", .{ current_virtual_size, new_virtual_size }); @@ -980,7 +980,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); if (self.base.options.output_mode == .Obj) { - // LLD's COFF driver does not support the equvialent of `-r` so we do a simple file copy + // LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy // here. TODO: think carefully about how we can avoid this redundant operation when doing // build-obj. See also the corresponding TODO in linkAsArchive. const the_object_path = blk: { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index e69b85ca7f..dc44474c0a 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -213,7 +213,7 @@ decls: std.AutoArrayHashMapUnmanaged(*Module.Decl, void) = .{}, /// Currently active Module.Decl. /// TODO this might not be necessary if we figure out how to pass Module.Decl instance -/// to codegen.genSetReg() or alterntively move PIE displacement for MCValue{ .memory = x } +/// to codegen.genSetReg() or alternatively move PIE displacement for MCValue{ .memory = x } /// somewhere else in the codegen. active_decl: ?*Module.Decl = null, @@ -512,7 +512,7 @@ pub fn flush(self: *MachO, comp: *Compilation) !void { const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); if (self.base.options.output_mode == .Obj) { - // LLD's MachO driver does not support the equvialent of `-r` so we do a simple file copy + // LLD's MachO driver does not support the equivalent of `-r` so we do a simple file copy // here. TODO: think carefully about how we can avoid this redundant operation when doing // build-obj. See also the corresponding TODO in linkAsArchive. const the_object_path = blk: { @@ -2245,7 +2245,7 @@ pub fn createStubAtom(self: *MachO, laptr_sym_index: u32) !*Atom { fn createTentativeDefAtoms(self: *MachO) !void { if (self.tentatives.count() == 0) return; // Convert any tentative definition into a regular symbol and allocate - // text blocks for each tentative defintion. + // text blocks for each tentative definition. while (self.tentatives.popOrNull()) |entry| { const match = MatchingSection{ .seg = self.data_segment_cmd_index.?, @@ -4609,7 +4609,7 @@ fn populateLazyBindOffsetsInStubHelper(self: *MachO, buffer: []const u8) !void { // Because we insert lazy binding opcodes in reverse order (from last to the first atom), // we need reverse the order of atom traversal here as well. - // TODO figure out a less error prone mechanims for this! + // TODO figure out a less error prone mechanisms for this! var atom = last_atom; while (atom.prev) |prev| { atom = prev; diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index bb6730fe0f..46e5191ab3 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -41,7 +41,7 @@ code: std.ArrayListUnmanaged(u8) = .{}, size: u64, /// Alignment of this atom as a power of 2. -/// For instance, aligmment of 0 should be read as 2^0 = 1 byte aligned. +/// For instance, alignment of 0 should be read as 2^0 = 1 byte aligned. alignment: u32, /// List of relocations belonging to this atom. diff --git a/src/link/MachO/Trie.zig b/src/link/MachO/Trie.zig index 7bf451f2c8..c166aaf432 100644 --- a/src/link/MachO/Trie.zig +++ b/src/link/MachO/Trie.zig @@ -506,7 +506,7 @@ test "write Trie to a byte stream" { }); try trie.finalize(gpa); - try trie.finalize(gpa); // Finalizing mulitple times is a nop subsequently unless we add new nodes. + try trie.finalize(gpa); // Finalizing multiple times is a nop subsequently unless we add new nodes. const exp_buffer = [_]u8{ 0x0, 0x1, // node root diff --git a/src/link/Plan9/aout.zig b/src/link/Plan9/aout.zig index f3570f880c..39994516fa 100644 --- a/src/link/Plan9/aout.zig +++ b/src/link/Plan9/aout.zig @@ -16,7 +16,7 @@ pub const ExecHdr = extern struct { comptime { assert(@sizeOf(@This()) == 32); } - /// It is up to the caller to disgard the last 8 bytes if the header is not fat. + /// It is up to the caller to discard the last 8 bytes if the header is not fat. pub fn toU8s(self: *@This()) [40]u8 { var buf: [40]u8 = undefined; var i: u8 = 0; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 17b656a06c..f9d3f7a1e6 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -12,7 +12,7 @@ //! - OpName and OpMemberName instructions. //! - OpModuleProcessed instructions. //! All annotation (decoration) instructions. -//! All type declaration instructions, constant instructions, global variable declarations, (preferrably) OpUndef instructions. +//! All type declaration instructions, constant instructions, global variable declarations, (preferably) OpUndef instructions. //! All function declarations without a body (extern functions presumably). //! All regular functions. @@ -93,7 +93,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_llvm) return error.LLVM_BackendIsTODO_ForSpirV; // TODO: LLVM Doesn't support SpirV at all. if (options.use_lld) return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all. - // TODO: read the file and keep vaild parts instead of truncating + // TODO: read the file and keep valid parts instead of truncating const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true }); errdefer file.close(); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 3de1fb49cc..a75ad1b2f7 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -35,7 +35,7 @@ llvm_object: ?*LlvmObject = null, /// TODO: can/should we access some data structure in Module directly? funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, /// List of all extern function Decls to be written to the `import` section of the -/// wasm binary. The positin in the list defines the function index +/// wasm binary. The position in the list defines the function index ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, /// When importing objects from the host environment, a name must be supplied. /// LLVM uses "env" by default when none is given. This would be a good default for Zig @@ -714,7 +714,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path}); if (self.base.options.output_mode == .Obj) { - // LLD's WASM driver does not support the equvialent of `-r` so we do a simple file copy + // LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy // here. TODO: think carefully about how we can avoid this redundant operation when doing // build-obj. See also the corresponding TODO in linkAsArchive. const the_object_path = blk: { @@ -756,7 +756,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { if (self.base.options.output_mode == .Exe) { // Increase the default stack size to a more reasonable value of 1MB instead of - // the default of 1 Wasm page being 64KB, unless overriden by the user. + // the default of 1 Wasm page being 64KB, unless overridden by the user. try argv.append("-z"); const stack_size = self.base.options.stack_size_override orelse 1048576; const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size}); diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index 320d8ff9b2..ec4ff8fc9e 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -6804,7 +6804,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { // Since this frame is async, an await might represent a suspend point, and // therefore need to spill. It also needs to mark expr scopes as having to spill. // For example: foo() + await z - // The funtion call result of foo() must be spilled. + // The function call result of foo() must be spilled. for (size_t i = 0; i < fn->await_list.length; i += 1) { Stage1AirInstAwait *await = fn->await_list.at(i); if (await->is_nosuspend) { diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 87dfee1bf2..b853961beb 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -6374,7 +6374,7 @@ static Stage1AirInst *ir_analyze_enum_to_union(IrAnalyze *ira, Scope *scope, Ast if (target->value->type->data.enumeration.non_exhaustive) { ir_add_error_node(ira, source_node, - buf_sprintf("runtime cast to union '%s' from non-exhustive enum", + buf_sprintf("runtime cast to union '%s' from non-exhaustive enum", buf_ptr(&wanted_type->name))); return ira->codegen->invalid_inst_gen; } @@ -15189,7 +15189,7 @@ static Stage1AirInst *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_ return ir_analyze_inferred_field_ptr(ira, field_name, scope, source_node, container_ptr, bare_type); } - // Tracks wether we should return an undefined value of the correct type. + // Tracks whether we should return an undefined value of the correct type. // We do this if the container pointer is undefined and we are in a TypeOf call. bool return_undef = container_ptr->value->special == ConstValSpecialUndef && \ get_scope_typeof(scope) != nullptr; @@ -15248,7 +15248,7 @@ static Stage1AirInst *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_ if (type_is_invalid(union_val->type)) return ira->codegen->invalid_inst_gen; - // Reject undefined values unless we're intializing the union: + // Reject undefined values unless we're initializing the union: // a undefined union means also the tag is undefined, accessing // its payload slot is UB. const UndefAllowed allow_undef = initializing ? UndefOk : UndefBad; diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp index 152221926d..a76d3e4d5a 100644 --- a/src/stage1/ir_print.cpp +++ b/src/stage1/ir_print.cpp @@ -558,7 +558,7 @@ const char* ir_inst_gen_type_str(Stage1AirInstId id) { case Stage1AirInstIdWasmMemoryGrow: return "GenWasmMemoryGrow"; case Stage1AirInstIdExtern: - return "GenExtrern"; + return "GenExtern"; } zig_unreachable(); } @@ -829,7 +829,7 @@ static const char *cast_op_str(CastOp op) { case CastOpIntToFloat: return "IntToFloat"; case CastOpFloatToInt: return "FloatToInt"; case CastOpBoolToInt: return "BoolToInt"; - case CastOpNumLitToConcrete: return "NumLitToConcrate"; + case CastOpNumLitToConcrete: return "NumLitToConcrete"; case CastOpErrSet: return "ErrSet"; case CastOpBitCast: return "BitCast"; } diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp index b06a944172..f7061bb232 100644 --- a/src/stage1/parser.cpp +++ b/src/stage1/parser.cpp @@ -2073,7 +2073,7 @@ static AstNode *ast_parse_field_init(ParseContext *pc) { return nullptr; } if (eat_token_if(pc, TokenIdEq) == 0) { - // Because ".Name" can also be intepreted as an enum literal, we should put back + // Because ".Name" can also be interpreted as an enum literal, we should put back // those two tokens again so that the parser can try to parse them as the enum // literal later. put_back_token(pc); diff --git a/src/type.zig b/src/type.zig index f2fe9e4ccb..4a0f2a0536 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3419,7 +3419,7 @@ pub const Type = extern union { anyerror_void_error_union, generic_poison, /// This is a special type for variadic parameters of a function call. - /// Casts to it will validate that the type can be passed to a c calling convetion function. + /// Casts to it will validate that the type can be passed to a c calling convention function. var_args_param, /// Same as `empty_struct` except it has an empty namespace. empty_struct_literal, diff --git a/src/windows_com.hpp b/src/windows_com.hpp index f9833e0912..5f0f5565f7 100644 --- a/src/windows_com.hpp +++ b/src/windows_com.hpp @@ -352,7 +352,7 @@ extern "C" { ///

    /// Gets product-specific properties. /// - /// A pointer to an instance of . This may be NULL if no properties are defined. + /// A pointer to an instance of . This may be NULL if no properties are defined. /// Standard HRESULT indicating success or failure, including E_FILENOTFOUND if the instance state does not exist. STDMETHOD(GetProperties)( _Outptr_result_maybenull_ ISetupPropertyStore** ppProperties diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index 6611585f68..685ee8bcce 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -1537,10 +1537,10 @@ void ZigClang_detect_enum_ConstantExprKind(clang::Expr::ConstantExprKind x) { break; } } -static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ContantExprKind_Normal == clang::Expr::ConstantExprKind::Normal, ""); -static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ContantExprKind_NonClassTemplateArgument == clang::Expr::ConstantExprKind::NonClassTemplateArgument, ""); -static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ContantExprKind_ClassTemplateArgument == clang::Expr::ConstantExprKind::ClassTemplateArgument, ""); -static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ContantExprKind_ImmediateInvocation == clang::Expr::ConstantExprKind::ImmediateInvocation, ""); +static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ConstantExprKind_Normal == clang::Expr::ConstantExprKind::Normal, ""); +static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ConstantExprKind_NonClassTemplateArgument == clang::Expr::ConstantExprKind::NonClassTemplateArgument, ""); +static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ConstantExprKind_ClassTemplateArgument == clang::Expr::ConstantExprKind::ClassTemplateArgument, ""); +static_assert((clang::Expr::ConstantExprKind)ZigClangExpr_ConstantExprKind_ImmediateInvocation == clang::Expr::ConstantExprKind::ImmediateInvocation, ""); static_assert(sizeof(ZigClangAPValue) == sizeof(clang::APValue), ""); diff --git a/src/zig_clang.h b/src/zig_clang.h index 0e7a8b2990..26b4b3ca9a 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -934,10 +934,10 @@ enum ZigClangPreprocessedEntity_EntityKind { }; enum ZigClangExpr_ConstantExprKind { - ZigClangExpr_ContantExprKind_Normal, - ZigClangExpr_ContantExprKind_NonClassTemplateArgument, - ZigClangExpr_ContantExprKind_ClassTemplateArgument, - ZigClangExpr_ContantExprKind_ImmediateInvocation, + ZigClangExpr_ConstantExprKind_Normal, + ZigClangExpr_ConstantExprKind_NonClassTemplateArgument, + ZigClangExpr_ConstantExprKind_ClassTemplateArgument, + ZigClangExpr_ConstantExprKind_ImmediateInvocation, }; enum ZigClangUnaryExprOrTypeTrait_Kind { diff --git a/src/zig_llvm-ar.cpp b/src/zig_llvm-ar.cpp index e4c376adbc..107f500fcb 100644 --- a/src/zig_llvm-ar.cpp +++ b/src/zig_llvm-ar.cpp @@ -492,7 +492,7 @@ static std::string ArchiveName; static std::vector> ArchiveBuffers; static std::vector> Archives; -// This variable holds the list of member files to proecess, as given +// This variable holds the list of member files to process, as given // on the command line. static std::vector Members; diff --git a/test/behavior/array_stage1.zig b/test/behavior/array_stage1.zig index d3a97665ac..c290ef9a08 100644 --- a/test/behavior/array_stage1.zig +++ b/test/behavior/array_stage1.zig @@ -140,7 +140,7 @@ fn testArrayByValAtComptime(b: [2]u8) u8 { return b[0]; } -test "comptime evalutating function that takes array by value" { +test "comptime evaluating function that takes array by value" { const arr = [_]u8{ 0, 1 }; _ = comptime testArrayByValAtComptime(arr); _ = comptime testArrayByValAtComptime(arr); diff --git a/test/behavior/bugs/1735.zig b/test/behavior/bugs/1735.zig index f3aa6eb9ec..1f6e3c99f4 100644 --- a/test/behavior/bugs/1735.zig +++ b/test/behavior/bugs/1735.zig @@ -40,7 +40,7 @@ const a = struct { } }; -test "intialization" { +test "initialization" { var t = a.init(); try std.testing.expect(t.foo.len == 0); } diff --git a/test/behavior/eval_stage1.zig b/test/behavior/eval_stage1.zig index 644de50fd0..743f7af69e 100644 --- a/test/behavior/eval_stage1.zig +++ b/test/behavior/eval_stage1.zig @@ -88,7 +88,7 @@ var st_init_str_foo = StInitStrFoo{ .y = true, }; -test "statically initalized array literal" { +test "statically initialized array literal" { const y: [4]u8 = st_init_arr_lit_x; try expect(y[3] == 4); } diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index e512565b80..62afc74d83 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -65,18 +65,18 @@ fn nonConstSwitchOnEnum(fruit: Fruit) void { } test "switch statement" { - try nonConstSwitch(SwitchStatmentFoo.C); + try nonConstSwitch(SwitchStatementFoo.C); } -fn nonConstSwitch(foo: SwitchStatmentFoo) !void { +fn nonConstSwitch(foo: SwitchStatementFoo) !void { const val = switch (foo) { - SwitchStatmentFoo.A => @as(i32, 1), - SwitchStatmentFoo.B => 2, - SwitchStatmentFoo.C => 3, - SwitchStatmentFoo.D => 4, + SwitchStatementFoo.A => @as(i32, 1), + SwitchStatementFoo.B => 2, + SwitchStatementFoo.C => 3, + SwitchStatementFoo.D => 4, }; try expect(val == 3); } -const SwitchStatmentFoo = enum { +const SwitchStatementFoo = enum { A, B, C, diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 2c615b542b..b673542f8b 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -116,7 +116,7 @@ test "array to vector" { _ = vec; } -test "vector casts of sizes not divisable by 8" { +test "vector casts of sizes not divisible by 8" { const S = struct { fn doTheTest() !void { { diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 058fd1f2db..0e59b0523f 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -683,7 +683,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = u; \\} , &[_][]const u8{ - "tmp.zig:12:16: error: runtime cast to union 'U' from non-exhustive enum", + "tmp.zig:12:16: error: runtime cast to union 'U' from non-exhaustive enum", "tmp.zig:17:16: error: no tag by value 15", }); @@ -6145,9 +6145,9 @@ pub fn addCases(ctx: *TestContext) !void { }); ctx.objErrStage1("endless loop in function evaluation", - \\const seventh_fib_number = fibbonaci(7); - \\fn fibbonaci(x: i32) i32 { - \\ return fibbonaci(x - 1) + fibbonaci(x - 2); + \\const seventh_fib_number = fibonacci(7); + \\fn fibonacci(x: i32) i32 { + \\ return fibonacci(x - 1) + fibonacci(x - 2); \\} \\ \\export fn entry() usize { return @sizeOf(@TypeOf(seventh_fib_number)); } @@ -6775,7 +6775,7 @@ pub fn addCases(ctx: *TestContext) !void { "tmp.zig:2:5: error: expression value is ignored", }); - ctx.objErrStage1("ignored defered statement value", + ctx.objErrStage1("ignored deferred statement value", \\export fn foo() void { \\ defer {1;} \\} @@ -6783,7 +6783,7 @@ pub fn addCases(ctx: *TestContext) !void { "tmp.zig:2:12: error: expression value is ignored", }); - ctx.objErrStage1("ignored defered function call", + ctx.objErrStage1("ignored deferred function call", \\export fn foo() void { \\ defer bar(); \\} diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 28ba7aa704..c222a00eb7 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -24,7 +24,7 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\} , "DEG2RAD is: 0.017453" ++ nl); - cases.add("use global scope for record/enum/typedef type transalation if needed", + cases.add("use global scope for record/enum/typedef type translation if needed", \\void bar(void); \\void baz(void); \\struct foo { int x; }; @@ -394,7 +394,7 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\} , ""); - cases.add("ensure array casts outisde +=", + cases.add("ensure array casts outside +=", \\#include \\static int hash_binary(int k) \\{ diff --git a/test/stage2/darwin.zig b/test/stage2/darwin.zig index 90058404d9..84334828bd 100644 --- a/test/stage2/darwin.zig +++ b/test/stage2/darwin.zig @@ -118,7 +118,7 @@ pub fn addCases(ctx: *TestContext) !void { { var case = ctx.exe("corner case - update existing, singular TextBlock", target); - // This test case also covers an infrequent scenarion where the string table *may* be relocated + // This test case also covers an infrequent scenario where the string table *may* be relocated // into the position preceeding the symbol table which results in a dyld error. case.addCompareOutput( \\extern fn exit(usize) noreturn; diff --git a/test/tests.zig b/test/tests.zig index a90531c600..0577092845 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -455,7 +455,7 @@ pub fn addTranslateCTests(b: *build.Builder, test_filter: ?[]const u8) *build.St const cases = b.allocator.create(TranslateCContext) catch unreachable; cases.* = TranslateCContext{ .b = b, - .step = b.step("test-translate-c", "Run the C transation tests"), + .step = b.step("test-translate-c", "Run the C translation tests"), .test_index = 0, .test_filter = test_filter, }; diff --git a/test/translate_c.zig b/test/translate_c.zig index 5ff6c22ead..db665124ba 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -3188,7 +3188,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\} }); - cases.add("macro comparisions", + cases.add("macro comparisons", \\#define MIN(a, b) ((b) < (a) ? (b) : (a)) \\#define MAX(a, b) ((b) > (a) ? (b) : (a)) , &[_][]const u8{ @@ -3443,7 +3443,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { }); } - cases.add("unnamed fields have predictabile names", + cases.add("unnamed fields have predictable names", \\struct a { \\ struct {}; \\}; From 8f58e2d77951cdb046e365394c5f02c9b3a93a4f Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Thu, 23 Sep 2021 22:47:12 +0200 Subject: [PATCH 112/160] stage2 codegen: move bit definitions to src/arch --- CMakeLists.txt | 8 ++-- .../aarch64.zig => arch/aarch64/bits.zig} | 0 src/{codegen/arm.zig => arch/arm/bits.zig} | 0 .../riscv64.zig => arch/riscv64/bits.zig} | 0 src/{codegen/x86.zig => arch/x86/bits.zig} | 0 .../x86_64.zig => arch/x86_64/bits.zig} | 0 src/codegen.zig | 48 +++++++++---------- src/link/MachO.zig | 4 +- src/link/MachO/Atom.zig | 2 +- 9 files changed, 31 insertions(+), 31 deletions(-) rename src/{codegen/aarch64.zig => arch/aarch64/bits.zig} (100%) rename src/{codegen/arm.zig => arch/arm/bits.zig} (100%) rename src/{codegen/riscv64.zig => arch/riscv64/bits.zig} (100%) rename src/{codegen/x86.zig => arch/x86/bits.zig} (100%) rename src/{codegen/x86_64.zig => arch/x86_64/bits.zig} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 42c0b7e0da..2839a3cffc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -551,18 +551,18 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/TypedValue.zig" "${CMAKE_SOURCE_DIR}/src/WaitGroup.zig" "${CMAKE_SOURCE_DIR}/src/Zir.zig" + "${CMAKE_SOURCE_DIR}/src/arch/aarch64/bits.zig" + "${CMAKE_SOURCE_DIR}/src/arch/arm/bits.zig" + "${CMAKE_SOURCE_DIR}/src/arch/riscv64/bits.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig" "${CMAKE_SOURCE_DIR}/src/clang.zig" "${CMAKE_SOURCE_DIR}/src/clang_options.zig" "${CMAKE_SOURCE_DIR}/src/clang_options_data.zig" "${CMAKE_SOURCE_DIR}/src/codegen.zig" - "${CMAKE_SOURCE_DIR}/src/codegen/aarch64.zig" - "${CMAKE_SOURCE_DIR}/src/codegen/arm.zig" "${CMAKE_SOURCE_DIR}/src/codegen/c.zig" "${CMAKE_SOURCE_DIR}/src/codegen/llvm.zig" "${CMAKE_SOURCE_DIR}/src/codegen/llvm/bindings.zig" - "${CMAKE_SOURCE_DIR}/src/codegen/riscv64.zig" "${CMAKE_SOURCE_DIR}/src/codegen/wasm.zig" - "${CMAKE_SOURCE_DIR}/src/codegen/x86_64.zig" "${CMAKE_SOURCE_DIR}/src/glibc.zig" "${CMAKE_SOURCE_DIR}/src/introspect.zig" "${CMAKE_SOURCE_DIR}/src/libc_installation.zig" diff --git a/src/codegen/aarch64.zig b/src/arch/aarch64/bits.zig similarity index 100% rename from src/codegen/aarch64.zig rename to src/arch/aarch64/bits.zig diff --git a/src/codegen/arm.zig b/src/arch/arm/bits.zig similarity index 100% rename from src/codegen/arm.zig rename to src/arch/arm/bits.zig diff --git a/src/codegen/riscv64.zig b/src/arch/riscv64/bits.zig similarity index 100% rename from src/codegen/riscv64.zig rename to src/arch/riscv64/bits.zig diff --git a/src/codegen/x86.zig b/src/arch/x86/bits.zig similarity index 100% rename from src/codegen/x86.zig rename to src/arch/x86/bits.zig diff --git a/src/codegen/x86_64.zig b/src/arch/x86_64/bits.zig similarity index 100% rename from src/codegen/x86_64.zig rename to src/arch/x86_64/bits.zig diff --git a/src/codegen.zig b/src/codegen.zig index 56580f91e1..f812cbc5d4 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -21,7 +21,7 @@ const log = std.log.scoped(.codegen); const build_options = @import("build_options"); const RegisterManager = @import("register_manager.zig").RegisterManager; -const X8664Encoder = @import("codegen/x86_64.zig").Encoder; +const X8664Encoder = @import("arch/x86_64/bits.zig").Encoder; pub const FnResult = union(enum) { /// The `code` parameter passed to `generateSymbol` has the value appended. @@ -470,7 +470,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { /// A branch in the ARM instruction set arm_branch: struct { pos: usize, - cond: @import("codegen/arm.zig").Condition, + cond: @import("arch/arm/bits.zig").Condition, }, }; @@ -5336,11 +5336,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } const Register = switch (arch) { - .i386 => @import("codegen/x86.zig").Register, - .x86_64 => @import("codegen/x86_64.zig").Register, - .riscv64 => @import("codegen/riscv64.zig").Register, - .arm, .armeb => @import("codegen/arm.zig").Register, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").Register, + .i386 => @import("arch/x86/bits.zig").Register, + .x86_64 => @import("arch/x86_64/bits.zig").Register, + .riscv64 => @import("arch/riscv64/bits.zig").Register, + .arm, .armeb => @import("arch/arm/bits.zig").Register, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").Register, else => enum { dummy, @@ -5352,39 +5352,39 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }; const Instruction = switch (arch) { - .riscv64 => @import("codegen/riscv64.zig").Instruction, - .arm, .armeb => @import("codegen/arm.zig").Instruction, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").Instruction, + .riscv64 => @import("arch/riscv64/bits.zig").Instruction, + .arm, .armeb => @import("arch/arm/bits.zig").Instruction, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").Instruction, else => void, }; const Condition = switch (arch) { - .arm, .armeb => @import("codegen/arm.zig").Condition, + .arm, .armeb => @import("arch/arm/bits.zig").Condition, else => void, }; const callee_preserved_regs = switch (arch) { - .i386 => @import("codegen/x86.zig").callee_preserved_regs, - .x86_64 => @import("codegen/x86_64.zig").callee_preserved_regs, - .riscv64 => @import("codegen/riscv64.zig").callee_preserved_regs, - .arm, .armeb => @import("codegen/arm.zig").callee_preserved_regs, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").callee_preserved_regs, + .i386 => @import("arch/x86/bits.zig").callee_preserved_regs, + .x86_64 => @import("arch/x86_64/bits.zig").callee_preserved_regs, + .riscv64 => @import("arch/riscv64/bits.zig").callee_preserved_regs, + .arm, .armeb => @import("arch/arm/bits.zig").callee_preserved_regs, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").callee_preserved_regs, else => [_]Register{}, }; const c_abi_int_param_regs = switch (arch) { - .i386 => @import("codegen/x86.zig").c_abi_int_param_regs, - .x86_64 => @import("codegen/x86_64.zig").c_abi_int_param_regs, - .arm, .armeb => @import("codegen/arm.zig").c_abi_int_param_regs, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").c_abi_int_param_regs, + .i386 => @import("arch/x86/bits.zig").c_abi_int_param_regs, + .x86_64 => @import("arch/x86_64/bits.zig").c_abi_int_param_regs, + .arm, .armeb => @import("arch/arm/bits.zig").c_abi_int_param_regs, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").c_abi_int_param_regs, else => [_]Register{}, }; const c_abi_int_return_regs = switch (arch) { - .i386 => @import("codegen/x86.zig").c_abi_int_return_regs, - .x86_64 => @import("codegen/x86_64.zig").c_abi_int_return_regs, - .arm, .armeb => @import("codegen/arm.zig").c_abi_int_return_regs, - .aarch64, .aarch64_be, .aarch64_32 => @import("codegen/aarch64.zig").c_abi_int_return_regs, + .i386 => @import("arch/x86/bits.zig").c_abi_int_return_regs, + .x86_64 => @import("arch/x86_64/bits.zig").c_abi_int_return_regs, + .arm, .armeb => @import("arch/arm/bits.zig").c_abi_int_return_regs, + .aarch64, .aarch64_be, .aarch64_32 => @import("arch/aarch64/bits.zig").c_abi_int_return_regs, else => [_]Register{}, }; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index dc44474c0a..9a2c462d55 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -12,7 +12,7 @@ const math = std.math; const mem = std.mem; const meta = std.meta; -const aarch64 = @import("../codegen/aarch64.zig"); +const aarch64 = @import("../arch/aarch64/bits.zig"); const bind = @import("MachO/bind.zig"); const codegen = @import("../codegen.zig"); const commands = @import("MachO/commands.zig"); @@ -200,7 +200,7 @@ atoms: std.AutoHashMapUnmanaged(MatchingSection, *Atom) = .{}, /// List of atoms that are owned directly by the linker. /// Currently these are only atoms that are the result of linking -/// object files. Atoms which take part in incremental linking are +/// object files. Atoms which take part in incremental linking are /// at present owned by Module.Decl. /// TODO consolidate this. managed_atoms: std.ArrayListUnmanaged(*Atom) = .{}, diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index 46e5191ab3..a98f624176 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -2,7 +2,7 @@ const Atom = @This(); const std = @import("std"); const build_options = @import("build_options"); -const aarch64 = @import("../../codegen/aarch64.zig"); +const aarch64 = @import("../../arch/aarch64/bits.zig"); const assert = std.debug.assert; const commands = @import("commands.zig"); const log = std.log.scoped(.text_block); From 1e7009a9d982faa466517063452ed7d299b66966 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Thu, 23 Sep 2021 18:09:35 -0500 Subject: [PATCH 113/160] Fix error references across inline and comptime functions --- src/Module.zig | 48 ++++++++++++++++++++++-------------------------- src/Sema.zig | 4 ++-- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 33d8f6b715..278f8621d8 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -248,6 +248,9 @@ pub const Export = struct { link: link.File.Export, /// The Decl that performs the export. Note that this is *not* the Decl being exported. owner_decl: *Decl, + /// The Decl containing the export statement. Inline function calls + /// may cause this to be different from the owner_decl. + src_decl: *Decl, /// The Decl being exported. Note this is *not* the Decl performing the export. exported_decl: *Decl, status: enum { @@ -261,8 +264,8 @@ pub const Export = struct { pub fn getSrcLoc(exp: Export) SrcLoc { return .{ - .file_scope = exp.owner_decl.namespace.file_scope, - .parent_decl_node = exp.owner_decl.src_node, + .file_scope = exp.src_decl.namespace.file_scope, + .parent_decl_node = exp.src_decl.src_node, .lazy = exp.src, }; } @@ -1014,15 +1017,6 @@ pub const Scope = struct { return @fieldParentPtr(T, "base", base); } - /// Get the decl that is currently being analyzed - pub fn ownerDecl(scope: *Scope) ?*Decl { - return switch (scope.tag) { - .block => scope.cast(Block).?.sema.owner_decl, - .file => null, - .namespace => null, - }; - } - /// Get the decl which contains this decl, for the purposes of source reporting pub fn srcDecl(scope: *Scope) ?*Decl { return switch (scope.tag) { @@ -3402,7 +3396,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { } // The scope needs to have the decl in it. const options: std.builtin.ExportOptions = .{ .name = mem.spanZ(decl.name) }; - try mod.analyzeExport(&block_scope.base, export_src, options, decl); + try mod.analyzeExport(&block_scope, export_src, options, decl); } return type_changed or is_inline != prev_is_inline; } @@ -3462,7 +3456,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { const export_src = src; // TODO point to the export token // The scope needs to have the decl in it. const options: std.builtin.ExportOptions = .{ .name = mem.spanZ(decl.name) }; - try mod.analyzeExport(&block_scope.base, export_src, options, decl); + try mod.analyzeExport(&block_scope, export_src, options, decl); } return type_changed; @@ -3931,7 +3925,7 @@ pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void { pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void { log.debug("deleteAnonDecl {*} ({s})", .{ decl, decl.name }); - const scope_decl = scope.ownerDecl().?; + const scope_decl = scope.srcDecl().?; assert(scope_decl.namespace.anon_decls.swapRemove(decl)); decl.destroy(mod); } @@ -4209,7 +4203,7 @@ pub fn getErrorValue(mod: *Module, name: []const u8) !std.StringHashMapUnmanaged pub fn analyzeExport( mod: *Module, - scope: *Scope, + block: *Scope.Block, src: LazySrcLoc, borrowed_options: std.builtin.ExportOptions, exported_decl: *Decl, @@ -4217,7 +4211,7 @@ pub fn analyzeExport( try mod.ensureDeclAnalyzed(exported_decl); switch (exported_decl.ty.zigTypeTag()) { .Fn => {}, - else => return mod.fail(scope, src, "unable to export type '{}'", .{exported_decl.ty}), + else => return mod.fail(&block.base, src, "unable to export type '{}'", .{exported_decl.ty}), } const gpa = mod.gpa; @@ -4234,7 +4228,8 @@ pub fn analyzeExport( const section: ?[]const u8 = if (borrowed_options.section) |s| try gpa.dupe(u8, s) else null; errdefer if (section) |s| gpa.free(s); - const owner_decl = scope.ownerDecl().?; + const src_decl = block.src_decl; + const owner_decl = block.sema.owner_decl; log.debug("exporting Decl '{s}' as symbol '{s}' from Decl '{s}'", .{ exported_decl.name, symbol_name, owner_decl.name, @@ -4257,6 +4252,7 @@ pub fn analyzeExport( .spirv => .{ .spirv = {} }, }, .owner_decl = owner_decl, + .src_decl = src_decl, .exported_decl = exported_decl, .status = .in_progress, }; @@ -4287,38 +4283,38 @@ pub fn createAnonymousDeclNamed( typed_value: TypedValue, name: [:0]u8, ) !*Decl { - return mod.createAnonymousDeclFromDeclNamed(scope.ownerDecl().?, scope.srcScope(), typed_value, name); + return mod.createAnonymousDeclFromDeclNamed(scope.srcDecl().?, scope.srcScope(), typed_value, name); } pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl { - return mod.createAnonymousDeclFromDecl(scope.ownerDecl().?, scope.srcScope(), typed_value); + return mod.createAnonymousDeclFromDecl(scope.srcDecl().?, scope.srcScope(), typed_value); } -pub fn createAnonymousDeclFromDecl(mod: *Module, owner_decl: *Decl, src_scope: ?*CaptureScope, tv: TypedValue) !*Decl { +pub fn createAnonymousDeclFromDecl(mod: *Module, src_decl: *Decl, src_scope: ?*CaptureScope, tv: TypedValue) !*Decl { const name_index = mod.getNextAnonNameIndex(); const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{ - owner_decl.name, name_index, + src_decl.name, name_index, }); - return mod.createAnonymousDeclFromDeclNamed(owner_decl, src_scope, tv, name); + return mod.createAnonymousDeclFromDeclNamed(src_decl, src_scope, tv, name); } /// Takes ownership of `name` even if it returns an error. pub fn createAnonymousDeclFromDeclNamed( mod: *Module, - owner_decl: *Decl, + src_decl: *Decl, src_scope: ?*CaptureScope, typed_value: TypedValue, name: [:0]u8, ) !*Decl { errdefer mod.gpa.free(name); - const namespace = owner_decl.namespace; + const namespace = src_decl.namespace; try namespace.anon_decls.ensureUnusedCapacity(mod.gpa, 1); - const new_decl = try mod.allocateNewDecl(namespace, owner_decl.src_node, src_scope); + const new_decl = try mod.allocateNewDecl(namespace, src_decl.src_node, src_scope); new_decl.name = name; - new_decl.src_line = owner_decl.src_line; + new_decl.src_line = src_decl.src_line; new_decl.ty = typed_value.ty; new_decl.val = typed_value.val; new_decl.align_val = Value.initTag(.null_value); diff --git a/src/Sema.zig b/src/Sema.zig index 2fa12baca6..87cde2ca1a 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2447,7 +2447,7 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro } const decl = try sema.lookupIdentifier(block, operand_src, decl_name); const options = try sema.resolveExportOptions(block, options_src, extra.options); - try sema.mod.analyzeExport(&block.base, src, options, decl); + try sema.mod.analyzeExport(block, src, options, decl); } fn zirExportValue(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { @@ -2465,7 +2465,7 @@ fn zirExportValue(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compil .function => operand.val.castTag(.function).?.data.owner_decl, else => return sema.mod.fail(&block.base, operand_src, "TODO implement exporting arbitrary Value objects", .{}), // TODO put this Value into an anonymous Decl and then export it. }; - try sema.mod.analyzeExport(&block.base, src, options, decl); + try sema.mod.analyzeExport(block, src, options, decl); } fn zirSetAlignStack(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { From a032fd01e88be2c6e8d0cfb0ccd3d9859c9dffdc Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Sat, 18 Sep 2021 23:26:55 +1000 Subject: [PATCH 114/160] Resolve scope IDs using IPv6 sockets On certain systems (Solaris), resolving the scope id from an interface name can only be done on AF_INET-domain sockets. While we're here, simplify the test while we're here, since there's only one address. Also note that the loopback interface name is not stable across OSs. BSDs and Solaris use `lo0` whilst Linux uses `l0`. --- lib/std/x/os/net.zig | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/lib/std/x/os/net.zig b/lib/std/x/os/net.zig index 5b06d492a5..a529396c3c 100644 --- a/lib/std/x/os/net.zig +++ b/lib/std/x/os/net.zig @@ -27,7 +27,7 @@ pub fn resolveScopeId(name: []const u8) !u32 { return rc; } - const fd = try os.socket(os.AF.UNIX, os.SOCK.DGRAM, 0); + const fd = try os.socket(os.AF.INET, os.SOCK.DGRAM, 0); defer os.closeSocket(fd); var f: os.ifreq = undefined; @@ -566,21 +566,17 @@ test "ipv6: parse & format" { test "ipv6: parse & format addresses with scope ids" { if (!have_ifnamesize) return error.SkipZigTest; + const iface = if (native_os.tag == .linux) + "lo" + else + "lo0"; + const input = "FF01::FB%" ++ iface; + const output = "ff01::fb%1"; - const inputs = [_][]const u8{ - "FF01::FB%lo", + const parsed = IPv6.parse(input) catch |err| switch (err) { + error.InterfaceNotFound => return, + else => return err, }; - const outputs = [_][]const u8{ - "ff01::fb%1", - }; - - for (inputs) |input, i| { - const parsed = IPv6.parse(input) catch |err| switch (err) { - error.InterfaceNotFound => continue, - else => return err, - }; - - try testing.expectFmt(outputs[i], "{}", .{parsed}); - } + try testing.expectFmt(output, "{}", .{parsed}); } From 87fd502fb68f8f488e6eba6b1f7d70902d6bfe5a Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Tue, 31 Aug 2021 22:21:23 +1000 Subject: [PATCH 115/160] Initial bringup of the Solaris/Illumos port --- lib/std/Thread.zig | 15 +- lib/std/c.zig | 27 + lib/std/c/solaris.zig | 1920 ++++++++++++++++++++++++++++++- lib/std/debug.zig | 10 +- lib/std/dynamic_library.zig | 2 +- lib/std/fs.zig | 65 +- lib/std/fs/file.zig | 50 +- lib/std/fs/get_app_data_dir.zig | 2 +- lib/std/fs/test.zig | 2 +- lib/std/os.zig | 19 + lib/std/os/test.zig | 27 +- lib/std/process.zig | 3 +- lib/std/target.zig | 12 +- lib/std/zig/system.zig | 23 + src/libc_installation.zig | 1 + src/link/Elf.zig | 9 + src/stage1/os.hpp | 2 + src/stage1/target.cpp | 5 +- src/target.zig | 7 + src/type.zig | 2 +- 20 files changed, 2148 insertions(+), 55 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 1fe8ca89d2..ae3a3e9d9e 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -41,6 +41,7 @@ pub const max_name_len = switch (target.os.tag) { .netbsd => 31, .freebsd => 15, .openbsd => 31, + .solaris => 31, else => 0, }; @@ -112,7 +113,7 @@ pub fn setName(self: Thread, name: []const u8) SetNameError!void { else => |e| return os.unexpectedErrno(e), } }, - .netbsd => if (use_pthreads) { + .netbsd, .solaris => if (use_pthreads) { const err = std.c.pthread_setname_np(self.getHandle(), name_with_terminator.ptr, null); switch (err) { .SUCCESS => return, @@ -202,7 +203,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co else => |e| return os.unexpectedErrno(e), } }, - .netbsd => if (use_pthreads) { + .netbsd, .solaris => if (use_pthreads) { const err = std.c.pthread_getname_np(self.getHandle(), buffer.ptr, max_name_len + 1); switch (err) { .SUCCESS => return std.mem.sliceTo(buffer, 0), @@ -565,6 +566,16 @@ const PosixThreadImpl = struct { }; return @intCast(usize, count); }, + .solaris => { + // The "proper" way to get the cpu count would be to query + // /dev/kstat via ioctls, and traverse a linked list for each + // cpu. + const rc = c.sysconf(os._SC.NPROCESSORS_ONLN); + return switch (os.errno(rc)) { + .SUCCESS => @intCast(usize, rc), + else => |err| os.unexpectedErrno(err), + }; + }, .haiku => { var count: u32 = undefined; var system_info: os.system_info = undefined; diff --git a/lib/std/c.zig b/lib/std/c.zig index 6a2624a93e..84fbb59640 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -252,6 +252,33 @@ pub extern "c" fn kevent( timeout: ?*const c.timespec, ) c_int; +pub extern "c" fn port_create() c.port_t; +pub extern "c" fn port_associate( + port: c.port_t, + source: u32, + object: usize, + events: u32, + user_var: ?*c_void, +) c_int; +pub extern "c" fn port_dissociate(port: c.port_t, source: u32, object: usize) c_int; +pub extern "c" fn port_send(port: c.port_t, events: u32, user_var: ?*c_void) c_int; +pub extern "c" fn port_sendn( + ports: [*]c.port_t, + errors: []u32, + num_ports: u32, + events: u32, + user_var: ?*c_void, +) c_int; +pub extern "c" fn port_get(port: c.port_t, event: *c.port_event, timeout: ?*c.timespec) c_int; +pub extern "c" fn port_getn( + port: c.port_t, + event_list: []c.port_event, + max_events: u32, + events_retrieved: *u32, + timeout: ?*c.timespec, +) c_int; +pub extern "c" fn port_alert(port: c.port_t, flags: u32, events: u32, user_var: ?*c_void) c_int; + pub extern "c" fn getaddrinfo( noalias node: ?[*:0]const u8, noalias service: ?[*:0]const u8, diff --git a/lib/std/c/solaris.zig b/lib/std/c/solaris.zig index 7c70a01fc4..283ea792cd 100644 --- a/lib/std/c/solaris.zig +++ b/lib/std/c/solaris.zig @@ -1,15 +1,1913 @@ +const std = @import("../std.zig"); +const builtin = @import("builtin"); +const maxInt = std.math.maxInt; +const iovec = std.os.iovec; +const iovec_const = std.os.iovec_const; +const timezone = std.c.timezone; + +extern "c" fn ___errno() *c_int; +pub const _errno = ___errno; + +pub const dl_iterate_phdr_callback = fn (info: *dl_phdr_info, size: usize, data: ?*c_void) callconv(.C) c_int; +pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int; + +pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize; +pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; +pub extern "c" fn pipe2(fds: *[2]fd_t, flags: u32) c_int; +pub extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void; +pub extern "c" fn posix_memalign(memptr: *?*c_void, alignment: usize, size: usize) c_int; +pub extern "c" fn sysconf(sc: c_int) i64; +pub extern "c" fn signalfd(fd: fd_t, mask: *const sigset_t, flags: u32) c_int; +pub extern "c" fn madvise(address: [*]u8, len: usize, advise: u32) c_int; + pub const pthread_mutex_t = extern struct { - __pthread_mutex_flag1: u16 = 0, - __pthread_mutex_flag2: u8 = 0, - __pthread_mutex_ceiling: u8 = 0, - __pthread_mutex_type: u16 = 0, - __pthread_mutex_magic: u16 = 0x4d58, - __pthread_mutex_lock: u64 = 0, - __pthread_mutex_data: u64 = 0, + flag1: u16 = 0, + flag2: u8 = 0, + ceiling: u8 = 0, + @"type": u16 = 0, + magic: u16 = 0x4d58, + lock: u64 = 0, + data: u64 = 0, }; pub const pthread_cond_t = extern struct { - __pthread_cond_flag: u32 = 0, - __pthread_cond_type: u16 = 0, - __pthread_cond_magic: u16 = 0x4356, - __pthread_cond_data: u64 = 0, + flag: [4]u8 = [_]u8{0} ** 4, + @"type": u16 = 0, + magic: u16 = 0x4356, + data: u64 = 0, }; +pub const pthread_rwlock_t = extern struct { + readers: i32 = 0, + @"type": u16 = 0, + magic: u16 = 0x5257, + mutex: pthread_mutex_t = .{}, + readercv: pthread_cond_t = .{}, + writercv: pthread_cond_t = .{}, +}; +pub const pthread_attr_t = extern struct { + mutexattr: ?*c_void = null, +}; +pub const pthread_key_t = c_int; + +pub const sem_t = extern struct { + count: u32 = 0, + @"type": u16 = 0, + magic: u16 = 0x534d, + __pad1: [3]u64 = [_]u64{0} ** 3, + __pad2: [2]u64 = [_]u64{0} ** 2, +}; + +pub extern "c" fn pthread_setname_np(thread: std.c.pthread_t, name: [*:0]const u8, arg: ?*c_void) E; +pub extern "c" fn pthread_getname_np(thread: std.c.pthread_t, name: [*:0]u8, len: usize) E; + +pub const blkcnt_t = i64; +pub const blksize_t = i32; +pub const clock_t = i64; +pub const dev_t = i32; +pub const fd_t = c_int; +pub const gid_t = u32; +pub const ino_t = u64; +pub const mode_t = u32; +pub const nlink_t = u32; +pub const off_t = i64; +pub const pid_t = i32; +pub const socklen_t = u32; +pub const time_t = i64; +pub const suseconds_t = i64; +pub const uid_t = u32; +pub const major_t = u32; +pub const minor_t = u32; +pub const port_t = c_int; +pub const nfds_t = usize; +pub const id_t = i32; +pub const taskid_t = id_t; +pub const projid_t = id_t; +pub const poolid_t = id_t; +pub const zoneid_t = id_t; +pub const ctid_t = id_t; + +pub const dl_phdr_info = extern struct { + dlpi_addr: std.elf.Addr, + dlpi_name: ?[*:0]const u8, + dlpi_phdr: [*]std.elf.Phdr, + dlpi_phnum: std.elf.Half, + /// Incremented when a new object is mapped into the process. + dlpi_adds: u64, + /// Incremented when an object is unmapped from the process. + dlpi_subs: u64, +}; + +pub const RTLD = struct { + pub const LAZY = 0x00001; + pub const NOW = 0x00002; + pub const NOLOAD = 0x00004; + pub const GLOBAL = 0x00100; + pub const LOCAL = 0x00000; + pub const PARENT = 0x00200; + pub const GROUP = 0x00400; + pub const WORLD = 0x00800; + pub const NODELETE = 0x01000; + pub const FIRST = 0x02000; + pub const CONFGEN = 0x10000; + + pub const NEXT = @intToPtr(*c_void, @bitCast(usize, @as(isize, -1))); + pub const DEFAULT = @intToPtr(*c_void, @bitCast(usize, @as(isize, -2))); + pub const SELF = @intToPtr(*c_void, @bitCast(usize, @as(isize, -3))); + pub const PROBE = @intToPtr(*c_void, @bitCast(usize, @as(isize, -4))); +}; + +pub const Flock = extern struct { + l_type: c_short, + l_whence: c_short, + l_start: off_t, + // len == 0 means until end of file. + l_len: off_t, + l_sysid: c_int, + l_pid: pid_t, + __pad: [4]c_long, +}; + +pub const utsname = extern struct { + sysname: [256:0]u8, + nodename: [256:0]u8, + release: [256:0]u8, + version: [256:0]u8, + machine: [256:0]u8, + domainname: [256:0]u8, +}; + +pub const addrinfo = extern struct { + flags: i32, + family: i32, + socktype: i32, + protocol: i32, + addrlen: socklen_t, + canonname: ?[*:0]u8, + addr: ?*sockaddr, + next: ?*addrinfo, +}; + +pub const EAI = enum(c_int) { + /// address family for hostname not supported + ADDRFAMILY = 1, + /// name could not be resolved at this time + AGAIN = 2, + /// flags parameter had an invalid value + BADFLAGS = 3, + /// non-recoverable failure in name resolution + FAIL = 4, + /// address family not recognized + FAMILY = 5, + /// memory allocation failure + MEMORY = 6, + /// no address associated with hostname + NODATA = 7, + /// name does not resolve + NONAME = 8, + /// service not recognized for socket type + SERVICE = 9, + /// intended socket type was not recognized + SOCKTYPE = 10, + /// system error returned in errno + SYSTEM = 11, + /// argument buffer overflow + OVERFLOW = 12, + /// resolved protocol is unknown + PROTOCOL = 13, + + _, +}; + +pub const EAI_MAX = 14; + +pub const msghdr = extern struct { + /// optional address + msg_name: ?*sockaddr, + /// size of address + msg_namelen: socklen_t, + /// scatter/gather array + msg_iov: [*]iovec, + /// # elements in msg_iov + msg_iovlen: i32, + /// ancillary data + msg_control: ?*c_void, + /// ancillary data buffer len + msg_controllen: socklen_t, + /// flags on received message + msg_flags: i32, +}; + +pub const msghdr_const = extern struct { + /// optional address + msg_name: ?*const sockaddr, + /// size of address + msg_namelen: socklen_t, + /// scatter/gather array + msg_iov: [*]iovec_const, + /// # elements in msg_iov + msg_iovlen: i32, + /// ancillary data + msg_control: ?*c_void, + /// ancillary data buffer len + msg_controllen: socklen_t, + /// flags on received message + msg_flags: i32, +}; + +pub const cmsghdr = extern struct { + cmsg_len: socklen_t, + cmsg_level: i32, + cmsg_type: i32, +}; + +/// The stat structure used by libc. +pub const Stat = extern struct { + dev: dev_t, + ino: ino_t, + mode: mode_t, + nlink: nlink_t, + uid: uid_t, + gid: gid_t, + rdev: dev_t, + size: off_t, + atim: timespec, + mtim: timespec, + ctim: timespec, + blksize: blksize_t, + blocks: blkcnt_t, + fstype: [16]u8, + + pub fn atime(self: @This()) timespec { + return self.atim; + } + + pub fn mtime(self: @This()) timespec { + return self.mtim; + } + + pub fn ctime(self: @This()) timespec { + return self.ctim; + } +}; + +pub const timespec = extern struct { + tv_sec: i64, + tv_nsec: isize, +}; + +pub const timeval = extern struct { + /// seconds + tv_sec: time_t, + /// microseconds + tv_usec: suseconds_t, +}; + +pub const MAXNAMLEN = 511; + +pub const dirent = extern struct { + /// Inode number of entry. + d_ino: ino_t, + /// Offset of this entry on disk. + d_off: off_t, + /// Length of this record. + d_reclen: u16, + /// File name. + d_name: [MAXNAMLEN:0]u8, + + pub fn reclen(self: dirent) u16 { + return self.d_reclen; + } +}; + +pub const SOCK = struct { + /// Datagram. + pub const DGRAM = 1; + /// STREAM. + pub const STREAM = 2; + /// Raw-protocol interface. + pub const RAW = 4; + /// Reliably-delivered message. + pub const RDM = 5; + /// Sequenced packed stream. + pub const SEQPACKET = 6; + + pub const NONBLOCK = 0x100000; + pub const NDELAY = 0x200000; + pub const CLOEXEC = 0x080000; +}; + +pub const SO = struct { + pub const DEBUG = 0x0001; + pub const ACCEPTCONN = 0x0002; + pub const REUSEADDR = 0x0004; + pub const KEEPALIVE = 0x0008; + pub const DONTROUTE = 0x0010; + pub const BROADCAST = 0x0020; + pub const USELOOPBACK = 0x0040; + pub const LINGER = 0x0080; + pub const OOBINLINE = 0x0100; + pub const DGRAM_ERRIND = 0x0200; + pub const RECVUCRED = 0x0400; + + pub const SNDBUF = 0x1001; + pub const RCVBUF = 0x1002; + pub const SNDLOWAT = 0x1003; + pub const RCVLOWAT = 0x1004; + pub const SNDTIMEO = 0x1005; + pub const RCVTIMEO = 0x1006; + pub const ERROR = 0x1007; + pub const TYPE = 0x1008; + pub const PROTOTYPE = 0x1009; + pub const ANON_MLP = 0x100a; + pub const MAC_EXEMPT = 0x100b; + pub const DOMAIN = 0x100c; + pub const RCVPSH = 0x100d; + + pub const SECATTR = 0x1011; + pub const TIMESTAMP = 0x1013; + pub const ALLZONES = 0x1014; + pub const EXCLBIND = 0x1015; + pub const MAC_IMPLICIT = 0x1016; + pub const VRRP = 0x1017; +}; + +pub const SOMAXCONN = 128; + +pub const SCM = struct { + pub const UCRED = 0x1012; + pub const RIGHTS = 0x1010; + pub const TIMESTAMP = SO.TIMESTAMP; +}; + +pub const AF = struct { + pub const UNSPEC = 0; + pub const UNIX = 1; + pub const LOCAL = UNIX; + pub const FILE = UNIX; + pub const INET = 2; + pub const IMPLINK = 3; + pub const PUP = 4; + pub const CHAOS = 5; + pub const NS = 6; + pub const NBS = 7; + pub const ECMA = 8; + pub const DATAKIT = 9; + pub const CCITT = 10; + pub const SNA = 11; + pub const DECnet = 12; + pub const DLI = 13; + pub const LAT = 14; + pub const HYLINK = 15; + pub const APPLETALK = 16; + pub const NIT = 17; + pub const @"802" = 18; + pub const OSI = 19; + pub const X25 = 20; + pub const OSINET = 21; + pub const GOSIP = 22; + pub const IPX = 23; + pub const ROUTE = 24; + pub const LINK = 25; + pub const INET6 = 26; + pub const KEY = 27; + pub const NCA = 28; + pub const POLICY = 29; + pub const INET_OFFLOAD = 30; + pub const TRILL = 31; + pub const PACKET = 32; + pub const LX_NETLINK = 33; + pub const MAX = 33; +}; + +pub const SOL = struct { + pub const SOCKET = 0xffff; + pub const ROUTE = 0xfffe; + pub const PACKET = 0xfffd; + pub const FILTER = 0xfffc; +}; + +pub const PF = struct { + pub const UNSPEC = AF.UNSPEC; + pub const UNIX = AF.UNIX; + pub const LOCAL = UNIX; + pub const FILE = UNIX; + pub const INET = AF.INET; + pub const IMPLINK = AF.IMPLINK; + pub const PUP = AF.PUP; + pub const CHAOS = AF.CHAOS; + pub const NS = AF.NS; + pub const NBS = AF.NBS; + pub const ECMA = AF.ECMA; + pub const DATAKIT = AF.DATAKIT; + pub const CCITT = AF.CCITT; + pub const SNA = AF.SNA; + pub const DECnet = AF.DECnet; + pub const DLI = AF.DLI; + pub const LAT = AF.LAT; + pub const HYLINK = AF.HYLINK; + pub const APPLETALK = AF.APPLETALK; + pub const NIT = AF.NIT; + pub const @"802" = AF.@"802"; + pub const OSI = AF.OSI; + pub const X25 = AF.X25; + pub const OSINET = AF.OSINET; + pub const GOSIP = AF.GOSIP; + pub const IPX = AF.IPX; + pub const ROUTE = AF.ROUTE; + pub const LINK = AF.LINK; + pub const INET6 = AF.INET6; + pub const KEY = AF.KEY; + pub const NCA = AF.NCA; + pub const POLICY = AF.POLICY; + pub const TRILL = AF.TRILL; + pub const PACKET = AF.PACKET; + pub const LX_NETLINK = AF.LX_NETLINK; + pub const MAX = AF.MAX; +}; + +pub const in_port_t = u16; +pub const sa_family_t = u16; + +pub const sockaddr = extern struct { + /// address family + family: sa_family_t, + + /// actually longer; address value + data: [14]u8, + + pub const SS_MAXSIZE = 256; + pub const storage = std.x.os.Socket.Address.Native.Storage; + + pub const in = extern struct { + family: sa_family_t = AF.INET, + port: in_port_t, + addr: u32, + zero: [8]u8 = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, + }; + + pub const in6 = extern struct { + family: sa_family_t = AF.INET6, + port: in_port_t, + flowinfo: u32, + addr: [16]u8, + scope_id: u32, + __src_id: u32 = 0, + }; + + /// Definitions for UNIX IPC domain. + pub const un = extern struct { + family: sa_family_t = AF.UNIX, + path: [108]u8, + }; +}; + +pub const AI = struct { + /// IPv4-mapped IPv6 address + pub const V4MAPPED = 0x0001; + pub const ALL = 0x0002; + /// only if any address is assigned + pub const ADDRCONFIG = 0x0004; + /// get address to use bind() + pub const PASSIVE = 0x0008; + /// fill ai_canonname + pub const CANONNAME = 0x0010; + /// prevent host name resolution + pub const NUMERICHOST = 0x0020; + /// prevent service name resolution + pub const NUMERICSERV = 0x0040; +}; + +pub const NI = struct { + pub const NOFQDN = 0x0001; + pub const NUMERICHOST = 0x0002; + pub const NAMEREQD = 0x0004; + pub const NUMERICSERV = 0x0008; + pub const DGRAM = 0x0010; + pub const WITHSCOPEID = 0x0020; + pub const NUMERICSCOPE = 0x0040; + + pub const MAXHOST = 1025; + pub const MAXSERV = 32; +}; + +pub const PATH_MAX = 1024; +pub const IOV_MAX = 1024; + +pub const STDIN_FILENO = 0; +pub const STDOUT_FILENO = 1; +pub const STDERR_FILENO = 2; + +pub const PROT = struct { + pub const NONE = 0; + pub const READ = 1; + pub const WRITE = 2; + pub const EXEC = 4; +}; + +pub const CLOCK = struct { + pub const VIRTUAL = 1; + pub const THREAD_CPUTIME_ID = 2; + pub const REALTIME = 3; + pub const MONOTONIC = 4; + pub const PROCESS_CPUTIME_ID = 5; + pub const HIGHRES = MONOTONIC; + pub const PROF = THREAD_CPUTIME_ID; +}; + +pub const MAP = struct { + pub const FAILED = @intToPtr(*c_void, maxInt(usize)); + pub const SHARED = 0x0001; + pub const PRIVATE = 0x0002; + pub const TYPE = 0x000f; + + pub const FILE = 0x0000; + pub const FIXED = 0x0010; + // Unimplemented + pub const RENAME = 0x0020; + pub const NORESERVE = 0x0040; + /// Force mapping in lower 4G address space + pub const @"32BIT" = 0x0080; + + pub const ANON = 0x0100; + pub const ANONYMOUS = ANON; + pub const ALIGN = 0x0200; + pub const TEXT = 0x0400; + pub const INITDATA = 0x0800; +}; + +pub const MADV = struct { + /// no further special treatment + pub const NORMAL = 0; + /// expect random page references + pub const RANDOM = 1; + /// expect sequential page references + pub const SEQUENTIAL = 2; + /// will need these pages + pub const WILLNEED = 3; + /// don't need these pages + pub const DONTNEED = 4; + /// contents can be freed + pub const FREE = 5; + /// default access + pub const ACCESS_DEFAULT = 6; + /// next LWP to access heavily + pub const ACCESS_LWP = 7; + /// many processes to access heavily + pub const ACCESS_MANY = 8; + /// contents will be purged + pub const PURGE = 9; +}; + +pub const W = struct { + pub const EXITED = 0o001; + pub const TRAPPED = 0o002; + pub const UNTRACED = 0o004; + pub const STOPPED = UNTRACED; + pub const CONTINUED = 0o010; + pub const NOHANG = 0o100; + pub const NOWAIT = 0o200; + + pub fn EXITSTATUS(s: u32) u8 { + return @intCast(u8, (s >> 8) & 0xff); + } + pub fn TERMSIG(s: u32) u32 { + return s & 0x7f; + } + pub fn STOPSIG(s: u32) u32 { + return EXITSTATUS(s); + } + pub fn IFEXITED(s: u32) bool { + return TERMSIG(s) == 0; + } + + pub fn IFCONTINUED(s: u32) bool { + return ((s & 0o177777) == 0o177777); + } + + pub fn IFSTOPPED(s: u32) bool { + return (s & 0x00ff != 0o177) and !(s & 0xff00 != 0); + } + + pub fn IFSIGNALED(s: u32) bool { + return s & 0x00ff > 0 and s & 0xff00 == 0; + } +}; + +pub const SA = struct { + pub const ONSTACK = 0x00000001; + pub const RESETHAND = 0x00000002; + pub const RESTART = 0x00000004; + pub const SIGINFO = 0x00000008; + pub const NODEFER = 0x00000010; + pub const NOCLDWAIT = 0x00010000; +}; + +// access function +pub const F_OK = 0; // test for existence of file +pub const X_OK = 1; // test for execute or search permission +pub const W_OK = 2; // test for write permission +pub const R_OK = 4; // test for read permission + +pub const F = struct { + /// Unlock a previously locked region + pub const ULOCK = 0; + /// Lock a region for exclusive use + pub const LOCK = 1; + /// Test and lock a region for exclusive use + pub const TLOCK = 2; + /// Test a region for other processes locks + pub const TEST = 3; + + /// Duplicate fildes + pub const DUPFD = 0; + /// Get fildes flags + pub const GETFD = 1; + /// Set fildes flags + pub const SETFD = 2; + /// Get file flags + pub const GETFL = 3; + /// Get file flags including open-only flags + pub const GETXFL = 45; + /// Set file flags + pub const SETFL = 4; + + /// Unused + pub const CHKFL = 8; + /// Duplicate fildes at third arg + pub const DUP2FD = 9; + /// Like DUP2FD with O_CLOEXEC set EINVAL is fildes matches arg1 + pub const DUP2FD_CLOEXEC = 36; + /// Like DUPFD with O_CLOEXEC set + pub const DUPFD_CLOEXEC = 37; + + /// Is the file desc. a stream ? + pub const ISSTREAM = 13; + /// Turn on private access to file + pub const PRIV = 15; + /// Turn off private access to file + pub const NPRIV = 16; + /// UFS quota call + pub const QUOTACTL = 17; + /// Get number of BLKSIZE blocks allocated + pub const BLOCKS = 18; + /// Get optimal I/O block size + pub const BLKSIZE = 19; + /// Get owner (socket emulation) + pub const GETOWN = 23; + /// Set owner (socket emulation) + pub const SETOWN = 24; + /// Object reuse revoke access to file desc. + pub const REVOKE = 25; + /// Does vp have NFS locks private to lock manager + pub const HASREMOTELOCKS = 26; + + /// Set file lock + pub const SETLK = 6; + /// Set file lock and wait + pub const SETLKW = 7; + /// Allocate file space + pub const ALLOCSP = 10; + /// Free file space + pub const FREESP = 11; + /// Get file lock + pub const GETLK = 14; + /// Get file lock owned by file + pub const OFD_GETLK = 47; + /// Set file lock owned by file + pub const OFD_SETLK = 48; + /// Set file lock owned by file and wait + pub const OFD_SETLKW = 49; + /// Set a file share reservation + pub const SHARE = 40; + /// Remove a file share reservation + pub const UNSHARE = 41; + /// Create Poison FD + pub const BADFD = 46; + + /// Read lock + pub const RDLCK = 1; + /// Write lock + pub const WRLCK = 2; + /// Remove lock(s) + pub const UNLCK = 3; + /// remove remote locks for a given system + pub const UNLKSYS = 4; + + // f_access values + /// Read-only share access + pub const RDACC = 0x1; + /// Write-only share access + pub const WRACC = 0x2; + /// Read-Write share access + pub const RWACC = 0x3; + + // f_deny values + /// Don't deny others access + pub const NODNY = 0x0; + /// Deny others read share access + pub const RDDNY = 0x1; + /// Deny others write share access + pub const WRDNY = 0x2; + /// Deny others read or write share access + pub const RWDNY = 0x3; + /// private flag: Deny delete share access + pub const RMDNY = 0x4; +}; + +pub const O = struct { + pub const RDONLY = 0; + pub const WRONLY = 1; + pub const RDWR = 2; + pub const SEARCH = 0x200000; + pub const EXEC = 0x400000; + pub const NDELAY = 0x04; + pub const APPEND = 0x08; + pub const SYNC = 0x10; + pub const DSYNC = 0x40; + pub const RSYNC = 0x8000; + pub const NONBLOCK = 0x80; + pub const LARGEFILE = 0x2000; + + pub const CREAT = 0x100; + pub const TRUNC = 0x200; + pub const EXCL = 0x400; + pub const NOCTTY = 0x800; + pub const XATTR = 0x4000; + pub const NOFOLLOW = 0x20000; + pub const NOLINKS = 0x40000; + pub const CLOEXEC = 0x800000; + pub const DIRECTORY = 0x1000000; + pub const DIRECT = 0x2000000; +}; + +pub const LOCK = struct { + pub const SH = 1; + pub const EX = 2; + pub const NB = 4; + pub const UN = 8; +}; + +pub const FD_CLOEXEC = 1; + +pub const SEEK = struct { + pub const SET = 0; + pub const CUR = 1; + pub const END = 2; + pub const DATA = 3; + pub const HOLE = 4; +}; + +pub const tcflag_t = c_uint; +pub const cc_t = u8; +pub const speed_t = c_uint; + +pub const NCCS = 19; + +pub const termios = extern struct { + c_iflag: tcflag_t, + c_oflag: tcflag_t, + c_cflag: tcflag_t, + c_lflag: tcflag_t, + c_cc: [NCCS]cc_t, +}; + +fn tioc(t: u16, num: u8) u16 { + return (t << 8) | num; +} + +pub const T = struct { + pub const CGETA = tioc('T', 1); + pub const CSETA = tioc('T', 2); + pub const CSETAW = tioc('T', 3); + pub const CSETAF = tioc('T', 4); + pub const CSBRK = tioc('T', 5); + pub const CXONC = tioc('T', 6); + pub const CFLSH = tioc('T', 7); + pub const IOCGWINSZ = tioc('T', 104); + pub const IOCSWINSZ = tioc('T', 103); + // Softcarrier ioctls + pub const IOCGSOFTCAR = tioc('T', 105); + pub const IOCSSOFTCAR = tioc('T', 106); + // termios ioctls + pub const CGETS = tioc('T', 13); + pub const CSETS = tioc('T', 14); + pub const CSANOW = tioc('T', 14); + pub const CSETSW = tioc('T', 15); + pub const CSADRAIN = tioc('T', 15); + pub const CSETSF = tioc('T', 16); + pub const IOCSETLD = tioc('T', 123); + pub const IOCGETLD = tioc('T', 124); + // NTP PPS ioctls + pub const IOCGPPS = tioc('T', 125); + pub const IOCSPPS = tioc('T', 126); + pub const IOCGPPSEV = tioc('T', 127); + + pub const IOCGETD = tioc('t', 0); + pub const IOCSETD = tioc('t', 1); + pub const IOCHPCL = tioc('t', 2); + pub const IOCGETP = tioc('t', 8); + pub const IOCSETP = tioc('t', 9); + pub const IOCSETN = tioc('t', 10); + pub const IOCEXCL = tioc('t', 13); + pub const IOCNXCL = tioc('t', 14); + pub const IOCFLUSH = tioc('t', 16); + pub const IOCSETC = tioc('t', 17); + pub const IOCGETC = tioc('t', 18); + /// bis local mode bits + pub const IOCLBIS = tioc('t', 127); + /// bic local mode bits + pub const IOCLBIC = tioc('t', 126); + /// set entire local mode word + pub const IOCLSET = tioc('t', 125); + /// get local modes + pub const IOCLGET = tioc('t', 124); + /// set break bit + pub const IOCSBRK = tioc('t', 123); + /// clear break bit + pub const IOCCBRK = tioc('t', 122); + /// set data terminal ready + pub const IOCSDTR = tioc('t', 121); + /// clear data terminal ready + pub const IOCCDTR = tioc('t', 120); + /// set local special chars + pub const IOCSLTC = tioc('t', 117); + /// get local special chars + pub const IOCGLTC = tioc('t', 116); + /// driver output queue size + pub const IOCOUTQ = tioc('t', 115); + /// void tty association + pub const IOCNOTTY = tioc('t', 113); + /// get a ctty + pub const IOCSCTTY = tioc('t', 132); + /// stop output, like ^S + pub const IOCSTOP = tioc('t', 111); + /// start output, like ^Q + pub const IOCSTART = tioc('t', 110); + /// get pgrp of tty + pub const IOCGPGRP = tioc('t', 20); + /// set pgrp of tty + pub const IOCSPGRP = tioc('t', 21); + /// get session id on ctty + pub const IOCGSID = tioc('t', 22); + /// simulate terminal input + pub const IOCSTI = tioc('t', 23); + /// set all modem bits + pub const IOCMSET = tioc('t', 26); + /// bis modem bits + pub const IOCMBIS = tioc('t', 27); + /// bic modem bits + pub const IOCMBIC = tioc('t', 28); + /// get all modem bits + pub const IOCMGET = tioc('t', 29); +}; + +pub const winsize = extern struct { + ws_row: u16, + ws_col: u16, + ws_xpixel: u16, + ws_ypixel: u16, +}; + +const NSIG = 75; + +pub const SIG = struct { + pub const DFL = @intToPtr(?Sigaction.sigaction_fn, 0); + pub const ERR = @intToPtr(?Sigaction.sigaction_fn, maxInt(usize)); + pub const IGN = @intToPtr(?Sigaction.sigaction_fn, 1); + pub const HOLD = @intToPtr(?Sigaction.sigaction_fn, 2); + + pub const WORDS = 4; + pub const MAXSIG = 75; + + pub const SIG_BLOCK = 1; + pub const SIG_UNBLOCK = 2; + pub const SIG_SETMASK = 3; + + pub const HUP = 1; + pub const INT = 2; + pub const QUIT = 3; + pub const ILL = 4; + pub const TRAP = 5; + pub const IOT = 6; + pub const ABRT = 6; + pub const EMT = 7; + pub const FPE = 8; + pub const KILL = 9; + pub const BUS = 10; + pub const SEGV = 11; + pub const SYS = 12; + pub const PIPE = 13; + pub const ALRM = 14; + pub const TERM = 15; + pub const USR1 = 16; + pub const USR2 = 17; + pub const CLD = 18; + pub const CHLD = 18; + pub const PWR = 19; + pub const WINCH = 20; + pub const URG = 21; + pub const POLL = 22; + pub const IO = .POLL; + pub const STOP = 23; + pub const TSTP = 24; + pub const CONT = 25; + pub const TTIN = 26; + pub const TTOU = 27; + pub const VTALRM = 28; + pub const PROF = 29; + pub const XCPU = 30; + pub const XFSZ = 31; + pub const WAITING = 32; + pub const LWP = 33; + pub const FREEZE = 34; + pub const THAW = 35; + pub const CANCEL = 36; + pub const LOST = 37; + pub const XRES = 38; + pub const JVM1 = 39; + pub const JVM2 = 40; + pub const INFO = 41; + + pub const RTMIN = 42; + pub const RTMAX = 74; + + pub inline fn IDX(sig: usize) usize { + return sig - 1; + } + pub inline fn WORD(sig: usize) usize { + return IDX(sig) >> 5; + } + pub inline fn BIT(sig: usize) usize { + return 1 << (IDX(sig) & 31); + } + pub inline fn VALID(sig: usize) usize { + return sig <= MAXSIG and sig > 0; + } +}; + +/// Renamed from `sigaction` to `Sigaction` to avoid conflict with the syscall. +pub const Sigaction = extern struct { + pub const handler_fn = fn (c_int) callconv(.C) void; + pub const sigaction_fn = fn (c_int, *const siginfo_t, ?*const c_void) callconv(.C) void; + + /// signal options + flags: c_uint, + /// signal handler + handler: extern union { + handler: ?handler_fn, + sigaction: ?sigaction_fn, + }, + /// signal mask to apply + mask: sigset_t, +}; + +pub const sigval_t = extern union { + int: c_int, + ptr: ?*c_void, +}; + +pub const siginfo_t = extern struct { + signo: c_int, + code: c_int, + errno: c_int, + // 64bit architectures insert 4bytes of padding here, this is done by + // correctly aligning the reason field + reason: extern union { + proc: extern struct { + pid: pid_t, + pdata: extern union { + kill: extern struct { + uid: uid_t, + value: sigval_t, + }, + cld: extern struct { + utime: clock_t, + status: c_int, + stime: clock_t, + }, + }, + contract: ctid_t, + zone: zoneid_t, + }, + fault: extern struct { + addr: ?*c_void, + trapno: c_int, + pc: ?*c_void, + }, + file: extern struct { + // fd not currently available for SIGPOLL. + fd: c_int, + band: c_long, + }, + prof: extern struct { + addr: ?*c_void, + timestamp: timespec, + syscall: c_short, + sysarg: u8, + fault: u8, + args: [8]c_long, + state: [10]c_int, + }, + rctl: extern struct { + entity: i32, + }, + __pad: [256 - 4 * @sizeOf(c_int)]u8, + } align(@sizeOf(usize)), +}; + +comptime { + std.debug.assert(@sizeOf(siginfo_t) == 256); + std.debug.assert(@alignOf(siginfo_t) == @sizeOf(usize)); +} + +pub const sigset_t = extern struct { + __bits: [SIG.WORDS]u32, +}; + +pub const empty_sigset = sigset_t{ .__bits = [_]u32{0} ** SIG.WORDS }; + +pub const fpregset_t = extern union { + regs: [130]u32, + chip_state: extern struct { + cw: u16, + sw: u16, + fctw: u8, + __fx_rsvd: u8, + fop: u16, + rip: u64, + rdp: u64, + mxcsr: u32, + mxcsr_mask: u32, + st: [8]extern union { + fpr_16: [5]u16, + __fpr_pad: u128, + }, + xmm: [16]u128, + __fx_ign2: [6]u128, + status: u32, + xstatus: u32, + }, +}; + +pub const mcontext_t = extern struct { + gregs: [28]u64, + fpregs: fpregset_t, +}; + +pub const REG = struct { + pub const RBP = 10; + pub const RIP = 17; + pub const RSP = 20; +}; + +pub const ucontext_t = extern struct { + flags: u64, + link: ?*ucontext_t, + sigmask: sigset_t, + stack: stack_t, + mcontext: mcontext_t, + brand_data: [3]?*c_void, + filler: [2]i64, +}; + +pub const GETCONTEXT = 0; +pub const SETCONTEXT = 1; +pub const GETUSTACK = 2; +pub const SETUSTACK = 3; + +pub const E = enum(u16) { + /// No error occurred. + SUCCESS = 0, + /// Not super-user + PERM = 1, + /// No such file or directory + NOENT = 2, + /// No such process + SRCH = 3, + /// interrupted system call + INTR = 4, + /// I/O error + IO = 5, + /// No such device or address + NXIO = 6, + /// Arg list too long + @"2BIG" = 7, + /// Exec format error + NOEXEC = 8, + /// Bad file number + BADF = 9, + /// No children + CHILD = 10, + /// Resource temporarily unavailable. + /// also: WOULDBLOCK: Operation would block. + AGAIN = 11, + /// Not enough core + NOMEM = 12, + /// Permission denied + ACCES = 13, + /// Bad address + FAULT = 14, + /// Block device required + NOTBLK = 15, + /// Mount device busy + BUSY = 16, + /// File exists + EXIST = 17, + /// Cross-device link + XDEV = 18, + /// No such device + NODEV = 19, + /// Not a directory + NOTDIR = 20, + /// Is a directory + ISDIR = 21, + /// Invalid argument + INVAL = 22, + /// File table overflow + NFILE = 23, + /// Too many open files + MFILE = 24, + /// Inappropriate ioctl for device + NOTTY = 25, + /// Text file busy + TXTBSY = 26, + /// File too large + FBIG = 27, + /// No space left on device + NOSPC = 28, + /// Illegal seek + SPIPE = 29, + /// Read only file system + ROFS = 30, + /// Too many links + MLINK = 31, + /// Broken pipe + PIPE = 32, + /// Math arg out of domain of func + DOM = 33, + /// Math result not representable + RANGE = 34, + /// No message of desired type + NOMSG = 35, + /// Identifier removed + IDRM = 36, + /// Channel number out of range + CHRNG = 37, + /// Level 2 not synchronized + L2NSYNC = 38, + /// Level 3 halted + L3HLT = 39, + /// Level 3 reset + L3RST = 40, + /// Link number out of range + LNRNG = 41, + /// Protocol driver not attached + UNATCH = 42, + /// No CSI structure available + NOCSI = 43, + /// Level 2 halted + L2HLT = 44, + /// Deadlock condition. + DEADLK = 45, + /// No record locks available. + NOLCK = 46, + /// Operation canceled + CANCELED = 47, + /// Operation not supported + NOTSUP = 48, + + // Filesystem Quotas + /// Disc quota exceeded + DQUOT = 49, + + // Convergent Error Returns + /// invalid exchange + BADE = 50, + /// invalid request descriptor + BADR = 51, + /// exchange full + XFULL = 52, + /// no anode + NOANO = 53, + /// invalid request code + BADRQC = 54, + /// invalid slot + BADSLT = 55, + /// file locking deadlock error + DEADLOCK = 56, + /// bad font file fmt + BFONT = 57, + + // Interprocess Robust Locks + /// process died with the lock + OWNERDEAD = 58, + /// lock is not recoverable + NOTRECOVERABLE = 59, + /// locked lock was unmapped + LOCKUNMAPPED = 72, + /// Facility is not active + NOTACTIVE = 73, + /// multihop attempted + MULTIHOP = 74, + /// trying to read unreadable message + BADMSG = 77, + /// path name is too long + NAMETOOLONG = 78, + /// value too large to be stored in data type + OVERFLOW = 79, + /// given log. name not unique + NOTUNIQ = 80, + /// f.d. invalid for this operation + BADFD = 81, + /// Remote address changed + REMCHG = 82, + + // Stream Problems + /// Device not a stream + NOSTR = 60, + /// no data (for no delay io) + NODATA = 61, + /// timer expired + TIME = 62, + /// out of streams resources + NOSR = 63, + /// Machine is not on the network + NONET = 64, + /// Package not installed + NOPKG = 65, + /// The object is remote + REMOTE = 66, + /// the link has been severed + NOLINK = 67, + /// advertise error + ADV = 68, + /// srmount error + SRMNT = 69, + /// Communication error on send + COMM = 70, + /// Protocol error + PROTO = 71, + + // Shared Library Problems + /// Can't access a needed shared lib. + LIBACC = 83, + /// Accessing a corrupted shared lib. + LIBBAD = 84, + /// .lib section in a.out corrupted. + LIBSCN = 85, + /// Attempting to link in too many libs. + LIBMAX = 86, + /// Attempting to exec a shared library. + LIBEXEC = 87, + /// Illegal byte sequence. + ILSEQ = 88, + /// Unsupported file system operation + NOSYS = 89, + /// Symbolic link loop + LOOP = 90, + /// Restartable system call + RESTART = 91, + /// if pipe/FIFO, don't sleep in stream head + STRPIPE = 92, + /// directory not empty + NOTEMPTY = 93, + /// Too many users (for UFS) + USERS = 94, + + // BSD Networking Software + // Argument Errors + /// Socket operation on non-socket + NOTSOCK = 95, + /// Destination address required + DESTADDRREQ = 96, + /// Message too long + MSGSIZE = 97, + /// Protocol wrong type for socket + PROTOTYPE = 98, + /// Protocol not available + NOPROTOOPT = 99, + /// Protocol not supported + PROTONOSUPPORT = 120, + /// Socket type not supported + SOCKTNOSUPPORT = 121, + /// Operation not supported on socket + OPNOTSUPP = 122, + /// Protocol family not supported + PFNOSUPPORT = 123, + /// Address family not supported by + AFNOSUPPORT = 124, + /// Address already in use + ADDRINUSE = 125, + /// Can't assign requested address + ADDRNOTAVAIL = 126, + + // Operational Errors + /// Network is down + NETDOWN = 127, + /// Network is unreachable + NETUNREACH = 128, + /// Network dropped connection because + NETRESET = 129, + /// Software caused connection abort + CONNABORTED = 130, + /// Connection reset by peer + CONNRESET = 131, + /// No buffer space available + NOBUFS = 132, + /// Socket is already connected + ISCONN = 133, + /// Socket is not connected + NOTCONN = 134, + /// Can't send after socket shutdown + SHUTDOWN = 143, + /// Too many references: can't splice + TOOMANYREFS = 144, + /// Connection timed out + TIMEDOUT = 145, + /// Connection refused + CONNREFUSED = 146, + /// Host is down + HOSTDOWN = 147, + /// No route to host + HOSTUNREACH = 148, + /// operation already in progress + ALREADY = 149, + /// operation now in progress + INPROGRESS = 150, + + // SUN Network File System + /// Stale NFS file handle + STALE = 151, + + _, +}; + +pub const MINSIGSTKSZ = 2048; +pub const SIGSTKSZ = 8192; + +pub const SS_ONSTACK = 0x1; +pub const SS_DISABLE = 0x2; + +pub const stack_t = extern struct { + sp: [*]u8, + size: isize, + flags: i32, +}; + +pub const S = struct { + pub const IFMT = 0o170000; + + pub const IFIFO = 0o010000; + pub const IFCHR = 0o020000; + pub const IFDIR = 0o040000; + pub const IFBLK = 0o060000; + pub const IFREG = 0o100000; + pub const IFLNK = 0o120000; + pub const IFSOCK = 0o140000; + /// SunOS 2.6 Door + pub const IFDOOR = 0o150000; + /// Solaris 10 Event Port + pub const IFPORT = 0o160000; + + pub const ISUID = 0o4000; + pub const ISGID = 0o2000; + pub const ISVTX = 0o1000; + pub const IRWXU = 0o700; + pub const IRUSR = 0o400; + pub const IWUSR = 0o200; + pub const IXUSR = 0o100; + pub const IRWXG = 0o070; + pub const IRGRP = 0o040; + pub const IWGRP = 0o020; + pub const IXGRP = 0o010; + pub const IRWXO = 0o007; + pub const IROTH = 0o004; + pub const IWOTH = 0o002; + pub const IXOTH = 0o001; + + pub fn ISFIFO(m: u32) bool { + return m & IFMT == IFIFO; + } + + pub fn ISCHR(m: u32) bool { + return m & IFMT == IFCHR; + } + + pub fn ISDIR(m: u32) bool { + return m & IFMT == IFDIR; + } + + pub fn ISBLK(m: u32) bool { + return m & IFMT == IFBLK; + } + + pub fn ISREG(m: u32) bool { + return m & IFMT == IFREG; + } + + pub fn ISLNK(m: u32) bool { + return m & IFMT == IFLNK; + } + + pub fn ISSOCK(m: u32) bool { + return m & IFMT == IFSOCK; + } + + pub fn ISDOOR(m: u32) bool { + return m & IFMT == IFDOOR; + } + + pub fn ISPORT(m: u32) bool { + return m & IFMT == IFPORT; + } +}; + +pub const AT = struct { + /// Magic value that specify the use of the current working directory + /// to determine the target of relative file paths in the openat() and + /// similar syscalls. + pub const FDCWD = @bitCast(fd_t, @as(u32, 0xffd19553)); + + /// Do not follow symbolic links + pub const SYMLINK_NOFOLLOW = 0x1000; + /// Follow symbolic link + pub const SYMLINK_FOLLOW = 0x2000; + /// Remove directory instead of file + pub const REMOVEDIR = 0x1; + pub const TRIGGER = 0x2; + /// Check access using effective user and group ID + pub const EACCESS = 0x4; +}; + +pub const POSIX_FADV = struct { + pub const NORMAL = 0; + pub const RANDOM = 1; + pub const SEQUENTIAL = 2; + pub const WILLNEED = 3; + pub const DONTNEED = 4; + pub const NOREUSE = 5; +}; + +pub const HOST_NAME_MAX = 255; + +pub const IPPROTO = struct { + /// dummy for IP + pub const IP = 0; + /// Hop by hop header for IPv6 + pub const HOPOPTS = 0; + /// control message protocol + pub const ICMP = 1; + /// group control protocol + pub const IGMP = 2; + /// gateway^2 (deprecated) + pub const GGP = 3; + /// IP in IP encapsulation + pub const ENCAP = 4; + /// tcp + pub const TCP = 6; + /// exterior gateway protocol + pub const EGP = 8; + /// pup + pub const PUP = 12; + /// user datagram protocol + pub const UDP = 17; + /// xns idp + pub const IDP = 22; + /// IPv6 encapsulated in IP + pub const IPV6 = 41; + /// Routing header for IPv6 + pub const ROUTING = 43; + /// Fragment header for IPv6 + pub const FRAGMENT = 44; + /// rsvp + pub const RSVP = 46; + /// IPsec Encap. Sec. Payload + pub const ESP = 50; + /// IPsec Authentication Hdr. + pub const AH = 51; + /// ICMP for IPv6 + pub const ICMPV6 = 58; + /// No next header for IPv6 + pub const NONE = 59; + /// Destination options + pub const DSTOPTS = 60; + /// "hello" routing protocol + pub const HELLO = 63; + /// UNOFFICIAL net disk proto + pub const ND = 77; + /// ISO clnp + pub const EON = 80; + /// OSPF + pub const OSPF = 89; + /// PIM routing protocol + pub const PIM = 103; + /// Stream Control + pub const SCTP = 132; + /// raw IP packet + pub const RAW = 255; + /// Sockets Direct Protocol + pub const PROTO_SDP = 257; +}; + +pub const priority = enum(c_int) { + PROCESS = 0, + PGRP = 1, + USER = 2, + GROUP = 3, + SESSION = 4, + LWP = 5, + TASK = 6, + PROJECT = 7, + ZONE = 8, + CONTRACT = 9, +}; + +pub const rlimit_resource = enum(c_int) { + CPU = 0, + FSIZE = 1, + DATA = 2, + STACK = 3, + CORE = 4, + NOFILE = 5, + VMEM = 6, + _, + + pub const AS: rlimit_resource = .VMEM; +}; + +pub const rlim_t = u64; + +pub const RLIM = struct { + /// No limit + pub const INFINITY: rlim_t = (1 << 63) - 3; + pub const SAVED_MAX: rlim_t = (1 << 63) - 2; + pub const SAVED_CUR: rlim_t = (1 << 63) - 1; +}; + +pub const rlimit = extern struct { + /// Soft limit + cur: rlim_t, + /// Hard limit + max: rlim_t, +}; + +pub const RUSAGE_SELF = 0; +pub const RUSAGE_CHILDREN = -1; +pub const RUSAGE_THREAD = 1; + +pub const rusage = extern struct { + utime: timeval, + stime: timeval, + maxrss: isize, + ixrss: isize, + idrss: isize, + isrss: isize, + minflt: isize, + majflt: isize, + nswap: isize, + inblock: isize, + oublock: isize, + msgsnd: isize, + msgrcv: isize, + nsignals: isize, + nvcsw: isize, + nivcsw: isize, +}; + +pub const SHUT = struct { + pub const RD = 0; + pub const WR = 1; + pub const RDWR = 2; +}; + +pub const pollfd = extern struct { + fd: fd_t, + events: i16, + revents: i16, +}; + +/// Testable events (may be specified in ::pollfd::events). +pub const POLL = struct { + pub const IN = 0x0001; + pub const PRI = 0x0002; + pub const OUT = 0x0004; + pub const RDNORM = 0x0040; + pub const WRNORM = .OUT; + pub const RDBAND = 0x0080; + pub const WRBAND = 0x0100; + /// Read-side hangup. + pub const RDHUP = 0x4000; + + /// Non-testable events (may not be specified in events). + pub const ERR = 0x0008; + pub const HUP = 0x0010; + pub const NVAL = 0x0020; + + /// Events to control `/dev/poll` (not specified in revents) + pub const REMOVE = 0x0800; + pub const ONESHOT = 0x1000; + pub const ET = 0x2000; +}; + +/// Extensions to the ELF auxiliary vector. +pub const AT_SUN = struct { + /// effective user id + pub const UID = 2000; + /// real user id + pub const RUID = 2001; + /// effective group id + pub const GID = 2002; + /// real group id + pub const RGID = 2003; + /// dynamic linker's ELF header + pub const LDELF = 2004; + /// dynamic linker's section headers + pub const LDSHDR = 2005; + /// name of dynamic linker + pub const LDNAME = 2006; + /// large pagesize + pub const LPAGESZ = 2007; + /// platform name + pub const PLATFORM = 2008; + /// hints about hardware capabilities. + pub const HWCAP = 2009; + pub const HWCAP2 = 2023; + /// flush icache? + pub const IFLUSH = 2010; + /// cpu name + pub const CPU = 2011; + /// exec() path name in the auxv, null terminated. + pub const EXECNAME = 2014; + /// mmu module name + pub const MMU = 2015; + /// dynamic linkers data segment + pub const LDDATA = 2016; + /// AF_SUN_ flags passed from the kernel + pub const AUXFLAGS = 2017; + /// name of the emulation binary for the linker + pub const EMULATOR = 2018; + /// name of the brand library for the linker + pub const BRANDNAME = 2019; + /// vectors for brand modules. + pub const BRAND_AUX1 = 2020; + pub const BRAND_AUX2 = 2021; + pub const BRAND_AUX3 = 2022; + pub const BRAND_AUX4 = 2025; + pub const BRAND_NROOT = 2024; + /// vector for comm page. + pub const COMMPAGE = 2026; + /// information about the x86 FPU. + pub const FPTYPE = 2027; + pub const FPSIZE = 2028; +}; + +/// ELF auxiliary vector flags. +pub const AF_SUN = struct { + /// tell ld.so.1 to run "secure" and ignore the environment. + pub const SETUGID = 0x00000001; + /// hardware capabilities can be verified against AT_SUN_HWCAP + pub const HWCAPVERIFY = 0x00000002; + pub const NOPLM = 0x00000004; +}; + +// TODO: Add sysconf numbers when the other OSs do. +pub const _SC = struct { + pub const NPROCESSORS_ONLN = 15; +}; + +pub const procfs = struct { + pub const misc_header = extern struct { + size: u32, + @"type": enum(u32) { + Pathname, + Socketname, + Peersockname, + SockoptsBoolOpts, + SockoptLinger, + SockoptSndbuf, + SockoptRcvbuf, + SockoptIpNexthop, + SockoptIpv6Nexthop, + SockoptType, + SockoptTcpCongestion, + SockfiltersPriv = 14, + }, + }; + + pub const fdinfo = extern struct { + fd: fd_t, + mode: mode_t, + ino: ino_t, + size: off_t, + offset: off_t, + uid: uid_t, + gid: gid_t, + dev_major: major_t, + dev_minor: minor_t, + special_major: major_t, + special_minor: minor_t, + fileflags: i32, + fdflags: i32, + locktype: i16, + lockpid: pid_t, + locksysid: i32, + peerpid: pid_t, + __filler: [25]c_int, + peername: [15:0]u8, + misc: [1]u8, + }; +}; + +pub const SFD = struct { + pub const CLOEXEC = 0o2000000; + pub const NONBLOCK = 0o4000; +}; + +pub const signalfd_siginfo = extern struct { + signo: u32, + errno: i32, + code: i32, + pid: u32, + uid: uid_t, + fd: i32, + tid: u32, // unused + band: u32, + overrun: u32, // unused + trapno: u32, + status: i32, + int: i32, // unused + ptr: u64, // unused + utime: u64, + stime: u64, + addr: u64, + __pad: [48]u8, +}; + +pub const PORT_SOURCE = struct { + pub const AIO = 1; + pub const TIMER = 2; + pub const USER = 3; + pub const FD = 4; + pub const ALERT = 5; + pub const MQ = 6; + pub const FILE = 7; +}; + +pub const PORT_ALERT = struct { + pub const SET = 0x01; + pub const UPDATE = 0x02; +}; + +/// User watchable file events. +pub const FILE_EVENT = struct { + pub const ACCESS = 0x00000001; + pub const MODIFIED = 0x00000002; + pub const ATTRIB = 0x00000004; + pub const DELETE = 0x00000010; + pub const RENAME_TO = 0x00000020; + pub const RENAME_FROM = 0x00000040; + pub const TRUNC = 0x00100000; + pub const NOFOLLOW = 0x10000000; + /// The filesystem holding the watched file was unmounted. + pub const UNMOUNTED = 0x20000000; + /// Some other file/filesystem got mounted over the watched file/directory. + pub const MOUNTEDOVER = 0x40000000; + + pub fn isException(event: u32) bool { + return event & (UNMOUNTED | DELETE | RENAME_TO | RENAME_FROM | MOUNTEDOVER) > 0; + } +}; + +pub const port_event = extern struct { + events: u32, + /// Event source. + source: u16, + __pad: u16, + /// Source-specific object. + object: ?*c_void, + /// User cookie. + cookie: ?*c_void, +}; + +pub const port_notify = extern struct { + /// Bind request(s) to port. + port: u32, + /// User defined variable. + user: ?*void, +}; + +pub const file_obj = extern struct { + /// Access time. + atim: timespec, + /// Modification time + mtim: timespec, + /// Change time + ctim: timespec, + __pad: [3]usize, + name: [*:0]u8, +}; + +// struct ifreq is marked obsolete, with struct lifreq prefered for interface requests. +// Here we alias lifreq to ifreq to avoid chainging existing code in os and x.os.IPv6. +pub const SIOCGLIFINDEX = IOWR('i', 133, lifreq); +pub const SIOCGIFINDEX = SIOCGLIFINDEX; +pub const MAX_HDW_LEN = 64; +pub const IFNAMESIZE = 32; + +pub const lif_nd_req = extern struct { + addr: sockaddr.storage, + state_create: u8, + state_same_lla: u8, + state_diff_lla: u8, + hdw_len: i32, + flags: i32, + __pad: i32, + hdw_addr: [MAX_HDW_LEN]u8, +}; + +pub const lif_ifinfo_req = extern struct { + maxhops: u8, + reachtime: u32, + reachretrans: u32, + maxmtu: u32, +}; + +/// IP interface request. See if_tcp(7p) for more info. +pub const lifreq = extern struct { + // Not actually in a union, but the stdlib expects one for ifreq + ifrn: extern union { + /// Interface name, e.g. "lo0", "en0". + name: [IFNAMESIZE]u8, + }, + ru1: extern union { + /// For subnet/token etc. + addrlen: i32, + /// Driver's PPA (physical point of attachment). + ppa: u32, + }, + /// One of the IFT types, e.g. IFT_ETHER. + @"type": u32, + ifru: extern union { + /// Address. + addr: sockaddr.storage, + /// Other end of a peer-to-peer link. + dstaddr: sockaddr.storage, + /// Broadcast address. + broadaddr: sockaddr.storage, + /// Address token. + token: sockaddr.storage, + /// Subnet prefix. + subnet: sockaddr.storage, + /// Interface index. + ivalue: i32, + /// Flags for SIOC?LIFFLAGS. + flags: u64, + /// Hop count metric + metric: i32, + /// Maximum transmission unit + mtu: u32, + // Technically [2]i32 + muxid: packed struct { ip: i32, arp: i32 }, + /// Neighbor reachability determination entries + nd_req: lif_nd_req, + /// Link info + ifinfo_req: lif_ifinfo_req, + /// Name of the multipath interface group + groupname: [IFNAMESIZE]u8, + binding: [IFNAMESIZE]u8, + /// Zone id associated with this interface. + zoneid: zoneid_t, + /// Duplicate address detection state. Either in progress or completed. + dadstate: u32, + }, +}; + +pub const ifreq = lifreq; + +const IoCtlCommand = enum(u32) { + none = 0x20000000, // no parameters + write = 0x40000000, // copy out parameters + read = 0x80000000, // copy in parameters + read_write = 0xc0000000, +}; + +fn ioImpl(cmd: IoCtlCommand, io_type: u8, nr: u8, comptime IOT: type) i32 { + const size = @intCast(u32, @truncate(u8, @sizeOf(IOT))) << 16; + const t = @intCast(u32, io_type) << 8; + return @bitCast(i32, @enumToInt(cmd) | size | t | nr); +} + +pub fn IO(io_type: u8, nr: u8) i32 { + return ioImpl(.none, io_type, nr, void); +} + +pub fn IOR(io_type: u8, nr: u8, comptime IOT: type) i32 { + return ioImpl(.write, io_type, nr, IOT); +} + +pub fn IOW(io_type: u8, nr: u8, comptime IOT: type) i32 { + return ioImpl(.read, io_type, nr, IOT); +} + +pub fn IOWR(io_type: u8, nr: u8, comptime IOT: type) i32 { + return ioImpl(.read_write, io_type, nr, IOT); +} diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 8517703566..6e2cef1f0c 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -654,6 +654,7 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) anyerror!DebugInfo { .openbsd, .macos, .windows, + .solaris, => return DebugInfo.init(allocator), else => return error.UnsupportedDebugInfo, } @@ -1420,7 +1421,7 @@ pub const ModuleDebugInfo = switch (native_os) { }; } }, - .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku => struct { + .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris => struct { base_address: usize, dwarf: DW.DwarfInfo, mapped_memory: []const u8, @@ -1468,7 +1469,7 @@ fn getDebugInfoAllocator() *mem.Allocator { /// Whether or not the current target can print useful debug information when a segfault occurs. pub const have_segfault_handling_support = switch (native_os) { - .linux, .netbsd => true, + .linux, .netbsd, .solaris => true, .windows => true, .freebsd, .openbsd => @hasDecl(os.system, "ucontext_t"), else => false, @@ -1535,6 +1536,7 @@ fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_v .freebsd => @ptrToInt(info.addr), .netbsd => @ptrToInt(info.info.reason.fault.addr), .openbsd => @ptrToInt(info.data.fault.addr), + .solaris => @ptrToInt(info.reason.fault.addr), else => unreachable, }; @@ -1559,13 +1561,13 @@ fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_v .x86_64 => { const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); const ip = switch (native_os) { - .linux, .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.RIP]), + .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RIP]), .freebsd => @intCast(usize, ctx.mcontext.rip), .openbsd => @intCast(usize, ctx.sc_rip), else => unreachable, }; const bp = switch (native_os) { - .linux, .netbsd => @intCast(usize, ctx.mcontext.gregs[os.REG.RBP]), + .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RBP]), .openbsd => @intCast(usize, ctx.sc_rbp), .freebsd => @intCast(usize, ctx.mcontext.rbp), else => unreachable, diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index fb4cefed0a..91c66503f1 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -14,7 +14,7 @@ const max = std.math.max; pub const DynLib = switch (builtin.os.tag) { .linux => if (builtin.link_libc) DlDynlib else ElfDynLib, .windows => WindowsDynLib, - .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly => DlDynlib, + .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris => DlDynlib, else => void, }; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 66eb1d6642..7aa5dd3976 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -35,7 +35,7 @@ pub const Watch = @import("fs/watch.zig").Watch; /// fit into a UTF-8 encoded array of this length. /// The byte count includes room for a null sentinel byte. pub const MAX_PATH_BYTES = switch (builtin.os.tag) { - .linux, .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .haiku => os.PATH_MAX, + .linux, .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .haiku, .solaris => os.PATH_MAX, // Each UTF-16LE character may be expanded to 3 UTF-8 bytes. // If it would require 4 UTF-8 bytes, then there would be a surrogate // pair in the UTF-16LE, and we (over)account 3 bytes for it that way. @@ -298,10 +298,10 @@ pub const Dir = struct { pub const Kind = File.Kind; }; - const IteratorError = error{AccessDenied} || os.UnexpectedError; + const IteratorError = error{ AccessDenied, SystemResources } || os.UnexpectedError; pub const Iterator = switch (builtin.os.tag) { - .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => struct { + .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => struct { dir: Dir, seek: i64, buf: [8192]u8, // TODO align(@alignOf(os.system.dirent)), @@ -318,6 +318,7 @@ pub const Dir = struct { switch (builtin.os.tag) { .macos, .ios => return self.nextDarwin(), .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(), + .solaris => return self.nextSolaris(), else => @compileError("unimplemented"), } } @@ -372,6 +373,60 @@ pub const Dir = struct { } } + fn nextSolaris(self: *Self) !?Entry { + start_over: while (true) { + if (self.index >= self.end_index) { + const rc = os.system.getdents(self.dir.fd, &self.buf, self.buf.len); + switch (os.errno(rc)) { + .SUCCESS => {}, + .BADF => unreachable, // Dir is invalid or was opened without iteration ability + .FAULT => unreachable, + .NOTDIR => unreachable, + .INVAL => unreachable, + else => |err| return os.unexpectedErrno(err), + } + if (rc == 0) return null; + self.index = 0; + self.end_index = @intCast(usize, rc); + } + const entry = @ptrCast(*align(1) os.system.dirent, &self.buf[self.index]); + const next_index = self.index + entry.reclen(); + self.index = next_index; + + const name = mem.spanZ(@ptrCast([*:0]u8, &entry.d_name)); + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) + continue :start_over; + + // Solaris dirent doesn't expose d_type, so we have to call stat to get it. + const stat_info = os.fstatat( + self.dir.fd, + name, + os.AT.SYMLINK_NOFOLLOW, + ) catch |err| switch (err) { + error.NameTooLong => unreachable, + error.SymLinkLoop => unreachable, + error.FileNotFound => unreachable, // lost the race + else => |e| return e, + }; + const entry_kind = switch (stat_info.mode & os.S.IFMT) { + os.S.IFIFO => Entry.Kind.NamedPipe, + os.S.IFCHR => Entry.Kind.CharacterDevice, + os.S.IFDIR => Entry.Kind.Directory, + os.S.IFBLK => Entry.Kind.BlockDevice, + os.S.IFREG => Entry.Kind.File, + os.S.IFLNK => Entry.Kind.SymLink, + os.S.IFSOCK => Entry.Kind.UnixDomainSocket, + os.S.IFDOOR => Entry.Kind.Door, + os.S.IFPORT => Entry.Kind.EventPort, + else => Entry.Kind.Unknown, + }; + return Entry{ + .name = name, + .kind = entry_kind, + }; + } + } + fn nextBsd(self: *Self) !?Entry { start_over: while (true) { if (self.index >= self.end_index) { @@ -704,6 +759,7 @@ pub const Dir = struct { .netbsd, .dragonfly, .openbsd, + .solaris, => return Iterator{ .dir = self, .seek = 0, @@ -1556,7 +1612,7 @@ pub const Dir = struct { 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 - .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd => { + .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => { // 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; @@ -2441,6 +2497,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { } switch (builtin.os.tag) { .linux => return os.readlinkZ("/proc/self/exe", out_buffer), + .solaris => return os.readlinkZ("/proc/self/path/a.out", out_buffer), .freebsd, .dragonfly => { var mib = [4]c_int{ os.CTL.KERN, os.KERN.PROC, os.KERN.PROC_PATHNAME, -1 }; var out_len: usize = out_buffer.len; diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index d08b743919..fd0492f0bf 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -41,6 +41,8 @@ pub const File = struct { File, UnixDomainSocket, Whiteout, + Door, + EventPort, Unknown, }; @@ -320,28 +322,40 @@ pub const File = struct { const atime = st.atime(); const mtime = st.mtime(); const ctime = st.ctime(); + const kind: Kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) { + .BLOCK_DEVICE => Kind.BlockDevice, + .CHARACTER_DEVICE => Kind.CharacterDevice, + .DIRECTORY => Kind.Directory, + .SYMBOLIC_LINK => Kind.SymLink, + .REGULAR_FILE => Kind.File, + .SOCKET_STREAM, .SOCKET_DGRAM => Kind.UnixDomainSocket, + else => Kind.Unknown, + } else blk: { + const m = st.mode & os.S.IFMT; + switch (m) { + os.S.IFBLK => break :blk Kind.BlockDevice, + os.S.IFCHR => break :blk Kind.CharacterDevice, + os.S.IFDIR => break :blk Kind.Directory, + os.S.IFIFO => break :blk Kind.NamedPipe, + os.S.IFLNK => break :blk Kind.SymLink, + os.S.IFREG => break :blk Kind.File, + os.S.IFSOCK => break :blk Kind.UnixDomainSocket, + else => {}, + } + if (builtin.os.tag == .solaris) switch (m) { + os.S.IFDOOR => break :blk Kind.Door, + os.S.IFPORT => break :blk Kind.EventPort, + else => {}, + }; + + break :blk .Unknown; + }; + return Stat{ .inode = st.ino, .size = @bitCast(u64, st.size), .mode = st.mode, - .kind = if (builtin.os.tag == .wasi and !builtin.link_libc) switch (st.filetype) { - .BLOCK_DEVICE => Kind.BlockDevice, - .CHARACTER_DEVICE => Kind.CharacterDevice, - .DIRECTORY => Kind.Directory, - .SYMBOLIC_LINK => Kind.SymLink, - .REGULAR_FILE => Kind.File, - .SOCKET_STREAM, .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, - }, + .kind = kind, .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/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig index fed1c85f39..491b6fe824 100644 --- a/lib/std/fs/get_app_data_dir.zig +++ b/lib/std/fs/get_app_data_dir.zig @@ -44,7 +44,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD }; return fs.path.join(allocator, &[_][]const u8{ home_dir, "Library", "Application Support", appname }); }, - .linux, .freebsd, .netbsd, .dragonfly, .openbsd => { + .linux, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => { const home_dir = os.getenv("HOME") orelse { // TODO look in /etc/passwd return error.AppDataDirUnavailable; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 0ce416454b..a6e1e440fd 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -188,7 +188,7 @@ fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool { test "Dir.realpath smoke test" { switch (builtin.os.tag) { - .linux, .windows, .macos, .ios, .watchos, .tvos => {}, + .linux, .windows, .macos, .ios, .watchos, .tvos, .solaris => {}, else => return error.SkipZigTest, } diff --git a/lib/std/os.zig b/lib/std/os.zig index 5a4828286d..d85815dde0 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -31,6 +31,7 @@ pub const freebsd = std.c; pub const haiku = std.c; pub const netbsd = std.c; pub const openbsd = std.c; +pub const solaris = std.c; pub const linux = @import("os/linux.zig"); pub const uefi = @import("os/uefi.zig"); pub const wasi = @import("os/wasi.zig"); @@ -64,8 +65,10 @@ else switch (builtin.os.tag) { }; pub const AF = system.AF; +pub const AF_SUN = system.AF_SUN; pub const ARCH = system.ARCH; pub const AT = system.AT; +pub const AT_SUN = system.AT_SUN; pub const CLOCK = system.CLOCK; pub const CPU_COUNT = system.CPU_COUNT; pub const CTL = system.CTL; @@ -101,6 +104,7 @@ pub const RR = system.RR; pub const S = system.S; pub const SA = system.SA; pub const SC = system.SC; +pub const _SC = system._SC; pub const SEEK = system.SEEK; pub const SHUT = system.SHUT; pub const SIG = system.SIG; @@ -143,6 +147,10 @@ pub const off_t = system.off_t; pub const oflags_t = system.oflags_t; pub const pid_t = system.pid_t; pub const pollfd = system.pollfd; +pub const port_t = system.port_t; +pub const port_event = system.port_event; +pub const port_notify = system.port_notify; +pub const file_obj = system.file_obj; pub const rights_t = system.rights_t; pub const rlim_t = system.rlim_t; pub const rlimit = system.rlimit; @@ -2038,6 +2046,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr .NOTDIR => return error.NotDir, .NOMEM => return error.SystemResources, .ROFS => return error.ReadOnlyFileSystem, + .EXIST => return error.DirNotEmpty, .NOTEMPTY => return error.DirNotEmpty, .INVAL => unreachable, // invalid flags, or pathname has . as last component @@ -4667,6 +4676,16 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { }; return target; }, + .solaris => { + var procfs_buf: ["/proc/self/path/-2147483648".len:0]u8 = undefined; + const proc_path = std.fmt.bufPrintZ(procfs_buf[0..], "/proc/self/path/{d}", .{fd}) catch unreachable; + + const target = readlinkZ(proc_path, out_buffer) catch |err| switch (err) { + error.UnsupportedReparsePointType => unreachable, + else => |e| return e, + }; + return target; + }, else => @compileError("querying for canonical path of a handle is unsupported on this host"), } } diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 3a0187f735..d3c8d13bd1 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -188,7 +188,10 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { } test "link with relative paths" { - if (native_os != .linux) return error.SkipZigTest; + switch (native_os) { + .linux, .solaris => {}, + else => return error.SkipZigTest, + } var cwd = fs.cwd(); cwd.deleteFile("example.txt") catch {}; @@ -222,7 +225,10 @@ test "link with relative paths" { } test "linkat with different directories" { - if (native_os != .linux) return error.SkipZigTest; + switch (native_os) { + .linux, .solaris => {}, + else => return error.SkipZigTest, + } var cwd = fs.cwd(); var tmp = tmpDir(.{}); @@ -634,8 +640,10 @@ test "fcntl" { } test "signalfd" { - if (native_os != .linux) - return error.SkipZigTest; + switch (native_os) { + .linux, .solaris => {}, + else => return error.SkipZigTest, + } _ = std.os.signalfd; } @@ -658,8 +666,10 @@ test "sync" { } test "fsync" { - if (native_os != .linux and native_os != .windows) - return error.SkipZigTest; + switch (native_os) { + .linux, .windows, .solaris => {}, + else => return error.SkipZigTest, + } var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -754,7 +764,10 @@ test "sigaction" { } test "dup & dup2" { - if (native_os != .linux) return error.SkipZigTest; + switch (native_os) { + .linux, .solaris => {}, + else => return error.SkipZigTest, + } var tmp = tmpDir(.{}); defer tmp.cleanup(); diff --git a/lib/std/process.zig b/lib/std/process.zig index f8b986d695..8dce2462a2 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -625,7 +625,7 @@ pub const UserInfo = struct { /// POSIX function which gets a uid from username. pub fn getUserInfo(name: []const u8) !UserInfo { return switch (builtin.os.tag) { - .linux, .macos, .watchos, .tvos, .ios, .freebsd, .netbsd, .openbsd, .haiku => posixGetUserInfo(name), + .linux, .macos, .watchos, .tvos, .ios, .freebsd, .netbsd, .openbsd, .haiku, .solaris => posixGetUserInfo(name), else => @compileError("Unsupported OS"), }; } @@ -753,6 +753,7 @@ pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0] .netbsd, .dragonfly, .openbsd, + .solaris, => { var paths = List.init(allocator); errdefer { diff --git a/lib/std/target.zig b/lib/std/target.zig index c94b7a4386..65c9b91839 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -235,7 +235,6 @@ pub const Target = struct { .fuchsia, .kfreebsd, .lv2, - .solaris, .zos, .haiku, .minix, @@ -310,6 +309,12 @@ pub const Target = struct { .max = .{ .major = 6, .minor = 0 }, }, }, + .solaris => return .{ + .semver = .{ + .min = .{ .major = 5, .minor = 11 }, + .max = .{ .major = 5, .minor = 11 }, + }, + }, .linux => return .{ .linux = .{ @@ -353,6 +358,7 @@ pub const Target = struct { .netbsd, .openbsd, .dragonfly, + .solaris, => return TaggedVersionRange{ .semver = self.version_range.semver }, else => return .none, @@ -385,6 +391,7 @@ pub const Target = struct { .dragonfly, .openbsd, .haiku, + .solaris, => true, .linux, @@ -395,7 +402,6 @@ pub const Target = struct { .fuchsia, .kfreebsd, .lv2, - .solaris, .zos, .minix, .rtems, @@ -1516,6 +1522,7 @@ pub const Target = struct { .netbsd => return copy(&result, "/libexec/ld.elf_so"), .openbsd => return copy(&result, "/usr/libexec/ld.so"), .dragonfly => return copy(&result, "/libexec/ld-elf.so.2"), + .solaris => return copy(&result, "/lib/64/ld.so.1"), .linux => switch (self.cpu.arch) { .i386, .sparc, @@ -1634,7 +1641,6 @@ pub const Target = struct { .fuchsia, .kfreebsd, .lv2, - .solaris, .zos, .minix, .rtems, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index da4057d6e1..a86d6a2d45 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -101,6 +101,17 @@ pub const NativePaths = struct { return self; } + if (comptime native_target.os.tag == .solaris) { + try self.addLibDir("/usr/lib/64"); + try self.addLibDir("/usr/local/lib/64"); + try self.addLibDir("/lib/64"); + + try self.addIncludeDir("/usr/include"); + try self.addIncludeDir("/usr/local/include"); + + return self; + } + if (native_target.os.tag != .windows) { const triple = try native_target.linuxTriple(allocator); const qual = native_target.cpu.arch.ptrBitWidth(); @@ -243,6 +254,18 @@ pub const NativeTargetInfo = struct { error.InvalidVersion => {}, } }, + .solaris => { + const uts = std.os.uname(); + const release = mem.spanZ(&uts.release); + if (std.builtin.Version.parse(release)) |ver| { + os.version_range.semver.min = ver; + os.version_range.semver.max = ver; + } else |err| switch (err) { + error.Overflow => {}, + error.InvalidCharacter => {}, + error.InvalidVersion => {}, + } + }, .windows => { const detected_version = windows.detectRuntimeVersion(); os.version_range.windows.min = detected_version; diff --git a/src/libc_installation.zig b/src/libc_installation.zig index 62174930f8..d54af71415 100644 --- a/src/libc_installation.zig +++ b/src/libc_installation.zig @@ -221,6 +221,7 @@ pub const LibCInstallation = struct { batch.add(&async self.findNativeIncludeDirPosix(args)); switch (Target.current.os.tag) { .freebsd, .netbsd, .openbsd, .dragonfly => self.crt_dir = try std.mem.dupeZ(args.allocator, u8, "/usr/lib"), + .solaris => self.crt_dir = try std.mem.dupeZ(args.allocator, u8, "/usr/lib/64"), .linux => batch.add(&async self.findNativeCrtDirPosix(args)), else => {}, } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index ec047ced35..6c3220d39e 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -3446,6 +3446,15 @@ const CsuObjects = struct { .static_pie => result.set( "start_dyn.o", "crti.o", "crtbeginS.o", "crtendS.o", "crtn.o" ), // zig fmt: on }, + .solaris => switch (mode) { + // zig fmt: off + .dynamic_lib => result.set( null, "crti.o", null, null, "crtn.o" ), + .dynamic_exe, + .dynamic_pie => result.set( "crt1.o", "crti.o", null, null, "crtn.o" ), + .static_exe, + .static_pie => result.set( null, null, null, null, null ), + // zig fmt: on + }, else => {}, } } diff --git a/src/stage1/os.hpp b/src/stage1/os.hpp index f1022778d4..6d086c8901 100644 --- a/src/stage1/os.hpp +++ b/src/stage1/os.hpp @@ -33,6 +33,8 @@ #define ZIG_OS_OPENBSD #elif defined(__HAIKU__) #define ZIG_OS_HAIKU +#elif defined(__sun) +#define ZIG_OS_SOLARIS #else #define ZIG_OS_UNKNOWN #endif diff --git a/src/stage1/target.cpp b/src/stage1/target.cpp index 8415b1934e..4da19e4c36 100644 --- a/src/stage1/target.cpp +++ b/src/stage1/target.cpp @@ -399,6 +399,9 @@ Error target_parse_os(Os *out_os, const char *os_ptr, size_t os_len) { #elif defined(ZIG_OS_HAIKU) *out_os = OsHaiku; return ErrorNone; +#elif defined(ZIG_OS_SOLARIS) + *out_os = OsSolaris; + return ErrorNone; #else zig_panic("stage1 is unable to detect native target for this OS"); #endif @@ -670,6 +673,7 @@ uint32_t target_c_type_size_in_bits(const ZigTarget *target, CIntType id) { case OsOpenBSD: case OsWASI: case OsHaiku: + case OsSolaris: case OsEmscripten: case OsPlan9: switch (id) { @@ -728,7 +732,6 @@ uint32_t target_c_type_size_in_bits(const ZigTarget *target, CIntType id) { case OsCloudABI: case OsKFreeBSD: case OsLv2: - case OsSolaris: case OsZOS: case OsMinix: case OsRTEMS: diff --git a/src/target.zig b/src/target.zig index 09e65ff909..621335209c 100644 --- a/src/target.zig +++ b/src/target.zig @@ -433,6 +433,13 @@ pub fn libcFullLinkFlags(target: std.Target) []const []const u8 { "-lc", "-lutil", }, + .solaris => &[_][]const u8{ + "-lm", + "-lsocket", + "-lnsl", + // Solaris releases after 10 merged the threading libraries into libc. + "-lc", + }, .haiku => &[_][]const u8{ "-lm", "-lroot", diff --git a/src/type.zig b/src/type.zig index 4a0f2a0536..a2da252f61 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3800,6 +3800,7 @@ pub const CType = enum { .wasi, .emscripten, .plan9, + .solaris, => switch (self) { .short, .ushort, @@ -3851,7 +3852,6 @@ pub const CType = enum { .fuchsia, .kfreebsd, .lv2, - .solaris, .zos, .haiku, .minix, From 42aa1ea115eca3dcc704eddf020ce87271a41174 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 24 Sep 2021 17:33:06 -0700 Subject: [PATCH 116/160] stage2: implement `@memset` and `@memcpy` builtins --- src/Air.zig | 15 ++++ src/AstGen.zig | 16 ++--- src/Liveness.zig | 5 ++ src/Sema.zig | 126 ++++++++++++++++++++++++++++++++-- src/codegen.zig | 12 ++++ src/codegen/c.zig | 46 ++++++++++++- src/codegen/llvm.zig | 53 ++++++++++++++ src/codegen/llvm/bindings.zig | 19 +++++ src/link/C/zig.h | 1 + src/print_air.zig | 24 +++++++ src/print_zir.zig | 30 +++++++- src/type.zig | 73 +++++++++++++++++--- test/behavior/basic.zig | 18 +++++ test/behavior/misc.zig | 10 --- 14 files changed, 412 insertions(+), 36 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index c3181fac60..4341271f3a 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -321,6 +321,19 @@ pub const Inst = struct { /// Uses the `ty_op` field. int_to_float, + /// Given dest ptr, value, and len, set all elements at dest to value. + /// Result type is always void. + /// Uses the `pl_op` field. Operand is the dest ptr. Payload is `Bin`. `lhs` is the + /// value, `rhs` is the length. + /// The element type may be any type, not just u8. + memset, + /// Given dest ptr, src ptr, and len, copy len elements from src to dest. + /// Result type is always void. + /// Uses the `pl_op` field. Operand is the dest ptr. Payload is `Bin`. `lhs` is the + /// src ptr, `rhs` is the length. + /// The element type may be any type, not just u8. + memcpy, + /// Uses the `ty_pl` field with payload `Cmpxchg`. cmpxchg_weak, /// Uses the `ty_pl` field with payload `Cmpxchg`. @@ -628,6 +641,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .atomic_store_monotonic, .atomic_store_release, .atomic_store_seq_cst, + .memset, + .memcpy, => return Type.initTag(.void), .ptrtoint, diff --git a/src/AstGen.zig b/src/AstGen.zig index 416584bee9..469e77037a 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2149,8 +2149,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .field_ptr_type, .field_parent_ptr, .maximum, - .memcpy, - .memset, .minimum, .builtin_async_call, .c_import, @@ -2204,6 +2202,8 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .set_float_mode, .set_runtime_safety, .closure_capture, + .memcpy, + .memset, => break :b true, } } else switch (maybe_unused_result) { @@ -7576,17 +7576,17 @@ fn builtinCall( }, .memcpy => { const result = try gz.addPlNode(.memcpy, node, Zir.Inst.Memcpy{ - .dest = try expr(gz, scope, .{ .ty = .manyptr_u8_type }, params[0]), - .source = try expr(gz, scope, .{ .ty = .manyptr_const_u8_type }, params[1]), - .byte_count = try expr(gz, scope, .{ .ty = .usize_type }, params[2]), + .dest = try expr(gz, scope, .{ .coerced_ty = .manyptr_u8_type }, params[0]), + .source = try expr(gz, scope, .{ .coerced_ty = .manyptr_const_u8_type }, params[1]), + .byte_count = try expr(gz, scope, .{ .coerced_ty = .usize_type }, params[2]), }); return rvalue(gz, rl, result, node); }, .memset => { const result = try gz.addPlNode(.memset, node, Zir.Inst.Memset{ - .dest = try expr(gz, scope, .{ .ty = .manyptr_u8_type }, params[0]), - .byte = try expr(gz, scope, .{ .ty = .u8_type }, params[1]), - .byte_count = try expr(gz, scope, .{ .ty = .usize_type }, params[2]), + .dest = try expr(gz, scope, .{ .coerced_ty = .manyptr_u8_type }, params[0]), + .byte = try expr(gz, scope, .{ .coerced_ty = .u8_type }, params[1]), + .byte_count = try expr(gz, scope, .{ .coerced_ty = .usize_type }, params[2]), }); return rvalue(gz, rl, result, node); }, diff --git a/src/Liveness.zig b/src/Liveness.zig index 4da5eaa284..42ab1ab351 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -361,6 +361,11 @@ fn analyzeInst( const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none }); }, + .memset, .memcpy => { + const pl_op = inst_datas[inst].pl_op; + const extra = a.air.extraData(Air.Bin, pl_op.payload).data; + return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs }); + }, .br => { const br = inst_datas[inst].br; return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index 87cde2ca1a..41fabbfacd 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -341,8 +341,6 @@ pub fn analyzeBody( .field_ptr_type => try sema.zirFieldPtrType(block, inst), .field_parent_ptr => try sema.zirFieldParentPtr(block, inst), .maximum => try sema.zirMaximum(block, inst), - .memcpy => try sema.zirMemcpy(block, inst), - .memset => try sema.zirMemset(block, inst), .minimum => try sema.zirMinimum(block, inst), .builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst), .@"resume" => try sema.zirResume(block, inst), @@ -526,6 +524,16 @@ pub fn analyzeBody( i += 1; continue; }, + .memcpy => { + try sema.zirMemcpy(block, inst); + i += 1; + continue; + }, + .memset => { + try sema.zirMemset(block, inst); + i += 1; + continue; + }, // Special case instructions to handle comptime control flow. .@"break" => { @@ -8422,16 +8430,119 @@ fn zirMaximum(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr return sema.mod.fail(&block.base, src, "TODO: Sema.zirMaximum", .{}); } -fn zirMemcpy(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirMemcpy(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.Memcpy, inst_data.payload_index).data; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemcpy", .{}); + const dest_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const src_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const dest_ptr = sema.resolveInst(extra.dest); + const dest_ptr_ty = sema.typeOf(dest_ptr); + + if (dest_ptr_ty.zigTypeTag() != .Pointer) { + return sema.mod.fail(&block.base, dest_src, "expected pointer, found '{}'", .{dest_ptr_ty}); + } + if (dest_ptr_ty.isConstPtr()) { + return sema.mod.fail(&block.base, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty}); + } + + const uncasted_src_ptr = sema.resolveInst(extra.source); + const uncasted_src_ptr_ty = sema.typeOf(uncasted_src_ptr); + if (uncasted_src_ptr_ty.zigTypeTag() != .Pointer) { + return sema.mod.fail(&block.base, src_src, "expected pointer, found '{}'", .{ + uncasted_src_ptr_ty, + }); + } + const src_ptr_info = uncasted_src_ptr_ty.ptrInfo().data; + const wanted_src_ptr_ty = try Module.ptrType( + sema.arena, + dest_ptr_ty.elemType2(), + null, + src_ptr_info.@"align", + src_ptr_info.@"addrspace", + 0, + 0, + false, + src_ptr_info.@"allowzero", + src_ptr_info.@"volatile", + .Many, + ); + const src_ptr = try sema.coerce(block, wanted_src_ptr_ty, uncasted_src_ptr, src_src); + const len = try sema.coerce(block, Type.initTag(.usize), sema.resolveInst(extra.byte_count), len_src); + + const maybe_dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr); + const maybe_src_ptr_val = try sema.resolveDefinedValue(block, src_src, src_ptr); + const maybe_len_val = try sema.resolveDefinedValue(block, len_src, len); + + const runtime_src = if (maybe_dest_ptr_val) |dest_ptr_val| rs: { + if (maybe_src_ptr_val) |src_ptr_val| { + if (maybe_len_val) |len_val| { + _ = dest_ptr_val; + _ = src_ptr_val; + _ = len_val; + return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemcpy at comptime", .{}); + } else break :rs len_src; + } else break :rs src_src; + } else dest_src; + + try sema.requireRuntimeBlock(block, runtime_src); + _ = try block.addInst(.{ + .tag = .memcpy, + .data = .{ .pl_op = .{ + .operand = dest_ptr, + .payload = try sema.addExtra(Air.Bin{ + .lhs = src_ptr, + .rhs = len, + }), + } }, + }); } -fn zirMemset(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirMemset(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.Memset, inst_data.payload_index).data; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemset", .{}); + const dest_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const value_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const dest_ptr = sema.resolveInst(extra.dest); + const dest_ptr_ty = sema.typeOf(dest_ptr); + if (dest_ptr_ty.zigTypeTag() != .Pointer) { + return sema.mod.fail(&block.base, dest_src, "expected pointer, found '{}'", .{dest_ptr_ty}); + } + if (dest_ptr_ty.isConstPtr()) { + return sema.mod.fail(&block.base, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty}); + } + const elem_ty = dest_ptr_ty.elemType2(); + const value = try sema.coerce(block, elem_ty, sema.resolveInst(extra.byte), value_src); + const len = try sema.coerce(block, Type.initTag(.usize), sema.resolveInst(extra.byte_count), len_src); + + const maybe_dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr); + const maybe_len_val = try sema.resolveDefinedValue(block, len_src, len); + + const runtime_src = if (maybe_dest_ptr_val) |ptr_val| rs: { + if (maybe_len_val) |len_val| { + if (try sema.resolveMaybeUndefVal(block, value_src, value)) |val| { + _ = ptr_val; + _ = len_val; + _ = val; + return sema.mod.fail(&block.base, src, "TODO: Sema.zirMemset at comptime", .{}); + } else break :rs value_src; + } else break :rs len_src; + } else dest_src; + + try sema.requireRuntimeBlock(block, runtime_src); + _ = try block.addInst(.{ + .tag = .memset, + .data = .{ .pl_op = .{ + .operand = dest_ptr, + .payload = try sema.addExtra(Air.Bin{ + .lhs = value, + .rhs = len, + }), + } }, + }); } fn zirMinimum(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -10090,7 +10201,8 @@ fn coerceArrayPtrToMany( // The comptime Value representation is compatible with both types. return sema.addConstant(dest_type, val); } - return sema.mod.fail(&block.base, inst_src, "TODO implement coerceArrayPtrToMany runtime instruction", .{}); + try sema.requireRuntimeBlock(block, inst_src); + return sema.bitcast(block, dest_type, inst, inst_src); } fn analyzeDeclVal( diff --git a/src/codegen.zig b/src/codegen.zig index f812cbc5d4..102f8d4985 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -887,6 +887,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .cmpxchg_weak => try self.airCmpxchg(inst), .atomic_rmw => try self.airAtomicRmw(inst), .atomic_load => try self.airAtomicLoad(inst), + .memcpy => try self.airMemcpy(inst), + .memset => try self.airMemset(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -4883,6 +4885,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.fail("TODO implement airAtomicStore for {}", .{self.target.cpu.arch}); } + fn airMemset(self: *Self, inst: Air.Inst.Index) !void { + _ = inst; + return self.fail("TODO implement airMemset for {}", .{self.target.cpu.arch}); + } + + fn airMemcpy(self: *Self, inst: Air.Inst.Index) !void { + _ = inst; + return self.fail("TODO implement airMemcpy for {}", .{self.target.cpu.arch}); + } + fn resolveInst(self: *Self, inst: Air.Inst.Ref) InnerError!MCValue { // First section of indexes correspond to a set number of constant values. const ref_int = @enumToInt(inst); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a82f0e57f7..16b13db292 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -953,6 +953,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .cmpxchg_strong => try airCmpxchg(f, inst, "strong"), .atomic_rmw => try airAtomicRmw(f, inst), .atomic_load => try airAtomicLoad(f, inst), + .memset => try airMemset(f, inst), + .memcpy => try airMemcpy(f, inst), .int_to_float, .float_to_int, @@ -2005,8 +2007,12 @@ fn airAtomicRmw(f: *Function, inst: Air.Inst.Index) !CValue { fn airAtomicLoad(f: *Function, inst: Air.Inst.Index) !CValue { const atomic_load = f.air.instructions.items(.data)[inst].atomic_load; - const inst_ty = f.air.typeOfIndex(inst); const ptr = try f.resolveInst(atomic_load.ptr); + const ptr_ty = f.air.typeOf(atomic_load.ptr); + if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst)) + return CValue.none; + + const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); @@ -2036,6 +2042,44 @@ fn airAtomicStore(f: *Function, inst: Air.Inst.Index, order: [*:0]const u8) !CVa return local; } +fn airMemset(f: *Function, inst: Air.Inst.Index) !CValue { + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const extra = f.air.extraData(Air.Bin, pl_op.payload).data; + const dest_ptr = try f.resolveInst(pl_op.operand); + const value = try f.resolveInst(extra.lhs); + const len = try f.resolveInst(extra.rhs); + const writer = f.object.writer(); + + try writer.writeAll("memset("); + try f.writeCValue(writer, dest_ptr); + try writer.writeAll(", "); + try f.writeCValue(writer, value); + try writer.writeAll(", "); + try f.writeCValue(writer, len); + try writer.writeAll(");\n"); + + return CValue.none; +} + +fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue { + const pl_op = f.air.instructions.items(.data)[inst].pl_op; + const extra = f.air.extraData(Air.Bin, pl_op.payload).data; + const dest_ptr = try f.resolveInst(pl_op.operand); + const src_ptr = try f.resolveInst(extra.lhs); + const len = try f.resolveInst(extra.rhs); + const writer = f.object.writer(); + + try writer.writeAll("memcpy("); + try f.writeCValue(writer, dest_ptr); + try writer.writeAll(", "); + try f.writeCValue(writer, src_ptr); + try writer.writeAll(", "); + try f.writeCValue(writer, len); + try writer.writeAll(");\n"); + + return CValue.none; +} + fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { return switch (order) { .Unordered => "memory_order_relaxed", diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b2b87b97d8..b15834c963 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1279,6 +1279,8 @@ pub const FuncGen = struct { .fence => try self.airFence(inst), .atomic_rmw => try self.airAtomicRmw(inst), .atomic_load => try self.airAtomicLoad(inst), + .memset => try self.airMemset(inst), + .memcpy => try self.airMemcpy(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -2426,6 +2428,8 @@ pub const FuncGen = struct { const atomic_load = self.air.instructions.items(.data)[inst].atomic_load; const ptr = try self.resolveInst(atomic_load.ptr); const ptr_ty = self.air.typeOf(atomic_load.ptr); + if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) + return null; const ordering = toLlvmAtomicOrdering(atomic_load.order); const operand_ty = ptr_ty.elemType(); const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false); @@ -2468,6 +2472,55 @@ pub const FuncGen = struct { return null; } + fn airMemset(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const extra = self.air.extraData(Air.Bin, pl_op.payload).data; + const dest_ptr = try self.resolveInst(pl_op.operand); + const ptr_ty = self.air.typeOf(pl_op.operand); + const value = try self.resolveInst(extra.lhs); + const val_is_undef = if (self.air.value(extra.lhs)) |val| val.isUndef() else false; + const len = try self.resolveInst(extra.rhs); + const u8_llvm_ty = self.context.intType(8); + const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0); + const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, ""); + const fill_char = if (val_is_undef) u8_llvm_ty.constInt(0xaa, .False) else value; + const target = self.dg.module.getTarget(); + const dest_ptr_align = ptr_ty.ptrAlignment(target); + const memset = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align); + memset.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr())); + + if (val_is_undef and self.dg.module.comp.bin_file.options.valgrind) { + // TODO generate valgrind client request to mark byte range as undefined + // see gen_valgrind_undef() in codegen.cpp + } + return null; + } + + fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const extra = self.air.extraData(Air.Bin, pl_op.payload).data; + const dest_ptr = try self.resolveInst(pl_op.operand); + const dest_ptr_ty = self.air.typeOf(pl_op.operand); + const src_ptr = try self.resolveInst(extra.lhs); + const src_ptr_ty = self.air.typeOf(extra.lhs); + const len = try self.resolveInst(extra.rhs); + const u8_llvm_ty = self.context.intType(8); + const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0); + const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, ""); + const src_ptr_u8 = self.builder.buildBitCast(src_ptr, ptr_u8_llvm_ty, ""); + const is_volatile = src_ptr_ty.isVolatilePtr() or dest_ptr_ty.isVolatilePtr(); + const target = self.dg.module.getTarget(); + const memcpy = self.builder.buildMemCpy( + dest_ptr_u8, + dest_ptr_ty.ptrAlignment(target), + src_ptr_u8, + src_ptr_ty.ptrAlignment(target), + len, + ); + memcpy.setVolatile(llvm.Bool.fromBool(is_volatile)); + return null; + } + fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value { const id = llvm.lookupIntrinsicID(name.ptr, name.len); assert(id != 0); diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index c53ac08fdd..9d32682260 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -632,6 +632,25 @@ pub const Builder = opaque { DestTy: *const Type, Name: [*:0]const u8, ) *const Value; + + pub const buildMemSet = LLVMBuildMemSet; + extern fn LLVMBuildMemSet( + B: *const Builder, + Ptr: *const Value, + Val: *const Value, + Len: *const Value, + Align: c_uint, + ) *const Value; + + pub const buildMemCpy = LLVMBuildMemCpy; + extern fn LLVMBuildMemCpy( + B: *const Builder, + Dst: *const Value, + DstAlign: c_uint, + Src: *const Value, + SrcAlign: c_uint, + Size: *const Value, + ) *const Value; }; pub const IntPredicate = enum(c_uint) { diff --git a/src/link/C/zig.h b/src/link/C/zig.h index e19a138c1b..b34068d1f2 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -126,6 +126,7 @@ #define int128_t __int128 #define uint128_t unsigned __int128 ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t); +ZIG_EXTERN_C void *memset (void *, int, size_t); static inline uint8_t zig_addw_u8(uint8_t lhs, uint8_t rhs, uint8_t max) { uint8_t thresh = max - rhs; diff --git a/src/print_air.zig b/src/print_air.zig index a9ad993eb0..fa384baae0 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -202,6 +202,8 @@ const Writer = struct { .atomic_store_release => try w.writeAtomicStore(s, inst, .Release), .atomic_store_seq_cst => try w.writeAtomicStore(s, inst, .SeqCst), .atomic_rmw => try w.writeAtomicRmw(s, inst), + .memcpy => try w.writeMemcpy(s, inst), + .memset => try w.writeMemset(s, inst), } } @@ -322,6 +324,28 @@ const Writer = struct { try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) }); } + fn writeMemset(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const pl_op = w.air.instructions.items(.data)[inst].pl_op; + const extra = w.air.extraData(Air.Bin, pl_op.payload).data; + + try w.writeOperand(s, inst, 0, pl_op.operand); + try s.writeAll(", "); + try w.writeOperand(s, inst, 1, extra.lhs); + try s.writeAll(", "); + try w.writeOperand(s, inst, 2, extra.rhs); + } + + fn writeMemcpy(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + const pl_op = w.air.instructions.items(.data)[inst].pl_op; + const extra = w.air.extraData(Air.Bin, pl_op.payload).data; + + try w.writeOperand(s, inst, 0, pl_op.operand); + try s.writeAll(", "); + try w.writeOperand(s, inst, 1, extra.lhs); + try s.writeAll(", "); + try w.writeOperand(s, inst, 2, extra.rhs); + } + fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const val = w.air.values[ty_pl.payload]; diff --git a/src/print_zir.zig b/src/print_zir.zig index 9350fd0de3..6ae218ed22 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -210,8 +210,6 @@ const Writer = struct { .mul_add, .builtin_call, .field_parent_ptr, - .memcpy, - .memset, .builtin_async_call, => try self.writePlNode(stream, inst), @@ -222,6 +220,8 @@ const Writer = struct { .cmpxchg_strong, .cmpxchg_weak => try self.writeCmpxchg(stream, inst), .atomic_store => try self.writeAtomicStore(stream, inst), .atomic_rmw => try self.writeAtomicRmw(stream, inst), + .memcpy => try self.writeMemcpy(stream, inst), + .memset => try self.writeMemset(stream, inst), .struct_init_anon, .struct_init_anon_ref, @@ -692,6 +692,32 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writeMemcpy(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Memcpy, inst_data.payload_index).data; + + try self.writeInstRef(stream, extra.dest); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.source); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.byte_count); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + + fn writeMemset(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.Memset, inst_data.payload_index).data; + + try self.writeInstRef(stream, extra.dest); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.byte); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.byte_count); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writeStructInitAnon(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.StructInitAnon, inst_data.payload_index); diff --git a/src/type.zig b/src/type.zig index a2da252f61..0381111345 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2391,12 +2391,11 @@ pub const Type = extern union { }; } - /// Asserts the type is a pointer or array type. - pub fn elemType(self: Type) Type { - return switch (self.tag()) { - .vector => self.castTag(.vector).?.data.elem_type, - .array => self.castTag(.array).?.data.elem_type, - .array_sentinel => self.castTag(.array_sentinel).?.data.elem_type, + pub fn childType(ty: Type) Type { + return switch (ty.tag()) { + .vector => ty.castTag(.vector).?.data.elem_type, + .array => ty.castTag(.array).?.data.elem_type, + .array_sentinel => ty.castTag(.array_sentinel).?.data.elem_type, .single_const_pointer, .single_mut_pointer, .many_const_pointer, @@ -2405,7 +2404,7 @@ pub const Type = extern union { .c_mut_pointer, .const_slice, .mut_slice, - => self.castPointer().?.data, + => ty.castPointer().?.data, .array_u8, .array_u8_sentinel_0, @@ -2415,12 +2414,70 @@ pub const Type = extern union { => Type.initTag(.u8), .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), - .pointer => self.castTag(.pointer).?.data.pointee_type, + .pointer => ty.castTag(.pointer).?.data.pointee_type, else => unreachable, }; } + /// Asserts the type is a pointer or array type. + /// TODO this is deprecated in favor of `childType`. + pub const elemType = childType; + + /// For *[N]T, returns T. + /// For ?*T, returns T. + /// For ?*[N]T, returns T. + /// For ?[*]T, returns T. + /// For *T, returns T. + /// For [*]T, returns T. + pub fn elemType2(ty: Type) Type { + return switch (ty.tag()) { + .vector => ty.castTag(.vector).?.data.elem_type, + .array => ty.castTag(.array).?.data.elem_type, + .array_sentinel => ty.castTag(.array_sentinel).?.data.elem_type, + .many_const_pointer, + .many_mut_pointer, + .c_const_pointer, + .c_mut_pointer, + .const_slice, + .mut_slice, + => ty.castPointer().?.data, + + .single_const_pointer, + .single_mut_pointer, + => ty.castPointer().?.data.shallowElemType(), + + .array_u8, + .array_u8_sentinel_0, + .const_slice_u8, + .manyptr_u8, + .manyptr_const_u8, + => Type.initTag(.u8), + + .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int), + .pointer => { + const info = ty.castTag(.pointer).?.data; + const child_ty = info.pointee_type; + if (info.size == .One) { + return child_ty.shallowElemType(); + } else { + return child_ty; + } + }, + + // TODO handle optionals + + else => unreachable, + }; + } + + fn shallowElemType(child_ty: Type) Type { + return switch (child_ty.zigTypeTag()) { + .Array, .Vector => child_ty.childType(), + else => child_ty, + }; + } + /// Asserts that the type is an optional. /// Resulting `Type` will have inner memory referencing `buf`. pub fn optionalChild(self: Type, buf: *Payload.ElemType) Type { diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 517162c8d4..f6876e29ad 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -170,3 +170,21 @@ test "string concatenation" { test "array mult operator" { try expect(mem.eql(u8, "ab" ** 5, "ababababab")); } + +test "memcpy and memset intrinsics" { + try testMemcpyMemset(); + // TODO add comptime test coverage + //comptime try testMemcpyMemset(); +} + +fn testMemcpyMemset() !void { + var foo: [20]u8 = undefined; + var bar: [20]u8 = undefined; + + @memset(&foo, 'A', foo.len); + @memcpy(&bar, &foo, bar.len); + + try expect(bar[0] == 'A'); + try expect(bar[11] == 'A'); + try expect(bar[19] == 'A'); +} diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig index 5394e6fd14..9b3bf48366 100644 --- a/test/behavior/misc.zig +++ b/test/behavior/misc.zig @@ -5,16 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings; const mem = std.mem; const builtin = @import("builtin"); -test "memcpy and memset intrinsics" { - var foo: [20]u8 = undefined; - var bar: [20]u8 = undefined; - - @memset(&foo, 'A', foo.len); - @memcpy(&bar, &foo, bar.len); - - if (bar[11] != 'A') unreachable; -} - test "slicing" { var array: [20]i32 = undefined; From 15f55b2805541276f491d255f60f501c8cbd1191 Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Fri, 24 Sep 2021 20:09:31 +0200 Subject: [PATCH 117/160] os.flock: FreeBSD can return EOPNOTSUPP --- lib/std/fs/file.zig | 2 ++ lib/std/os.zig | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index fd0492f0bf..a71c9ae0d2 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -866,6 +866,7 @@ pub const File = struct { pub const LockError = error{ SystemResources, + FileLocksNotSupported, } || os.UnexpectedError; /// Blocks when an incompatible lock is held by another process. @@ -928,6 +929,7 @@ pub const File = struct { return os.flock(file.handle, os.LOCK.UN) catch |err| switch (err) { error.WouldBlock => unreachable, // unlocking can't block error.SystemResources => unreachable, // We are deallocating resources. + error.FileLocksNotSupported => unreachable, // We already got the lock. error.Unexpected => unreachable, // Resource deallocation must succeed. }; } diff --git a/lib/std/os.zig b/lib/std/os.zig index d85815dde0..a4fad9bc20 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -4501,8 +4501,12 @@ pub const FlockError = error{ /// The kernel ran out of memory for allocating file locks SystemResources, + + /// The underlying filesystem does not support file locks + FileLocksNotSupported, } || UnexpectedError; +/// Depending on the operating system `flock` may or may not interact with `fcntl` locks made by other processes. pub fn flock(fd: fd_t, operation: i32) FlockError!void { while (true) { const rc = system.flock(fd, operation); @@ -4513,6 +4517,7 @@ pub fn flock(fd: fd_t, operation: i32) FlockError!void { .INVAL => unreachable, // invalid parameters .NOLCK => return error.SystemResources, .AGAIN => return error.WouldBlock, // TODO: integrate with async instead of just returning an error + .OPNOTSUPP => return error.FileLocksNotSupported, else => |err| return unexpectedErrno(err), } } From 04366576ea4be4959b596ebff7041d17e18d08d8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 25 Sep 2021 17:52:50 -0700 Subject: [PATCH 118/160] stage2: implement `@sizeOf` for non-packed structs --- src/Sema.zig | 4 +--- src/type.zig | 16 +++++++++++++++- test/behavior/struct.zig | 21 +++++++++++++++++++++ test/behavior/struct_stage1.zig | 17 +---------------- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 41fabbfacd..1001b388a9 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6575,6 +6575,7 @@ fn zirSizeOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro const src = inst_data.src(); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_ty = try sema.resolveType(block, operand_src, inst_data.operand); + try sema.resolveTypeLayout(block, src, operand_ty); const target = sema.mod.getTarget(); const abi_size = switch (operand_ty.zigTypeTag()) { .Fn => unreachable, @@ -10846,9 +10847,6 @@ pub fn resolveTypeLayout( ty: Type, ) CompileError!void { switch (ty.zigTypeTag()) { - .Pointer => { - return sema.resolveTypeLayout(block, src, ty.elemType()); - }, .Struct => { const resolved_ty = try sema.resolveTypeFields(block, src, ty); const struct_obj = resolved_ty.castTag(.@"struct").?.data; diff --git a/src/type.zig b/src/type.zig index 0381111345..bd008e809a 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1765,7 +1765,21 @@ pub const Type = extern union { .@"struct" => { const s = self.castTag(.@"struct").?.data; assert(s.status == .have_layout); - @panic("TODO abiSize struct"); + const is_packed = s.layout == .Packed; + if (is_packed) @panic("TODO packed structs"); + var size: u64 = 0; + for (s.fields.values()) |field| { + const field_align = a: { + if (field.abi_align.tag() == .abi_align_default) { + break :a field.ty.abiAlignment(target); + } else { + break :a field.abi_align.toUnsignedInt(); + } + }; + size = std.mem.alignForwardGeneric(u64, size, field_align); + size += field.ty.abiSize(target); + } + return size; }, .enum_simple, .enum_full, .enum_nonexhaustive => { var buffer: Payload.Bits = undefined; diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 6f00b71057..e048848799 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -31,3 +31,24 @@ test "return empty struct instance" { fn returnEmptyStructInstance() StructWithNoFields { return empty_global_instance; } + +const StructFoo = struct { + a: i32, + b: bool, + c: f32, +}; +test "structs" { + var foo: StructFoo = undefined; + @memset(@ptrCast([*]u8, &foo), 0, @sizeOf(StructFoo)); + foo.a += 1; + foo.b = foo.a == 1; + try testFoo(foo); + testMutation(&foo); + try expect(foo.c == 100); +} +fn testFoo(foo: StructFoo) !void { + try expect(foo.b); +} +fn testMutation(foo: *StructFoo) void { + foo.c = 100; +} diff --git a/test/behavior/struct_stage1.zig b/test/behavior/struct_stage1.zig index 9f084ceb85..fd19b37661 100644 --- a/test/behavior/struct_stage1.zig +++ b/test/behavior/struct_stage1.zig @@ -30,26 +30,11 @@ const VoidStructFieldsFoo = struct { c: void, }; -test "structs" { - var foo: StructFoo = undefined; - @memset(@ptrCast([*]u8, &foo), 0, @sizeOf(StructFoo)); - foo.a += 1; - foo.b = foo.a == 1; - try testFoo(foo); - testMutation(&foo); - try expect(foo.c == 100); -} const StructFoo = struct { a: i32, b: bool, c: f32, }; -fn testFoo(foo: StructFoo) !void { - try expect(foo.b); -} -fn testMutation(foo: *StructFoo) void { - foo.c = 100; -} const Node = struct { val: Val, @@ -84,7 +69,7 @@ test "struct byval assign" { try expect(foo2.a == 1234); } -fn structInitializer() void { +test "struct initializer" { const val = Val{ .x = 42 }; try expect(val.x == 42); } From 1f2f9f05c254374044d8c30cce6f299d7a18da72 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 25 Sep 2021 22:18:43 -0700 Subject: [PATCH 119/160] stage2: implement zirCoerceResultPtr and remove Module.simplePtrType and Module.ptrType in favor of `Type.ptr`. --- src/Module.zig | 76 -------- src/Sema.zig | 306 ++++++++++++++++---------------- src/type.zig | 61 +++++-- test/behavior/struct.zig | 38 ++++ test/behavior/struct_stage1.zig | 29 +-- 5 files changed, 239 insertions(+), 271 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 278f8621d8..dbece09255 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4460,82 +4460,6 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) Co return error.AnalysisFail; } -pub fn simplePtrType( - arena: *Allocator, - elem_ty: Type, - mutable: bool, - size: std.builtin.TypeInfo.Pointer.Size, - @"addrspace": std.builtin.AddressSpace, -) Allocator.Error!Type { - return ptrType( - arena, - elem_ty, - null, - 0, - @"addrspace", - 0, - 0, - mutable, - false, - false, - size, - ); -} - -pub fn ptrType( - arena: *Allocator, - elem_ty: Type, - sentinel: ?Value, - @"align": u32, - @"addrspace": std.builtin.AddressSpace, - bit_offset: u16, - host_size: u16, - mutable: bool, - @"allowzero": bool, - @"volatile": bool, - size: std.builtin.TypeInfo.Pointer.Size, -) Allocator.Error!Type { - assert(host_size == 0 or bit_offset < host_size * 8); - - if (sentinel != null or @"align" != 0 or @"addrspace" != .generic or - bit_offset != 0 or host_size != 0 or @"allowzero" or @"volatile") - { - return Type.Tag.pointer.create(arena, .{ - .pointee_type = elem_ty, - .sentinel = sentinel, - .@"align" = @"align", - .@"addrspace" = @"addrspace", - .bit_offset = bit_offset, - .host_size = host_size, - .@"allowzero" = @"allowzero", - .mutable = mutable, - .@"volatile" = @"volatile", - .size = size, - }); - } - - if (!mutable and size == .Slice and elem_ty.eql(Type.initTag(.u8))) { - return Type.initTag(.const_slice_u8); - } - - // TODO stage1 type inference bug - const T = Type.Tag; - - const type_payload = try arena.create(Type.Payload.ElemType); - type_payload.* = .{ - .base = .{ - .tag = switch (size) { - .One => if (mutable) T.single_mut_pointer else T.single_const_pointer, - .Many => if (mutable) T.many_mut_pointer else T.many_const_pointer, - .C => if (mutable) T.c_mut_pointer else T.c_const_pointer, - .Slice => if (mutable) T.mut_slice else T.const_slice, - }, - }, - .data = elem_ty, - }; - return Type.initPayload(&type_payload.base); -} - pub fn optionalType(arena: *Allocator, child_type: Type) Allocator.Error!Type { switch (child_type.tag()) { .single_const_pointer => return Type.Tag.optional_single_const_pointer.create( diff --git a/src/Sema.zig b/src/Sema.zig index 1001b388a9..533252d682 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -979,10 +979,38 @@ fn zirBitcastResultPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) C } fn zirCoerceResultPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - _ = inst; const tracy = trace(@src()); defer tracy.end(); - return sema.mod.fail(&block.base, sema.src, "TODO implement zirCoerceResultPtr", .{}); + + const src: LazySrcLoc = sema.src; + const bin_inst = sema.code.instructions.items(.data)[inst].bin; + const pointee_ty = try sema.resolveType(block, src, bin_inst.lhs); + const ptr = sema.resolveInst(bin_inst.rhs); + + // Create a runtime bitcast instruction with exactly the type the pointer wants. + const ptr_ty = try Type.ptr(sema.arena, .{ + .pointee_type = pointee_ty, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); + try sema.requireRuntimeBlock(block, src); + const bitcasted_ptr = try block.addTyOp(.bitcast, ptr_ty, ptr); + + if (Air.refToIndex(ptr)) |ptr_inst| { + if (sema.air_instructions.items(.tag)[ptr_inst] == .constant) { + const air_datas = sema.air_instructions.items(.data); + const ptr_val = sema.air_values.items[air_datas[ptr_inst].ty_pl.payload]; + if (ptr_val.castTag(.inferred_alloc)) |inferred_alloc| { + // Add the stored instruction to the set we will use to resolve peer types + // for the inferred allocation. + // This instruction will not make it to codegen; it is only to participate + // in the `stored_inst_list` of the `inferred_alloc`. + const operand = try block.addTyOp(.bitcast, pointee_ty, .void_value); + try inferred_alloc.data.stored_inst_list.append(sema.arena, operand); + } + } + } + + return bitcasted_ptr; } pub fn analyzeStructDecl( @@ -1427,13 +1455,10 @@ fn zirRetPtr( return sema.analyzeComptimeAlloc(block, sema.fn_ret_ty); } - const ptr_type = try Module.simplePtrType( - sema.arena, - sema.fn_ret_ty, - true, - .One, - target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - ); + const ptr_type = try Type.ptr(sema.arena, .{ + .pointee_type = sema.fn_ret_ty, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); return block.addTy(.alloc, ptr_type); } @@ -1581,13 +1606,10 @@ fn zirAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node }; const var_decl_src = inst_data.src(); const var_type = try sema.resolveType(block, ty_src, inst_data.operand); - const ptr_type = try Module.simplePtrType( - sema.arena, - var_type, - true, - .One, - target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - ); + const ptr_type = try Type.ptr(sema.arena, .{ + .pointee_type = var_type, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); try sema.requireRuntimeBlock(block, var_decl_src); return block.addTy(.alloc, ptr_type); } @@ -1604,13 +1626,10 @@ fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.analyzeComptimeAlloc(block, var_type); } try sema.validateVarType(block, ty_src, var_type); - const ptr_type = try Module.simplePtrType( - sema.arena, - var_type, - true, - .One, - target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - ); + const ptr_type = try Type.ptr(sema.arena, .{ + .pointee_type = var_type, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); try sema.requireRuntimeBlock(block, var_decl_src); return block.addTy(.alloc, ptr_type); } @@ -1670,13 +1689,10 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde try sema.mod.declareDeclDependency(sema.owner_decl, decl); const final_elem_ty = try decl.ty.copy(sema.arena); - const final_ptr_ty = try Module.simplePtrType( - sema.arena, - final_elem_ty, - true, - .One, - target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - ); + const final_ptr_ty = try Type.ptr(sema.arena, .{ + .pointee_type = final_elem_ty, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); const final_ptr_ty_inst = try sema.addType(final_ptr_ty); sema.air_instructions.items(.data)[ptr_inst].ty_pl.ty = final_ptr_ty_inst; @@ -1698,13 +1714,10 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde try sema.validateVarType(block, ty_src, final_elem_ty); } // Change it to a normal alloc. - const final_ptr_ty = try Module.simplePtrType( - sema.arena, - final_elem_ty, - true, - .One, - target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - ); + const final_ptr_ty = try Type.ptr(sema.arena, .{ + .pointee_type = final_elem_ty, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); sema.air_instructions.set(ptr_inst, .{ .tag = .alloc, .data = .{ .ty = final_ptr_ty }, @@ -1858,14 +1871,11 @@ fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co } const ptr = sema.resolveInst(bin_inst.lhs); const value = sema.resolveInst(bin_inst.rhs); - const ptr_ty = try Module.simplePtrType( - sema.arena, - sema.typeOf(value), - true, - .One, + const ptr_ty = try Type.ptr(sema.arena, .{ + .pointee_type = sema.typeOf(value), // TODO figure out which address space is appropriate here - target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - ); + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); // TODO detect when this store should be done at compile-time. For example, // if expressions should force it when the condition is compile-time known. const src: LazySrcLoc = .unneeded; @@ -1912,14 +1922,10 @@ fn zirStoreToInferredPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) // for the inferred allocation. try inferred_alloc.data.stored_inst_list.append(sema.arena, operand); // Create a runtime bitcast instruction with exactly the type the pointer wants. - const ptr_ty = try Module.simplePtrType( - sema.arena, - operand_ty, - true, - .One, - // TODO figure out which address space is appropriate here - target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - ); + const ptr_ty = try Type.ptr(sema.arena, .{ + .pointee_type = operand_ty, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); const bitcasted_ptr = try block.addTyOp(.bitcast, ptr_ty, ptr); return sema.storePtr(block, src, bitcasted_ptr, operand); } @@ -3845,13 +3851,11 @@ fn zirOptionalPayloadPtr( } const child_type = try opt_type.optionalChildAlloc(sema.arena); - const child_pointer = try Module.simplePtrType( - sema.arena, - child_type, - !optional_ptr_ty.isConstPtr(), - .One, - optional_ptr_ty.ptrAddressSpace(), - ); + const child_pointer = try Type.ptr(sema.arena, .{ + .pointee_type = child_type, + .mutable = !optional_ptr_ty.isConstPtr(), + .@"addrspace" = optional_ptr_ty.ptrAddressSpace(), + }); if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| { if (try pointer_val.pointerDeref(sema.arena)) |val| { @@ -3966,13 +3970,11 @@ fn zirErrUnionPayloadPtr( return sema.mod.fail(&block.base, src, "expected error union type, found {}", .{operand_ty.elemType()}); const payload_ty = operand_ty.elemType().errorUnionPayload(); - const operand_pointer_ty = try Module.simplePtrType( - sema.arena, - payload_ty, - !operand_ty.isConstPtr(), - .One, - operand_ty.ptrAddressSpace(), - ); + const operand_pointer_ty = try Type.ptr(sema.arena, .{ + .pointee_type = payload_ty, + .mutable = !operand_ty.isConstPtr(), + .@"addrspace" = operand_ty.ptrAddressSpace(), + }); if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { if (try pointer_val.pointerDeref(sema.arena)) |val| { @@ -7306,19 +7308,14 @@ fn zirPtrTypeSimple(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp const inst_data = sema.code.instructions.items(.data)[inst].ptr_type_simple; const elem_type = try sema.resolveType(block, .unneeded, inst_data.elem_type); - const ty = try Module.ptrType( - sema.arena, - elem_type, - null, - 0, - .generic, - 0, - 0, - inst_data.is_mutable, - inst_data.is_allowzero, - inst_data.is_volatile, - inst_data.size, - ); + const ty = try Type.ptr(sema.arena, .{ + .pointee_type = elem_type, + .@"addrspace" = .generic, + .mutable = inst_data.is_mutable, + .@"allowzero" = inst_data.is_allowzero, + .@"volatile" = inst_data.is_volatile, + .size = inst_data.size, + }); return sema.addType(ty); } @@ -7367,19 +7364,18 @@ fn zirPtrType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const elem_type = try sema.resolveType(block, .unneeded, extra.data.elem_type); - const ty = try Module.ptrType( - sema.arena, - elem_type, - sentinel, - abi_align, - address_space, - bit_start, - bit_end, - inst_data.flags.is_mutable, - inst_data.flags.is_allowzero, - inst_data.flags.is_volatile, - inst_data.size, - ); + const ty = try Type.ptr(sema.arena, .{ + .pointee_type = elem_type, + .sentinel = sentinel, + .@"align" = abi_align, + .@"addrspace" = address_space, + .bit_offset = bit_start, + .host_size = bit_end, + .mutable = inst_data.flags.is_mutable, + .@"allowzero" = inst_data.flags.is_allowzero, + .@"volatile" = inst_data.flags.is_volatile, + .size = inst_data.size, + }); return sema.addType(ty); } @@ -8456,19 +8452,15 @@ fn zirMemcpy(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro }); } const src_ptr_info = uncasted_src_ptr_ty.ptrInfo().data; - const wanted_src_ptr_ty = try Module.ptrType( - sema.arena, - dest_ptr_ty.elemType2(), - null, - src_ptr_info.@"align", - src_ptr_info.@"addrspace", - 0, - 0, - false, - src_ptr_info.@"allowzero", - src_ptr_info.@"volatile", - .Many, - ); + const wanted_src_ptr_ty = try Type.ptr(sema.arena, .{ + .pointee_type = dest_ptr_ty.elemType2(), + .@"align" = src_ptr_info.@"align", + .@"addrspace" = src_ptr_info.@"addrspace", + .mutable = false, + .@"allowzero" = src_ptr_info.@"allowzero", + .@"volatile" = src_ptr_info.@"volatile", + .size = .Many, + }); const src_ptr = try sema.coerce(block, wanted_src_ptr_ty, uncasted_src_ptr, src_src); const len = try sema.coerce(block, Type.initTag(.usize), sema.resolveInst(extra.byte_count), len_src); @@ -8922,13 +8914,10 @@ fn panicWithMsg( const panic_fn = try sema.getBuiltin(block, src, "panic"); const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace"); const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty); - const ptr_stack_trace_ty = try Module.simplePtrType( - arena, - stack_trace_ty, - true, - .One, - target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), // TODO might need a place that is more dynamic - ); + const ptr_stack_trace_ty = try Type.ptr(arena, .{ + .pointee_type = stack_trace_ty, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), // TODO might need a place that is more dynamic + }); const null_stack_trace = try sema.addConstant( try Module.optionalType(arena, ptr_stack_trace_ty), Value.initTag(.null_value), @@ -9407,13 +9396,11 @@ fn structFieldPtr( const field_index = struct_obj.fields.getIndex(field_name) orelse return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); const field = struct_obj.fields.values()[field_index]; - const ptr_field_ty = try Module.simplePtrType( - arena, - field.ty, - struct_ptr_ty.ptrIsMutable(), - .One, - struct_ptr_ty.ptrAddressSpace(), - ); + const ptr_field_ty = try Type.ptr(arena, .{ + .pointee_type = field.ty, + .mutable = struct_ptr_ty.ptrIsMutable(), + .@"addrspace" = struct_ptr_ty.ptrAddressSpace(), + }); if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| { return sema.addConstant( @@ -9512,13 +9499,11 @@ fn unionFieldPtr( return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name); const field = union_obj.fields.values()[field_index]; - const ptr_field_ty = try Module.simplePtrType( - arena, - field.ty, - union_ptr_ty.ptrIsMutable(), - .One, - union_ptr_ty.ptrAddressSpace(), - ); + const ptr_field_ty = try Type.ptr(arena, .{ + .pointee_type = field.ty, + .mutable = union_ptr_ty.ptrIsMutable(), + .@"addrspace" = union_ptr_ty.ptrAddressSpace(), + }); if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| { // TODO detect inactive union field and emit compile error @@ -9694,13 +9679,11 @@ fn elemPtrArray( ) CompileError!Air.Inst.Ref { const array_ptr_ty = sema.typeOf(array_ptr); const pointee_type = array_ptr_ty.elemType().elemType(); - const result_ty = try Module.simplePtrType( - sema.arena, - pointee_type, - array_ptr_ty.ptrIsMutable(), - .One, - array_ptr_ty.ptrAddressSpace(), - ); + const result_ty = try Type.ptr(sema.arena, .{ + .pointee_type = pointee_type, + .mutable = array_ptr_ty.ptrIsMutable(), + .@"addrspace" = array_ptr_ty.ptrAddressSpace(), + }); if (try sema.resolveDefinedValue(block, src, array_ptr)) |array_ptr_val| { if (try sema.resolveDefinedValue(block, elem_index_src, elem_index)) |index_val| { @@ -10243,11 +10226,19 @@ fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { const decl_tv = try decl.typedValue(); if (decl_tv.val.castTag(.variable)) |payload| { const variable = payload.data; - const ty = try Module.simplePtrType(sema.arena, decl_tv.ty, variable.is_mutable, .One, decl.@"addrspace"); + const ty = try Type.ptr(sema.arena, .{ + .pointee_type = decl_tv.ty, + .mutable = variable.is_mutable, + .@"addrspace" = decl.@"addrspace", + }); return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl)); } return sema.addConstant( - try Module.simplePtrType(sema.arena, decl_tv.ty, false, .One, decl.@"addrspace"), + try Type.ptr(sema.arena, .{ + .pointee_type = decl_tv.ty, + .mutable = false, + .@"addrspace" = decl.@"addrspace", + }), try Value.Tag.decl_ref.create(sema.arena, decl), ); } @@ -10271,8 +10262,15 @@ fn analyzeRef( try sema.requireRuntimeBlock(block, src); const address_space = target_util.defaultAddressSpace(sema.mod.getTarget(), .local); - const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One, address_space); - const mut_ptr_type = try Module.simplePtrType(sema.arena, operand_ty, true, .One, address_space); + const ptr_type = try Type.ptr(sema.arena, .{ + .pointee_type = operand_ty, + .mutable = false, + .@"addrspace" = address_space, + }); + const mut_ptr_type = try Type.ptr(sema.arena, .{ + .pointee_type = operand_ty, + .@"addrspace" = address_space, + }); const alloc = try block.addTy(.alloc, mut_ptr_type); try sema.storePtr(block, src, alloc, operand); @@ -10428,19 +10426,16 @@ fn analyzeSlice( } } } - const return_type = try Module.ptrType( - sema.arena, - return_elem_type, - if (end_opt == .none) slice_sentinel else null, - 0, // TODO alignment - if (ptr_child.zigTypeTag() == .Pointer) ptr_child.ptrAddressSpace() else .generic, - 0, - 0, - !ptr_child.isConstPtr(), - ptr_child.isAllowzeroPtr(), - ptr_child.isVolatilePtr(), - return_ptr_size, - ); + const return_type = try Type.ptr(sema.arena, .{ + .pointee_type = return_elem_type, + .sentinel = if (end_opt == .none) slice_sentinel else null, + .@"align" = 0, // TODO alignment + .@"addrspace" = if (ptr_child.zigTypeTag() == .Pointer) ptr_child.ptrAddressSpace() else .generic, + .mutable = !ptr_child.isConstPtr(), + .@"allowzero" = ptr_child.isAllowzeroPtr(), + .@"volatile" = ptr_child.isVolatilePtr(), + .size = return_ptr_size, + }); _ = return_type; return sema.mod.fail(&block.base, src, "TODO implement analysis of slice", .{}); @@ -11626,13 +11621,10 @@ fn analyzeComptimeAlloc( block: *Scope.Block, var_type: Type, ) CompileError!Air.Inst.Ref { - const ptr_type = try Module.simplePtrType( - sema.arena, - var_type, - true, - .One, - target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), - ); + const ptr_type = try Type.ptr(sema.arena, .{ + .pointee_type = var_type, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), + }); var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); diff --git a/src/type.zig b/src/type.zig index bd008e809a..48c65c1008 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3652,12 +3652,12 @@ pub const Type = extern union { } pub fn create(comptime t: Tag, ally: *Allocator, data: Data(t)) error{OutOfMemory}!file_struct.Type { - const ptr = try ally.create(t.Type()); - ptr.* = .{ + const p = try ally.create(t.Type()); + p.* = .{ .base = .{ .tag = t }, .data = data, }; - return file_struct.Type{ .ptr_otherwise = &ptr.base }; + return file_struct.Type{ .ptr_otherwise = &p.base }; } pub fn Data(comptime t: Tag) type { @@ -3747,19 +3747,23 @@ pub const Type = extern union { pub const base_tag = Tag.pointer; base: Payload = Payload{ .tag = base_tag }, - data: struct { + data: Data, + + pub const Data = struct { pointee_type: Type, - sentinel: ?Value, + sentinel: ?Value = null, /// If zero use pointee_type.AbiAlign() - @"align": u32, + @"align": u32 = 0, + /// See src/target.zig defaultAddressSpace function for how to obtain + /// an appropriate value for this field. @"addrspace": std.builtin.AddressSpace, - bit_offset: u16, - host_size: u16, - @"allowzero": bool, - mutable: bool, - @"volatile": bool, - size: std.builtin.TypeInfo.Pointer.Size, - }, + bit_offset: u16 = 0, + host_size: u16 = 0, + @"allowzero": bool = false, + mutable: bool = true, // TODO change this to const, not mutable + @"volatile": bool = false, + size: std.builtin.TypeInfo.Pointer.Size = .One, + }; }; pub const ErrorUnion = struct { @@ -3815,6 +3819,37 @@ pub const Type = extern union { data: *Module.EnumSimple, }; }; + + pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type { + assert(d.host_size == 0 or d.bit_offset < d.host_size * 8); + + if (d.sentinel != null or d.@"align" != 0 or d.@"addrspace" != .generic or + d.bit_offset != 0 or d.host_size != 0 or d.@"allowzero" or d.@"volatile") + { + return Type.Tag.pointer.create(arena, d); + } + + if (!d.mutable and d.size == .Slice and d.pointee_type.eql(Type.initTag(.u8))) { + return Type.initTag(.const_slice_u8); + } + + // TODO stage1 type inference bug + const T = Type.Tag; + + const type_payload = try arena.create(Type.Payload.ElemType); + type_payload.* = .{ + .base = .{ + .tag = switch (d.size) { + .One => if (d.mutable) T.single_mut_pointer else T.single_const_pointer, + .Many => if (d.mutable) T.many_mut_pointer else T.many_const_pointer, + .C => if (d.mutable) T.c_mut_pointer else T.c_const_pointer, + .Slice => if (d.mutable) T.mut_slice else T.const_slice, + }, + }, + .data = d.pointee_type, + }; + return Type.initPayload(&type_payload.base); + } }; pub const CType = enum { diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index e048848799..2dde3c930d 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -52,3 +52,41 @@ fn testFoo(foo: StructFoo) !void { fn testMutation(foo: *StructFoo) void { foo.c = 100; } + +test "struct byval assign" { + var foo1: StructFoo = undefined; + var foo2: StructFoo = undefined; + + foo1.a = 1234; + foo2.a = 0; + try expect(foo2.a == 0); + foo2 = foo1; + try expect(foo2.a == 1234); +} + +const Node = struct { + val: Val, + next: *Node, +}; + +const Val = struct { + x: i32, +}; + +test "struct initializer" { + const val = Val{ .x = 42 }; + try expect(val.x == 42); +} + +const MemberFnTestFoo = struct { + x: i32, + fn member(foo: MemberFnTestFoo) i32 { + return foo.x; + } +}; + +test "call member function directly" { + const instance = MemberFnTestFoo{ .x = 1234 }; + const result = MemberFnTestFoo.member(instance); + try expect(result == 1234); +} diff --git a/test/behavior/struct_stage1.zig b/test/behavior/struct_stage1.zig index fd19b37661..b5394afd50 100644 --- a/test/behavior/struct_stage1.zig +++ b/test/behavior/struct_stage1.zig @@ -5,6 +5,7 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectEqualSlices = std.testing.expectEqualSlices; const maxInt = std.math.maxInt; + top_level_field: i32, test "top level fields" { @@ -58,22 +59,6 @@ test "struct point to self" { try expect(node.next.next.next.val.x == 1); } -test "struct byval assign" { - var foo1: StructFoo = undefined; - var foo2: StructFoo = undefined; - - foo1.a = 1234; - foo2.a = 0; - try expect(foo2.a == 0); - foo2 = foo1; - try expect(foo2.a == 1234); -} - -test "struct initializer" { - const val = Val{ .x = 42 }; - try expect(val.x == 42); -} - test "fn call of struct field" { const Foo = struct { ptr: fn () i32, @@ -91,22 +76,16 @@ test "fn call of struct field" { try expect(S.callStructField(Foo{ .ptr = S.aFunc }) == 13); } -test "store member function in variable" { - const instance = MemberFnTestFoo{ .x = 1234 }; - const memberFn = MemberFnTestFoo.member; - const result = memberFn(instance); - try expect(result == 1234); -} const MemberFnTestFoo = struct { x: i32, fn member(foo: MemberFnTestFoo) i32 { return foo.x; } }; - -test "call member function directly" { +test "store member function in variable" { const instance = MemberFnTestFoo{ .x = 1234 }; - const result = MemberFnTestFoo.member(instance); + const memberFn = MemberFnTestFoo.member; + const result = memberFn(instance); try expect(result == 1234); } From 25266d08046df6032007b46346faf01a2f40ef31 Mon Sep 17 00:00:00 2001 From: "Mr. Paul" Date: Mon, 27 Sep 2021 09:34:59 +0700 Subject: [PATCH 120/160] Langref: fix HTML escaped symbol WASM JavaScript code example docgen HTML escapes characters inside of `syntax_block`s. This commit replaces the escaped greater than with the `>` character. No other occurrences were found. Fixes #9840 --- 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 561065bc0d..a5dfa5c927 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -10543,8 +10543,8 @@ const typedArray = new Uint8Array(source); WebAssembly.instantiate(typedArray, { env: { - print: (result) => { console.log(`The result is ${result}`); } - }}).then(result => { + print: (result) => { console.log(`The result is ${result}`); } + }}).then(result => { const add = result.instance.exports.add; add(1, 2); });{#end_syntax_block#} From c0aa4a1a42b3e0d312bd274799be67d60a1c0238 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 27 Sep 2021 19:48:42 -0700 Subject: [PATCH 121/160] stage2: implement basic unions * AIR instructions struct_field_ptr and related functions now are also emitted by the frontend for unions. Backends must inspect the type of the pointer operand to lower the instructions correctly. - These will be renamed to `agg_field_ptr` (short for "aggregate") in the future. * Introduce the new `set_union_tag` AIR instruction. * Introduce `Module.EnumNumbered` and associated `Type` methods. This is for enums which have no decls, but do have the possibility of overriding the integer tag type and tag values. * Sema: Implement support for union tag types in both the auto-generated and explicitly-provided cases, as well as explicitly provided enum tag values in union declarations. * LLVM backend: implement lowering union types, union field pointer instructions, and the new `set_union_tag` instruction. --- src/Air.zig | 14 +- src/Liveness.zig | 1 + src/Module.zig | 107 ++++++++++- src/Sema.zig | 320 +++++++++++++++++++++++++++------ src/codegen.zig | 9 + src/codegen/c.zig | 16 ++ src/codegen/llvm.zig | 80 ++++++++- src/print_air.zig | 1 + src/type.zig | 170 +++++++++--------- test/behavior/union.zig | 12 ++ test/behavior/union_stage1.zig | 7 - 11 files changed, 576 insertions(+), 161 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index 4341271f3a..40070dccfb 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -270,19 +270,26 @@ pub const Inst = struct { /// wrap from E to E!T /// Uses the `ty_op` field. wrap_errunion_err, - /// Given a pointer to a struct and a field index, returns a pointer to the field. + /// Given a pointer to a struct or union and a field index, returns a pointer to the field. /// Uses the `ty_pl` field, payload is `StructField`. + /// TODO rename to `agg_field_ptr`. struct_field_ptr, - /// Given a pointer to a struct, returns a pointer to the field. + /// Given a pointer to a struct or union, returns a pointer to the field. /// The field index is the number at the end of the name. /// Uses `ty_op` field. + /// TODO rename to `agg_field_ptr_index_X` struct_field_ptr_index_0, struct_field_ptr_index_1, struct_field_ptr_index_2, struct_field_ptr_index_3, - /// Given a byval struct and a field index, returns the field byval. + /// Given a byval struct or union and a field index, returns the field byval. /// Uses the `ty_pl` field, payload is `StructField`. + /// TODO rename to `agg_field_val` struct_field_val, + /// Given a pointer to a tagged union, set its tag to the provided value. + /// Result type is always void. + /// Uses the `bin_op` field. LHS is union pointer, RHS is new tag value. + set_union_tag, /// Given a slice value, return the length. /// Result type is always usize. /// Uses the `ty_op` field. @@ -643,6 +650,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .atomic_store_seq_cst, .memset, .memcpy, + .set_union_tag, => return Type.initTag(.void), .ptrtoint, diff --git a/src/Liveness.zig b/src/Liveness.zig index 42ab1ab351..9a7126d135 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -256,6 +256,7 @@ fn analyzeInst( .atomic_store_monotonic, .atomic_store_release, .atomic_store_seq_cst, + .set_union_tag, => { const o = inst_datas[inst].bin_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); diff --git a/src/Module.zig b/src/Module.zig index dbece09255..83bbbb6366 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -859,6 +859,36 @@ pub const EnumSimple = struct { } }; +/// Represents the data that an enum declaration provides, when there are no +/// declarations. However an integer tag type is provided, and the enum tag values +/// are explicitly provided. +pub const EnumNumbered = struct { + /// The Decl that corresponds to the enum itself. + owner_decl: *Decl, + /// An integer type which is used for the numerical value of the enum. + /// Whether zig chooses this type or the user specifies it, it is stored here. + tag_ty: Type, + /// Set of field names in declaration order. + fields: NameMap, + /// Maps integer tag value to field index. + /// Entries are in declaration order, same as `fields`. + /// If this hash map is empty, it means the enum tags are auto-numbered. + values: ValueMap, + /// Offset from `owner_decl`, points to the enum decl AST node. + node_offset: i32, + + pub const NameMap = EnumFull.NameMap; + pub const ValueMap = EnumFull.ValueMap; + + pub fn srcLoc(self: EnumNumbered) SrcLoc { + return .{ + .file_scope = self.owner_decl.getFileScope(), + .parent_decl_node = self.owner_decl.src_node, + .lazy = .{ .node_offset = self.node_offset }, + }; + } +}; + /// Represents the data that an enum declaration provides, when there is /// at least one tag value explicitly specified, or at least one declaration. pub const EnumFull = struct { @@ -868,16 +898,17 @@ pub const EnumFull = struct { /// Whether zig chooses this type or the user specifies it, it is stored here. tag_ty: Type, /// Set of field names in declaration order. - fields: std.StringArrayHashMapUnmanaged(void), + fields: NameMap, /// Maps integer tag value to field index. /// Entries are in declaration order, same as `fields`. /// If this hash map is empty, it means the enum tags are auto-numbered. values: ValueMap, - /// Represents the declarations inside this struct. + /// Represents the declarations inside this enum. namespace: Scope.Namespace, /// Offset from `owner_decl`, points to the enum decl AST node. node_offset: i32, + pub const NameMap = std.StringArrayHashMapUnmanaged(void); pub const ValueMap = std.ArrayHashMapUnmanaged(Value, void, Value.ArrayHashContext, false); pub fn srcLoc(self: EnumFull) SrcLoc { @@ -933,6 +964,44 @@ pub const Union = struct { .lazy = .{ .node_offset = self.node_offset }, }; } + + pub fn haveFieldTypes(u: Union) bool { + return switch (u.status) { + .none, + .field_types_wip, + => false, + .have_field_types, + .layout_wip, + .have_layout, + => true, + }; + } + + pub fn onlyTagHasCodegenBits(u: Union) bool { + assert(u.haveFieldTypes()); + for (u.fields.values()) |field| { + if (field.ty.hasCodeGenBits()) return false; + } + return true; + } + + pub fn mostAlignedField(u: Union, target: Target) u32 { + assert(u.haveFieldTypes()); + var most_alignment: u64 = 0; + var most_index: usize = undefined; + for (u.fields.values()) |field, i| { + if (!field.ty.hasCodeGenBits()) continue; + const field_align = if (field.abi_align.tag() == .abi_align_default) + field.ty.abiAlignment(target) + else + field.abi_align.toUnsignedInt(); + if (field_align > most_alignment) { + most_alignment = field_align; + most_index = i; + } + } + return @intCast(u32, most_index); + } }; /// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. @@ -1543,6 +1612,40 @@ pub const Scope = struct { }); } + pub fn addStructFieldPtr( + block: *Block, + struct_ptr: Air.Inst.Ref, + field_index: u32, + ptr_field_ty: Type, + ) !Air.Inst.Ref { + const ty = try block.sema.addType(ptr_field_ty); + const tag: Air.Inst.Tag = switch (field_index) { + 0 => .struct_field_ptr_index_0, + 1 => .struct_field_ptr_index_1, + 2 => .struct_field_ptr_index_2, + 3 => .struct_field_ptr_index_3, + else => { + return block.addInst(.{ + .tag = .struct_field_ptr, + .data = .{ .ty_pl = .{ + .ty = ty, + .payload = try block.sema.addExtra(Air.StructField{ + .struct_operand = struct_ptr, + .field_index = @intCast(u32, field_index), + }), + } }, + }); + }, + }; + return block.addInst(.{ + .tag = tag, + .data = .{ .ty_op = .{ + .ty = ty, + .operand = struct_ptr, + } }, + }); + } + pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref { return Air.indexToRef(try block.addInstAsIndex(inst)); } diff --git a/src/Sema.zig b/src/Sema.zig index 533252d682..f076389797 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1625,7 +1625,7 @@ fn zirAllocMut(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr if (block.is_comptime) { return sema.analyzeComptimeAlloc(block, var_type); } - try sema.validateVarType(block, ty_src, var_type); + try sema.validateVarType(block, ty_src, var_type, false); const ptr_type = try Type.ptr(sema.arena, .{ .pointee_type = var_type, .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), @@ -1711,7 +1711,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Inde const peer_inst_list = inferred_alloc.data.stored_inst_list.items; const final_elem_ty = try sema.resolvePeerTypes(block, ty_src, peer_inst_list, .none); if (var_is_mut) { - try sema.validateVarType(block, ty_src, final_elem_ty); + try sema.validateVarType(block, ty_src, final_elem_ty, false); } // Change it to a normal alloc. const final_ptr_ty = try Type.ptr(sema.arena, .{ @@ -1730,19 +1730,82 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Ind const tracy = trace(@src()); defer tracy.end(); - const gpa = sema.gpa; - const mod = sema.mod; const validate_inst = sema.code.instructions.items(.data)[inst].pl_node; - const struct_init_src = validate_inst.src(); + const init_src = validate_inst.src(); const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index); const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len]; + const field_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node; + const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; + const object_ptr = sema.resolveInst(field_ptr_extra.lhs); + const agg_ty = sema.typeOf(object_ptr).elemType(); + switch (agg_ty.zigTypeTag()) { + .Struct => return sema.validateStructInitPtr( + block, + agg_ty.castTag(.@"struct").?.data, + init_src, + instrs, + ), + .Union => return sema.validateUnionInitPtr( + block, + agg_ty.cast(Type.Payload.Union).?.data, + init_src, + instrs, + object_ptr, + ), + else => unreachable, + } +} - const struct_obj: *Module.Struct = s: { - const field_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node; - const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; - const object_ptr = sema.resolveInst(field_ptr_extra.lhs); - break :s sema.typeOf(object_ptr).elemType().castTag(.@"struct").?.data; - }; +fn validateUnionInitPtr( + sema: *Sema, + block: *Scope.Block, + union_obj: *Module.Union, + init_src: LazySrcLoc, + instrs: []const Zir.Inst.Index, + union_ptr: Air.Inst.Ref, +) CompileError!void { + const mod = sema.mod; + + if (instrs.len != 1) { + // TODO add note for other field + // TODO add note for union declared here + return mod.fail(&block.base, init_src, "only one union field can be active at once", .{}); + } + + const field_ptr = instrs[0]; + const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node; + const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node }; + const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; + const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start); + const field_index_big = union_obj.fields.getIndex(field_name) orelse + return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); + const field_index = @intCast(u32, field_index_big); + + // TODO here we need to go back and see if we need to convert the union + // to a comptime-known value. This will involve editing the AIR code we have + // generated so far - in particular deleting some runtime pointer bitcast + // instructions which are not actually needed if the initialization expression + // ends up being comptime-known. + + // Otherwise, we set the new union tag now. + const new_tag = try sema.addConstant( + union_obj.tag_ty, + try Value.Tag.enum_field_index.create(sema.arena, field_index), + ); + + try sema.requireRuntimeBlock(block, init_src); + _ = try block.addBinOp(.set_union_tag, union_ptr, new_tag); +} + +fn validateStructInitPtr( + sema: *Sema, + block: *Scope.Block, + struct_obj: *Module.Struct, + init_src: LazySrcLoc, + instrs: []const Zir.Inst.Index, +) CompileError!void { + const gpa = sema.gpa; + const mod = sema.mod; // Maps field index to field_ptr index of where it was already initialized. const found_fields = try gpa.alloc(Zir.Inst.Index, struct_obj.fields.count()); @@ -1781,9 +1844,9 @@ fn zirValidateStructInitPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Ind const template = "missing struct field: {s}"; const args = .{field_name}; if (root_msg) |msg| { - try mod.errNote(&block.base, struct_init_src, msg, template, args); + try mod.errNote(&block.base, init_src, msg, template, args); } else { - root_msg = try mod.errMsg(&block.base, struct_init_src, template, args); + root_msg = try mod.errMsg(&block.base, init_src, template, args); } } if (root_msg) |msg| { @@ -8037,7 +8100,7 @@ fn checkAtomicOperandType( const max_atomic_bits = target_util.largestAtomicBits(target); const int_ty = switch (ty.zigTypeTag()) { .Int => ty, - .Enum => ty.enumTagType(&buffer), + .Enum => ty.intTagType(&buffer), .Float => { const bit_count = ty.floatBits(target); if (bit_count > max_atomic_bits) { @@ -8621,11 +8684,7 @@ fn zirVarExtended( return sema.failWithNeededComptime(block, init_src); } else Value.initTag(.unreachable_value); - if (!var_ty.isValidVarType(small.is_extern)) { - return sema.mod.fail(&block.base, mut_src, "variable of type '{}' must be const", .{ - var_ty, - }); - } + try sema.validateVarType(block, mut_src, var_ty, small.is_extern); if (lib_name != null) { // Look at the sema code for functions which has this logic, it just needs to @@ -8810,9 +8869,54 @@ fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Typ } } -fn validateVarType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void { - if (!ty.isValidVarType(false)) { - return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{ty}); +/// Emit a compile error if type cannot be used for a runtime variable. +fn validateVarType( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + var_ty: Type, + is_extern: bool, +) CompileError!void { + var ty = var_ty; + const ok: bool = while (true) switch (ty.zigTypeTag()) { + .Bool, + .Int, + .Float, + .ErrorSet, + .Enum, + .Frame, + .AnyFrame, + => break true, + + .BoundFn, + .ComptimeFloat, + .ComptimeInt, + .EnumLiteral, + .NoReturn, + .Type, + .Void, + .Undefined, + .Null, + => break false, + + .Opaque => break is_extern, + + .Optional => { + var buf: Type.Payload.ElemType = undefined; + const child_ty = ty.optionalChild(&buf); + return validateVarType(sema, block, src, child_ty, is_extern); + }, + .Pointer, .Array, .Vector => ty = ty.elemType(), + .ErrorUnion => ty = ty.errorUnionPayload(), + + .Fn => @panic("TODO fn validateVarType"), + .Struct, .Union => { + const resolved_ty = try sema.resolveTypeFields(block, src, ty); + break !resolved_ty.requiresComptime(); + }, + } else unreachable; // TODO should not need else unreachable + if (!ok) { + return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{var_ty}); } } @@ -9393,8 +9497,9 @@ fn structFieldPtr( const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_ty); const struct_obj = struct_ty.castTag(.@"struct").?.data; - const field_index = struct_obj.fields.getIndex(field_name) orelse + const field_index_big = struct_obj.fields.getIndex(field_name) orelse return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name); + const field_index = @intCast(u32, field_index_big); const field = struct_obj.fields.values()[field_index]; const ptr_field_ty = try Type.ptr(arena, .{ .pointee_type = field.ty, @@ -9413,31 +9518,7 @@ fn structFieldPtr( } try sema.requireRuntimeBlock(block, src); - const tag: Air.Inst.Tag = switch (field_index) { - 0 => .struct_field_ptr_index_0, - 1 => .struct_field_ptr_index_1, - 2 => .struct_field_ptr_index_2, - 3 => .struct_field_ptr_index_3, - else => { - return block.addInst(.{ - .tag = .struct_field_ptr, - .data = .{ .ty_pl = .{ - .ty = try sema.addType(ptr_field_ty), - .payload = try sema.addExtra(Air.StructField{ - .struct_operand = struct_ptr, - .field_index = @intCast(u32, field_index), - }), - } }, - }); - }, - }; - return block.addInst(.{ - .tag = tag, - .data = .{ .ty_op = .{ - .ty = try sema.addType(ptr_field_ty), - .operand = struct_ptr, - } }, - }); + return block.addStructFieldPtr(struct_ptr, field_index, ptr_field_ty); } fn structFieldVal( @@ -9487,7 +9568,6 @@ fn unionFieldPtr( field_name_src: LazySrcLoc, unresolved_union_ty: Type, ) CompileError!Air.Inst.Ref { - const mod = sema.mod; const arena = sema.arena; assert(unresolved_union_ty.zigTypeTag() == .Union); @@ -9495,8 +9575,9 @@ fn unionFieldPtr( const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty); const union_obj = union_ty.cast(Type.Payload.Union).?.data; - const field_index = union_obj.fields.getIndex(field_name) orelse + const field_index_big = union_obj.fields.getIndex(field_name) orelse return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name); + const field_index = @intCast(u32, field_index_big); const field = union_obj.fields.values()[field_index]; const ptr_field_ty = try Type.ptr(arena, .{ @@ -9517,7 +9598,7 @@ fn unionFieldPtr( } try sema.requireRuntimeBlock(block, src); - return mod.fail(&block.base, src, "TODO implement runtime union field access", .{}); + return block.addStructFieldPtr(union_ptr, field_index, ptr_field_ty); } fn unionFieldVal( @@ -11160,6 +11241,28 @@ fn analyzeUnionFields( if (body.len != 0) { _ = try sema.analyzeBody(block, body); } + var int_tag_ty: Type = undefined; + var enum_field_names: ?*Module.EnumNumbered.NameMap = null; + var enum_value_map: ?*Module.EnumNumbered.ValueMap = null; + if (tag_type_ref != .none) { + const provided_ty = try sema.resolveType(block, src, tag_type_ref); + if (small.auto_enum_tag) { + // The provided type is an integer type and we must construct the enum tag type here. + int_tag_ty = provided_ty; + union_obj.tag_ty = try sema.generateUnionTagTypeNumbered(block, fields_len, provided_ty); + enum_field_names = &union_obj.tag_ty.castTag(.enum_numbered).?.data.fields; + enum_value_map = &union_obj.tag_ty.castTag(.enum_numbered).?.data.values; + } else { + // The provided type is the enum tag type. + union_obj.tag_ty = provided_ty; + } + } else { + // If auto_enum_tag is false, this is an untagged union. However, for semantic analysis + // purposes, we still auto-generate an enum tag type the same way. That the union is + // untagged is represented by the Type tag (union vs union_tagged). + union_obj.tag_ty = try sema.generateUnionTagTypeSimple(block, fields_len); + enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields; + } const bits_per_field = 4; const fields_per_u32 = 32 / bits_per_field; @@ -11198,12 +11301,25 @@ fn analyzeUnionFields( break :blk align_ref; } else .none; - if (has_tag) { + const tag_ref: Zir.Inst.Ref = if (has_tag) blk: { + const tag_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); extra_index += 1; + break :blk tag_ref; + } else .none; + + if (enum_value_map) |map| { + const tag_src = src; // TODO better source location + const coerced = try sema.coerce(block, int_tag_ty, tag_ref, tag_src); + const val = try sema.resolveConstValue(block, tag_src, coerced); + map.putAssumeCapacityContext(val, {}, .{ .ty = int_tag_ty }); } // This string needs to outlive the ZIR code. const field_name = try decl_arena.allocator.dupe(u8, field_name_zir); + if (enum_field_names) |set| { + set.putAssumeCapacity(field_name, {}); + } + const field_ty: Type = if (field_type_ref == .none) Type.initTag(.void) else @@ -11225,11 +11341,84 @@ fn analyzeUnionFields( // But only resolve the source location if we need to emit a compile error. const abi_align_val = (try sema.resolveInstConst(block, src, align_ref)).val; gop.value_ptr.abi_align = try abi_align_val.copy(&decl_arena.allocator); + } else { + gop.value_ptr.abi_align = Value.initTag(.abi_align_default); } } +} - // TODO resolve the union tag_type_ref - _ = tag_type_ref; +fn generateUnionTagTypeNumbered( + sema: *Sema, + block: *Scope.Block, + fields_len: u32, + int_ty: Type, +) !Type { + const mod = sema.mod; + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + + const enum_obj = try new_decl_arena.allocator.create(Module.EnumNumbered); + const enum_ty_payload = try new_decl_arena.allocator.create(Type.Payload.EnumNumbered); + enum_ty_payload.* = .{ + .base = .{ .tag = .enum_numbered }, + .data = enum_obj, + }; + const enum_ty = Type.initPayload(&enum_ty_payload.base); + const enum_val = try Value.Tag.ty.create(&new_decl_arena.allocator, enum_ty); + // TODO better type name + const new_decl = try mod.createAnonymousDecl(&block.base, .{ + .ty = Type.initTag(.type), + .val = enum_val, + }); + new_decl.owns_tv = true; + errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); + + enum_obj.* = .{ + .owner_decl = new_decl, + .tag_ty = int_ty, + .fields = .{}, + .values = .{}, + .node_offset = 0, + }; + // Here we pre-allocate the maps using the decl arena. + try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len); + try enum_obj.values.ensureTotalCapacityContext(&new_decl_arena.allocator, fields_len, .{ .ty = int_ty }); + try new_decl.finalizeNewArena(&new_decl_arena); + return enum_ty; +} + +fn generateUnionTagTypeSimple(sema: *Sema, block: *Scope.Block, fields_len: u32) !Type { + const mod = sema.mod; + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + + const enum_obj = try new_decl_arena.allocator.create(Module.EnumSimple); + const enum_ty_payload = try new_decl_arena.allocator.create(Type.Payload.EnumSimple); + enum_ty_payload.* = .{ + .base = .{ .tag = .enum_simple }, + .data = enum_obj, + }; + const enum_ty = Type.initPayload(&enum_ty_payload.base); + const enum_val = try Value.Tag.ty.create(&new_decl_arena.allocator, enum_ty); + // TODO better type name + const new_decl = try mod.createAnonymousDecl(&block.base, .{ + .ty = Type.initTag(.type), + .val = enum_val, + }); + new_decl.owns_tv = true; + errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); + + enum_obj.* = .{ + .owner_decl = new_decl, + .fields = .{}, + .node_offset = 0, + }; + // Here we pre-allocate the maps using the decl arena. + try enum_obj.fields.ensureTotalCapacity(&new_decl_arena.allocator, fields_len); + try new_decl.finalizeNewArena(&new_decl_arena); + return enum_ty; } fn getBuiltin( @@ -11367,11 +11556,28 @@ fn typeHasOnePossibleValue( } return Value.initTag(.empty_struct_value); }, + .enum_numbered => { + const resolved_ty = try sema.resolveTypeFields(block, src, ty); + const enum_obj = resolved_ty.castTag(.enum_numbered).?.data; + if (enum_obj.fields.count() == 1) { + if (enum_obj.values.count() == 0) { + return Value.initTag(.zero); // auto-numbered + } else { + return enum_obj.values.keys()[0]; + } + } else { + return null; + } + }, .enum_full => { const resolved_ty = try sema.resolveTypeFields(block, src, ty); - const enum_full = resolved_ty.castTag(.enum_full).?.data; - if (enum_full.fields.count() == 1) { - return enum_full.values.keys()[0]; + const enum_obj = resolved_ty.castTag(.enum_full).?.data; + if (enum_obj.fields.count() == 1) { + if (enum_obj.values.count() == 0) { + return Value.initTag(.zero); // auto-numbered + } else { + return enum_obj.values.keys()[0]; + } } else { return null; } diff --git a/src/codegen.zig b/src/codegen.zig index 102f8d4985..6a605edca9 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -889,6 +889,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .atomic_load => try self.airAtomicLoad(inst), .memcpy => try self.airMemcpy(inst), .memset => try self.airMemset(inst), + .set_union_tag => try self.airSetUnionTag(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -1543,6 +1544,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airSetUnionTag(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = switch (arch) { + else => return self.fail("TODO implement airSetUnionTag for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool { if (!self.liveness.operandDies(inst, op_index)) return false; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 16b13db292..fc0c86b8f1 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -955,6 +955,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .atomic_load => try airAtomicLoad(f, inst), .memset => try airMemset(f, inst), .memcpy => try airMemcpy(f, inst), + .set_union_tag => try airSetUnionTag(f, inst), .int_to_float, .float_to_int, @@ -2080,6 +2081,21 @@ fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue { return CValue.none; } +fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const union_ptr = try f.resolveInst(bin_op.lhs); + const new_tag = try f.resolveInst(bin_op.rhs); + const writer = f.object.writer(); + + try writer.writeAll("*"); + try f.writeCValue(writer, union_ptr); + try writer.writeAll(" = "); + try f.writeCValue(writer, new_tag); + try writer.writeAll(";\n"); + + return CValue.none; +} + fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { return switch (order) { .Unordered => "memory_order_relaxed", diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b15834c963..ab164b5d91 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -735,7 +735,7 @@ pub const DeclGen = struct { }, .Enum => { var buffer: Type.Payload.Bits = undefined; - const int_ty = t.enumTagType(&buffer); + const int_ty = t.intTagType(&buffer); const bit_count = int_ty.intInfo(self.module.getTarget()).bits; return self.context.intType(bit_count); }, @@ -812,6 +812,29 @@ pub const DeclGen = struct { .False, ); }, + .Union => { + const union_obj = t.castTag(.@"union").?.data; + assert(union_obj.haveFieldTypes()); + + const enum_tag_ty = union_obj.tag_ty; + const enum_tag_llvm_ty = try self.llvmType(enum_tag_ty); + + if (union_obj.onlyTagHasCodegenBits()) { + return enum_tag_llvm_ty; + } + + const target = self.module.getTarget(); + const most_aligned_field_index = union_obj.mostAlignedField(target); + const most_aligned_field = union_obj.fields.values()[most_aligned_field_index]; + // TODO handle when the most aligned field is different than the + // biggest sized field. + + const llvm_fields = [_]*const llvm.Type{ + try self.llvmType(most_aligned_field.ty), + enum_tag_llvm_ty, + }; + return self.context.structType(&llvm_fields, llvm_fields.len, .False); + }, .Fn => { const ret_ty = try self.llvmType(t.fnReturnType()); const params_len = t.fnParamLen(); @@ -840,7 +863,6 @@ pub const DeclGen = struct { .BoundFn => @panic("TODO remove BoundFn from the language"), - .Union, .Opaque, .Frame, .AnyFrame, @@ -1131,7 +1153,7 @@ pub const DeclGen = struct { var buffer: Type.Payload.Bits = undefined; const int_ty = switch (ty.zigTypeTag()) { .Int => ty, - .Enum => ty.enumTagType(&buffer), + .Enum => ty.intTagType(&buffer), .Float => { if (!is_rmw_xchg) return null; return dg.context.intType(@intCast(c_uint, ty.abiSize(target) * 8)); @@ -1281,6 +1303,7 @@ pub const FuncGen = struct { .atomic_load => try self.airAtomicLoad(inst), .memset => try self.airMemset(inst), .memcpy => try self.airMemcpy(inst), + .set_union_tag => try self.airSetUnionTag(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -1381,7 +1404,7 @@ pub const FuncGen = struct { const int_ty = switch (operand_ty.zigTypeTag()) { .Enum => blk: { var buffer: Type.Payload.Bits = undefined; - const int_ty = operand_ty.enumTagType(&buffer); + const int_ty = operand_ty.intTagType(&buffer); break :blk int_ty; }, .Int, .Bool, .Pointer, .ErrorSet => operand_ty, @@ -1660,8 +1683,9 @@ pub const FuncGen = struct { const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; const struct_ptr = try self.resolveInst(struct_field.struct_operand); + const struct_ptr_ty = self.air.typeOf(struct_field.struct_operand); const field_index = @intCast(c_uint, struct_field.field_index); - return self.builder.buildStructGEP(struct_ptr, field_index, ""); + return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index); } fn airStructFieldPtrIndex(self: *FuncGen, inst: Air.Inst.Index, field_index: c_uint) !?*const llvm.Value { @@ -1670,7 +1694,8 @@ pub const FuncGen = struct { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const struct_ptr = try self.resolveInst(ty_op.operand); - return self.builder.buildStructGEP(struct_ptr, field_index, ""); + const struct_ptr_ty = self.air.typeOf(ty_op.operand); + return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index); } fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { @@ -2521,6 +2546,49 @@ pub const FuncGen = struct { return null; } + fn airSetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const union_ptr = try self.resolveInst(bin_op.lhs); + // TODO handle when onlyTagHasCodegenBits() == true + const new_tag = try self.resolveInst(bin_op.rhs); + const tag_field_ptr = self.builder.buildStructGEP(union_ptr, 1, ""); + + _ = self.builder.buildStore(new_tag, tag_field_ptr); + return null; + } + + fn fieldPtr( + self: *FuncGen, + inst: Air.Inst.Index, + struct_ptr: *const llvm.Value, + struct_ptr_ty: Type, + field_index: c_uint, + ) !?*const llvm.Value { + const struct_ty = struct_ptr_ty.childType(); + switch (struct_ty.zigTypeTag()) { + .Struct => return self.builder.buildStructGEP(struct_ptr, field_index, ""), + .Union => return self.unionFieldPtr(inst, struct_ptr, struct_ty, field_index), + else => unreachable, + } + } + + fn unionFieldPtr( + self: *FuncGen, + inst: Air.Inst.Index, + union_ptr: *const llvm.Value, + union_ty: Type, + field_index: c_uint, + ) !?*const llvm.Value { + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + const field = &union_obj.fields.values()[field_index]; + const result_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst)); + if (!field.ty.hasCodeGenBits()) { + return null; + } + const union_field_ptr = self.builder.buildStructGEP(union_ptr, 0, ""); + return self.builder.buildBitCast(union_field_ptr, result_llvm_ty, ""); + } + fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value { const id = llvm.lookupIntrinsicID(name.ptr, name.len); assert(id != 0); diff --git a/src/print_air.zig b/src/print_air.zig index fa384baae0..e735d03bd3 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -130,6 +130,7 @@ const Writer = struct { .ptr_ptr_elem_val, .shl, .shr, + .set_union_tag, => try w.writeBinOp(s, inst), .is_null, diff --git a/src/type.zig b/src/type.zig index 48c65c1008..bb798959f4 100644 --- a/src/type.zig +++ b/src/type.zig @@ -124,6 +124,7 @@ pub const Type = extern union { .enum_full, .enum_nonexhaustive, .enum_simple, + .enum_numbered, .atomic_order, .atomic_rmw_op, .calling_convention, @@ -874,6 +875,7 @@ pub const Type = extern union { .@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct), .@"union", .union_tagged => return self.copyPayloadShallow(allocator, Payload.Union), .enum_simple => return self.copyPayloadShallow(allocator, Payload.EnumSimple), + .enum_numbered => return self.copyPayloadShallow(allocator, Payload.EnumNumbered), .enum_full, .enum_nonexhaustive => return self.copyPayloadShallow(allocator, Payload.EnumFull), .@"opaque" => return self.copyPayloadShallow(allocator, Payload.Opaque), } @@ -958,6 +960,10 @@ pub const Type = extern union { const enum_simple = ty.castTag(.enum_simple).?.data; return enum_simple.owner_decl.renderFullyQualifiedName(writer); }, + .enum_numbered => { + const enum_numbered = ty.castTag(.enum_numbered).?.data; + return enum_numbered.owner_decl.renderFullyQualifiedName(writer); + }, .@"opaque" => { // TODO use declaration name return writer.writeAll("opaque {}"); @@ -1268,6 +1274,7 @@ pub const Type = extern union { .@"union", .union_tagged, .enum_simple, + .enum_numbered, .enum_full, .enum_nonexhaustive, => false, // TODO some of these should be `true` depending on their child types @@ -1421,7 +1428,7 @@ pub const Type = extern union { const enum_simple = self.castTag(.enum_simple).?.data; return enum_simple.fields.count() >= 2; }, - .enum_nonexhaustive => { + .enum_numbered, .enum_nonexhaustive => { var buffer: Payload.Bits = undefined; const int_tag_ty = self.intTagType(&buffer); return int_tag_ty.hasCodeGenBits(); @@ -1682,7 +1689,7 @@ pub const Type = extern union { assert(biggest != 0); return biggest; }, - .enum_full, .enum_nonexhaustive, .enum_simple => { + .enum_full, .enum_nonexhaustive, .enum_simple, .enum_numbered => { var buffer: Payload.Bits = undefined; const int_tag_ty = self.intTagType(&buffer); return int_tag_ty.abiAlignment(target); @@ -1781,7 +1788,7 @@ pub const Type = extern union { } return size; }, - .enum_simple, .enum_full, .enum_nonexhaustive => { + .enum_simple, .enum_full, .enum_nonexhaustive, .enum_numbered => { var buffer: Payload.Bits = undefined; const int_tag_ty = self.intTagType(&buffer); return int_tag_ty.abiSize(target); @@ -1948,7 +1955,7 @@ pub const Type = extern union { .@"struct" => { @panic("TODO bitSize struct"); }, - .enum_simple, .enum_full, .enum_nonexhaustive => { + .enum_simple, .enum_full, .enum_nonexhaustive, .enum_numbered => { var buffer: Payload.Bits = undefined; const int_tag_ty = self.intTagType(&buffer); return int_tag_ty.bitSize(target); @@ -2094,23 +2101,6 @@ pub const Type = extern union { }; } - /// Asserts the type is an enum. - pub fn intTagType(self: Type, buffer: *Payload.Bits) Type { - switch (self.tag()) { - .enum_full, .enum_nonexhaustive => return self.cast(Payload.EnumFull).?.data.tag_ty, - .enum_simple => { - const enum_simple = self.castTag(.enum_simple).?.data; - const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count()); - buffer.* = .{ - .base = .{ .tag = .int_unsigned }, - .data = bits, - }; - return Type.initPayload(&buffer.base); - }, - else => unreachable, - } - } - pub fn isSinglePointer(self: Type) bool { return switch (self.tag()) { .single_const_pointer, @@ -2363,48 +2353,6 @@ pub const Type = extern union { } } - /// Returns if type can be used for a runtime variable - pub fn isValidVarType(self: Type, is_extern: bool) bool { - var ty = self; - while (true) switch (ty.zigTypeTag()) { - .Bool, - .Int, - .Float, - .ErrorSet, - .Enum, - .Frame, - .AnyFrame, - => return true, - - .Opaque => return is_extern, - .BoundFn, - .ComptimeFloat, - .ComptimeInt, - .EnumLiteral, - .NoReturn, - .Type, - .Void, - .Undefined, - .Null, - => return false, - - .Optional => { - var buf: Payload.ElemType = undefined; - return ty.optionalChild(&buf).isValidVarType(is_extern); - }, - .Pointer, .Array, .Vector => ty = ty.elemType(), - .ErrorUnion => ty = ty.errorUnionPayload(), - - .Fn => @panic("TODO fn isValidVarType"), - .Struct => { - // TODO this is not always correct; introduce lazy value mechanism - // and here we need to force a resolve of "type requires comptime". - return true; - }, - .Union => @panic("TODO union isValidVarType"), - }; - } - pub fn childType(ty: Type) Type { return switch (ty.tag()) { .vector => ty.castTag(.vector).?.data.elem_type, @@ -2530,6 +2478,15 @@ pub const Type = extern union { } } + /// Returns the tag type of a union, if the type is a union and it has a tag type. + /// Otherwise, returns `null`. + pub fn unionTagType(ty: Type) ?Type { + return switch (ty.tag()) { + .union_tagged => ty.castTag(.union_tagged).?.data.tag_ty, + else => null, + }; + } + /// Asserts that the type is an error union. pub fn errorUnionPayload(self: Type) Type { return switch (self.tag()) { @@ -3000,6 +2957,7 @@ pub const Type = extern union { } }, .enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty, + .enum_numbered => ty = ty.castTag(.enum_numbered).?.data.tag_ty, .@"union" => { return null; // TODO }, @@ -3114,31 +3072,21 @@ pub const Type = extern union { } } - /// Returns the integer tag type of the enum. - pub fn enumTagType(ty: Type, buffer: *Payload.Bits) Type { - switch (ty.tag()) { - .enum_full, .enum_nonexhaustive => { - const enum_full = ty.cast(Payload.EnumFull).?.data; - return enum_full.tag_ty; - }, + /// Asserts the type is an enum or a union. + /// TODO support unions + pub fn intTagType(self: Type, buffer: *Payload.Bits) Type { + switch (self.tag()) { + .enum_full, .enum_nonexhaustive => return self.cast(Payload.EnumFull).?.data.tag_ty, + .enum_numbered => return self.castTag(.enum_numbered).?.data.tag_ty, .enum_simple => { - const enum_simple = ty.castTag(.enum_simple).?.data; + const enum_simple = self.castTag(.enum_simple).?.data; + const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count()); buffer.* = .{ .base = .{ .tag = .int_unsigned }, - .data = std.math.log2_int_ceil(usize, enum_simple.fields.count()), + .data = bits, }; return Type.initPayload(&buffer.base); }, - .atomic_order, - .atomic_rmw_op, - .calling_convention, - .float_mode, - .reduce_op, - .call_options, - .export_options, - .extern_options, - => @panic("TODO resolve std.builtin types"), - else => unreachable, } } @@ -3156,10 +3104,8 @@ pub const Type = extern union { const enum_full = ty.cast(Payload.EnumFull).?.data; return enum_full.fields.count(); }, - .enum_simple => { - const enum_simple = ty.castTag(.enum_simple).?.data; - return enum_simple.fields.count(); - }, + .enum_simple => return ty.castTag(.enum_simple).?.data.fields.count(), + .enum_numbered => return ty.castTag(.enum_numbered).?.data.fields.count(), .atomic_order, .atomic_rmw_op, .calling_convention, @@ -3185,6 +3131,10 @@ pub const Type = extern union { const enum_simple = ty.castTag(.enum_simple).?.data; return enum_simple.fields.keys()[field_index]; }, + .enum_numbered => { + const enum_numbered = ty.castTag(.enum_numbered).?.data; + return enum_numbered.fields.keys()[field_index]; + }, .atomic_order, .atomic_rmw_op, .calling_convention, @@ -3209,6 +3159,10 @@ pub const Type = extern union { const enum_simple = ty.castTag(.enum_simple).?.data; return enum_simple.fields.getIndex(field_name); }, + .enum_numbered => { + const enum_numbered = ty.castTag(.enum_numbered).?.data; + return enum_numbered.fields.getIndex(field_name); + }, .atomic_order, .atomic_rmw_op, .calling_convention, @@ -3252,6 +3206,15 @@ pub const Type = extern union { return enum_full.values.getIndexContext(enum_tag, .{ .ty = tag_ty }); } }, + .enum_numbered => { + const enum_obj = ty.castTag(.enum_numbered).?.data; + const tag_ty = enum_obj.tag_ty; + if (enum_obj.values.count() == 0) { + return S.fieldWithRange(tag_ty, enum_tag, enum_obj.fields.count()); + } else { + return enum_obj.values.getIndexContext(enum_tag, .{ .ty = tag_ty }); + } + }, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; const fields_len = enum_simple.fields.count(); @@ -3303,6 +3266,7 @@ pub const Type = extern union { const enum_full = ty.cast(Payload.EnumFull).?.data; return enum_full.srcLoc(); }, + .enum_numbered => return ty.castTag(.enum_numbered).?.data.srcLoc(), .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; return enum_simple.srcLoc(); @@ -3340,6 +3304,7 @@ pub const Type = extern union { const enum_full = ty.cast(Payload.EnumFull).?.data; return enum_full.owner_decl; }, + .enum_numbered => return ty.castTag(.enum_numbered).?.data.owner_decl, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; return enum_simple.owner_decl; @@ -3397,6 +3362,15 @@ pub const Type = extern union { return enum_full.values.containsContext(int, .{ .ty = tag_ty }); } }, + .enum_numbered => { + const enum_obj = ty.castTag(.enum_numbered).?.data; + const tag_ty = enum_obj.tag_ty; + if (enum_obj.values.count() == 0) { + return S.intInRange(tag_ty, int, enum_obj.fields.count()); + } else { + return enum_obj.values.containsContext(int, .{ .ty = tag_ty }); + } + }, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; const fields_len = enum_simple.fields.count(); @@ -3534,6 +3508,7 @@ pub const Type = extern union { @"union", union_tagged, enum_simple, + enum_numbered, enum_full, enum_nonexhaustive, @@ -3642,6 +3617,7 @@ pub const Type = extern union { .@"union", .union_tagged => Payload.Union, .enum_full, .enum_nonexhaustive => Payload.EnumFull, .enum_simple => Payload.EnumSimple, + .enum_numbered => Payload.EnumNumbered, .empty_struct => Payload.ContainerScope, }; } @@ -3818,6 +3794,11 @@ pub const Type = extern union { base: Payload = .{ .tag = .enum_simple }, data: *Module.EnumSimple, }; + + pub const EnumNumbered = struct { + base: Payload = .{ .tag = .enum_numbered }, + data: *Module.EnumNumbered, + }; }; pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type { @@ -3850,6 +3831,23 @@ pub const Type = extern union { }; return Type.initPayload(&type_payload.base); } + + pub fn smallestUnsignedInt(arena: *Allocator, max: u64) !Type { + const bits = bits: { + if (max == 0) break :bits 0; + const base = std.math.log2(max); + const upper = (@as(u64, 1) << base) - 1; + break :bits base + @boolToInt(upper < max); + }; + return switch (bits) { + 1 => initTag(.u1), + 8 => initTag(.u8), + 16 => initTag(.u16), + 32 => initTag(.u32), + 64 => initTag(.u64), + else => return Tag.int_unsigned.create(arena, bits), + }; + } }; pub const CType = enum { diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 14b5e374dd..6b8705e044 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -2,3 +2,15 @@ const std = @import("std"); const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const Tag = std.meta.Tag; + +const Foo = union { + float: f64, + int: i32, +}; + +test "basic unions" { + var foo = Foo{ .int = 1 }; + try expect(foo.int == 1); + foo = Foo{ .float = 12.34 }; + try expect(foo.float == 12.34); +} diff --git a/test/behavior/union_stage1.zig b/test/behavior/union_stage1.zig index 086bd981cd..5741858d51 100644 --- a/test/behavior/union_stage1.zig +++ b/test/behavior/union_stage1.zig @@ -39,13 +39,6 @@ const Foo = union { int: i32, }; -test "basic unions" { - var foo = Foo{ .int = 1 }; - try expect(foo.int == 1); - foo = Foo{ .float = 12.34 }; - try expect(foo.float == 12.34); -} - test "comptime union field access" { comptime { var foo = Foo{ .int = 0 }; From c2a7542df5e9e93289a5d487ba3bbc37c12ffc11 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 27 Sep 2021 21:39:27 -0700 Subject: [PATCH 122/160] ci: azure: run build steps independently to save ram Azure is hitting OOM on test-toolchain. This commit is trying to coast for another week until we switch to Drone CI for this job. --- ci/azure/linux_script | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ci/azure/linux_script b/ci/azure/linux_script index d24663160a..c796f7a2f3 100755 --- a/ci/azure/linux_script +++ b/ci/azure/linux_script @@ -71,9 +71,22 @@ make $JOBS install release/bin/zig test ../test/behavior.zig -fno-stage1 -fLLVM -I ../test -release/bin/zig build test-toolchain -Denable-qemu -Denable-wasmtime -release/bin/zig build test-std -Denable-qemu -Denable-wasmtime -release/bin/zig build docs -Denable-qemu -Denable-wasmtime +release/bin/zig build test-behavior -Denable-qemu -Denable-wasmtime +release/bin/zig build test-compiler-rt -Denable-qemu -Denable-wasmtime +release/bin/zig build test-std -Denable-qemu -Denable-wasmtime +release/bin/zig build test-minilibc -Denable-qemu -Denable-wasmtime +release/bin/zig build test-test-compare-output -Denable-qemu -Denable-wasmtime +release/bin/zig build test-standalone -Denable-qemu -Denable-wasmtime +release/bin/zig build test-stack-traces -Denable-qemu -Denable-wasmtime +release/bin/zig build test-cli -Denable-qemu -Denable-wasmtime +release/bin/zig build test-asm-link -Denable-qemu -Denable-wasmtime +release/bin/zig build test-runtime-safety -Denable-qemu -Denable-wasmtime +release/bin/zig build test-translate-c -Denable-qemu -Denable-wasmtime +release/bin/zig build test-run-translated-c -Denable-qemu -Denable-wasmtime +release/bin/zig build docs -Denable-qemu -Denable-wasmtime +release/bin/zig build # test building self-hosted without LLVM +release/bin/zig build test-fmt -Denable-qemu -Denable-wasmtime +release/bin/zig build test-stage2 -Denable-qemu -Denable-wasmtime # Look for HTML errors. tidy -qe ../zig-cache/langref.html From 09e1f37cb6a8e0df4c521f4b76eab07f0c811852 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 27 Sep 2021 23:11:00 -0700 Subject: [PATCH 123/160] stage2: implement union coercion to its own tag * AIR: add `get_union_tag` instruction - implement in LLVM backend * Sema: implement == and != for union and enum literal - Also implement coercion from union to its own tag type * Value: implement hashing for union values The motivating example is this snippet: comptime assert(@typeInfo(T) == .Float); This was the next blocker for stage2 building compiler-rt. Now it is switch at compile-time on an integer. --- src/Air.zig | 4 ++ src/Liveness.zig | 1 + src/Sema.zig | 82 +++++++++++++++++++++++++++++----- src/codegen.zig | 9 ++++ src/codegen/c.zig | 17 +++++++ src/codegen/llvm.zig | 13 ++++++ src/print_air.zig | 1 + src/type.zig | 8 ++++ src/value.zig | 15 ++++++- test/behavior/union.zig | 18 ++++++++ test/behavior/union_stage1.zig | 27 +++-------- 11 files changed, 162 insertions(+), 33 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index 40070dccfb..b4552f9d7b 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -290,6 +290,9 @@ pub const Inst = struct { /// Result type is always void. /// Uses the `bin_op` field. LHS is union pointer, RHS is new tag value. set_union_tag, + /// Given a tagged union value, get its tag value. + /// Uses the `ty_op` field. + get_union_tag, /// Given a slice value, return the length. /// Result type is always usize. /// Uses the `ty_op` field. @@ -630,6 +633,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .array_to_slice, .float_to_int, .int_to_float, + .get_union_tag, => return air.getRefType(datas[inst].ty_op.ty), .loop, diff --git a/src/Liveness.zig b/src/Liveness.zig index 9a7126d135..a9ff586aeb 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -297,6 +297,7 @@ fn analyzeInst( .array_to_slice, .float_to_int, .int_to_float, + .get_union_tag, => { const o = inst_datas[inst].ty_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index f076389797..b669cdb979 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1349,7 +1349,13 @@ fn zirUnionDecl( errdefer new_decl_arena.deinit(); const union_obj = try new_decl_arena.allocator.create(Module.Union); - const union_ty = try Type.Tag.@"union".create(&new_decl_arena.allocator, union_obj); + const type_tag: Type.Tag = if (small.has_tag_type or small.auto_enum_tag) .union_tagged else .@"union"; + const union_payload = try new_decl_arena.allocator.create(Type.Payload.Union); + union_payload.* = .{ + .base = .{ .tag = type_tag }, + .data = union_obj, + }; + const union_ty = Type.initPayload(&union_payload.base); const union_val = try Value.Tag.ty.create(&new_decl_arena.allocator, union_ty); const type_name = try sema.createTypeName(block, small.name_strategy); const new_decl = try sema.mod.createAnonymousDeclNamed(&block.base, .{ @@ -6477,10 +6483,11 @@ fn zirCmpEq( const non_null_type = if (lhs_ty_tag == .Null) rhs_ty else lhs_ty; return mod.fail(&block.base, src, "comparison of '{}' with null", .{non_null_type}); } - if (((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or - (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) - { - return mod.fail(&block.base, src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); + if (lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) { + return sema.analyzeCmpUnionTag(block, rhs, rhs_src, lhs, lhs_src, op); + } + if (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union) { + return sema.analyzeCmpUnionTag(block, lhs, lhs_src, rhs, rhs_src, op); } if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { const runtime_src: LazySrcLoc = src: { @@ -6521,6 +6528,28 @@ fn zirCmpEq( return sema.analyzeCmp(block, src, lhs, rhs, op, lhs_src, rhs_src, true); } +fn analyzeCmpUnionTag( + sema: *Sema, + block: *Scope.Block, + un: Air.Inst.Ref, + un_src: LazySrcLoc, + tag: Air.Inst.Ref, + tag_src: LazySrcLoc, + op: std.math.CompareOperator, +) CompileError!Air.Inst.Ref { + const union_ty = sema.typeOf(un); + const union_tag_ty = union_ty.unionTagType() orelse { + // TODO note at declaration site that says "union foo is not tagged" + return sema.mod.fail(&block.base, un_src, "comparison of union and enum literal is only valid for tagged union types", .{}); + }; + // Coerce both the union and the tag to the union's tag type, and then execute the + // enum comparison codepath. + const coerced_tag = try sema.coerce(block, union_tag_ty, tag, tag_src); + const coerced_union = try sema.coerce(block, union_tag_ty, un, un_src); + + return sema.cmpSelf(block, coerced_union, coerced_tag, op, un_src, tag_src); +} + /// Only called for non-equality operators. See also `zirCmpEq`. fn zirCmp( sema: *Sema, @@ -6567,10 +6596,21 @@ fn analyzeCmp( @tagName(op), resolved_type, }); } - const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + return sema.cmpSelf(block, casted_lhs, casted_rhs, op, lhs_src, rhs_src); +} +fn cmpSelf( + sema: *Sema, + block: *Scope.Block, + casted_lhs: Air.Inst.Ref, + casted_rhs: Air.Inst.Ref, + op: std.math.CompareOperator, + lhs_src: LazySrcLoc, + rhs_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { + const resolved_type = sema.typeOf(casted_lhs); const runtime_src: LazySrcLoc = src: { if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { if (lhs_val.isUndef()) return sema.addConstUndef(resolved_type); @@ -9919,9 +9959,9 @@ fn coerce( } } }, - .Enum => { - // enum literal to enum - if (inst_ty.zigTypeTag() == .EnumLiteral) { + .Enum => switch (inst_ty.zigTypeTag()) { + .EnumLiteral => { + // enum literal to enum const val = try sema.resolveConstValue(block, inst_src, inst); const bytes = val.castTag(.enum_literal).?.data; const resolved_dest_type = try sema.resolveTypeFields(block, inst_src, dest_type); @@ -9948,7 +9988,15 @@ fn coerce( resolved_dest_type, try Value.Tag.enum_field_index.create(arena, @intCast(u32, field_index)), ); - } + }, + .Union => blk: { + // union to its own tag type + const union_tag_ty = inst_ty.unionTagType() orelse break :blk; + if (union_tag_ty.eql(dest_type)) { + return sema.unionToTag(block, dest_type, inst, inst_src); + } + }, + else => {}, }, .ErrorUnion => { // T to E!T or E to E!T @@ -10802,6 +10850,20 @@ fn wrapErrorUnion( } } +fn unionToTag( + sema: *Sema, + block: *Scope.Block, + dest_type: Type, + un: Air.Inst.Ref, + un_src: LazySrcLoc, +) !Air.Inst.Ref { + if (try sema.resolveMaybeUndefVal(block, un_src, un)) |un_val| { + return sema.addConstant(dest_type, un_val.unionTag()); + } + try sema.requireRuntimeBlock(block, un_src); + return block.addTyOp(.get_union_tag, dest_type, un); +} + fn resolvePeerTypes( sema: *Sema, block: *Scope.Block, diff --git a/src/codegen.zig b/src/codegen.zig index 6a605edca9..4eda3f2594 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -890,6 +890,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .memcpy => try self.airMemcpy(inst), .memset => try self.airMemset(inst), .set_union_tag => try self.airSetUnionTag(inst), + .get_union_tag => try self.airGetUnionTag(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -1552,6 +1553,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airGetUnionTag(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement airGetUnionTag for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool { if (!self.liveness.operandDies(inst, op_index)) return false; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index fc0c86b8f1..a6534b1eba 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -956,6 +956,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .memset => try airMemset(f, inst), .memcpy => try airMemcpy(f, inst), .set_union_tag => try airSetUnionTag(f, inst), + .get_union_tag => try airGetUnionTag(f, inst), .int_to_float, .float_to_int, @@ -2096,6 +2097,22 @@ fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { return CValue.none; } +fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) + return CValue.none; + + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(ty_op.operand); + + try writer.writeAll("get_union_tag("); + try f.writeCValue(writer, operand); + try writer.writeAll(");\n"); + return local; +} + fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { return switch (order) { .Unordered => "memory_order_relaxed", diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index ab164b5d91..4a0d218ead 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1304,6 +1304,7 @@ pub const FuncGen = struct { .memset => try self.airMemset(inst), .memcpy => try self.airMemcpy(inst), .set_union_tag => try self.airSetUnionTag(inst), + .get_union_tag => try self.airGetUnionTag(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -2557,6 +2558,18 @@ pub const FuncGen = struct { return null; } + fn airGetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const un_ty = self.air.typeOf(ty_op.operand); + const un = try self.resolveInst(ty_op.operand); + + _ = un_ty; // TODO handle when onlyTagHasCodegenBits() == true and other union forms + return self.builder.buildExtractValue(un, 1, ""); + } + fn fieldPtr( self: *FuncGen, inst: Air.Inst.Index, diff --git a/src/print_air.zig b/src/print_air.zig index e735d03bd3..2a7538f81a 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -179,6 +179,7 @@ const Writer = struct { .array_to_slice, .int_to_float, .float_to_int, + .get_union_tag, => try w.writeTyOp(s, inst), .block, diff --git a/src/type.zig b/src/type.zig index bb798959f4..781fe74d45 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2487,6 +2487,12 @@ pub const Type = extern union { }; } + pub fn unionFieldType(ty: Type, enum_tag: Value) Type { + const union_obj = ty.cast(Payload.Union).?.data; + const index = union_obj.tag_ty.enumTagFieldIndex(enum_tag).?; + return union_obj.fields.values()[index].ty; + } + /// Asserts that the type is an error union. pub fn errorUnionPayload(self: Type) Type { return switch (self.tag()) { @@ -3801,6 +3807,8 @@ pub const Type = extern union { }; }; + pub const @"bool" = initTag(.bool); + pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type { assert(d.host_size == 0 or d.bit_offset < d.host_size * 8); diff --git a/src/value.zig b/src/value.zig index cb5d211b1e..69f8945e01 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1275,7 +1275,12 @@ pub const Value = extern union { } }, .Union => { - @panic("TODO implement hashing union values"); + const union_obj = val.castTag(.@"union").?.data; + if (ty.unionTagType()) |tag_ty| { + union_obj.tag.hash(tag_ty, hasher); + } + const active_field_ty = ty.unionFieldType(union_obj.tag); + union_obj.val.hash(active_field_ty, hasher); }, .Fn => { @panic("TODO implement hashing function values"); @@ -1431,6 +1436,14 @@ pub const Value = extern union { } } + pub fn unionTag(val: Value) Value { + switch (val.tag()) { + .undef => return val, + .@"union" => return val.castTag(.@"union").?.data.tag, + else => unreachable, + } + } + /// Returns a pointer to the element value at the index. pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value { if (self.castTag(.elem_ptr)) |elem_ptr| { diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 6b8705e044..afefa7cf85 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -14,3 +14,21 @@ test "basic unions" { foo = Foo{ .float = 12.34 }; try expect(foo.float == 12.34); } + +test "init union with runtime value" { + var foo: Foo = undefined; + + setFloat(&foo, 12.34); + try expect(foo.float == 12.34); + + setInt(&foo, 42); + try expect(foo.int == 42); +} + +fn setFloat(foo: *Foo, x: f64) void { + foo.* = Foo{ .float = x }; +} + +fn setInt(foo: *Foo, x: i32) void { + foo.* = Foo{ .int = x }; +} diff --git a/test/behavior/union_stage1.zig b/test/behavior/union_stage1.zig index 5741858d51..725d7bd028 100644 --- a/test/behavior/union_stage1.zig +++ b/test/behavior/union_stage1.zig @@ -49,24 +49,6 @@ test "comptime union field access" { } } -test "init union with runtime value" { - var foo: Foo = undefined; - - setFloat(&foo, 12.34); - try expect(foo.float == 12.34); - - setInt(&foo, 42); - try expect(foo.int == 42); -} - -fn setFloat(foo: *Foo, x: f64) void { - foo.* = Foo{ .float = x }; -} - -fn setInt(foo: *Foo, x: i32) void { - foo.* = Foo{ .int = x }; -} - const FooExtern = extern union { float: f64, int: i32, @@ -185,12 +167,13 @@ test "union field access gives the enum values" { } test "cast union to tag type of union" { - try testCastUnionToTag(TheUnion{ .B = 1234 }); - comptime try testCastUnionToTag(TheUnion{ .B = 1234 }); + try testCastUnionToTag(); + comptime try testCastUnionToTag(); } -fn testCastUnionToTag(x: TheUnion) !void { - try expect(@as(TheTag, x) == TheTag.B); +fn testCastUnionToTag() !void { + var u = TheUnion{ .B = 1234 }; + try expect(@as(TheTag, u) == TheTag.B); } test "cast tag type of union to union" { From 60b6e74468570a124f602a62b6bd2da95ba8c17c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Sep 2021 09:50:25 -0700 Subject: [PATCH 124/160] ci: fix typo introduced in earlier commit c2a7542df5e9e93289a5d487ba3bbc37c12ffc11 introduced a typo in the linux CI script. --- ci/azure/linux_script | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/ci/azure/linux_script b/ci/azure/linux_script index c796f7a2f3..3a1f8b8928 100755 --- a/ci/azure/linux_script +++ b/ci/azure/linux_script @@ -71,22 +71,22 @@ make $JOBS install release/bin/zig test ../test/behavior.zig -fno-stage1 -fLLVM -I ../test -release/bin/zig build test-behavior -Denable-qemu -Denable-wasmtime -release/bin/zig build test-compiler-rt -Denable-qemu -Denable-wasmtime -release/bin/zig build test-std -Denable-qemu -Denable-wasmtime -release/bin/zig build test-minilibc -Denable-qemu -Denable-wasmtime -release/bin/zig build test-test-compare-output -Denable-qemu -Denable-wasmtime -release/bin/zig build test-standalone -Denable-qemu -Denable-wasmtime -release/bin/zig build test-stack-traces -Denable-qemu -Denable-wasmtime -release/bin/zig build test-cli -Denable-qemu -Denable-wasmtime -release/bin/zig build test-asm-link -Denable-qemu -Denable-wasmtime -release/bin/zig build test-runtime-safety -Denable-qemu -Denable-wasmtime -release/bin/zig build test-translate-c -Denable-qemu -Denable-wasmtime -release/bin/zig build test-run-translated-c -Denable-qemu -Denable-wasmtime -release/bin/zig build docs -Denable-qemu -Denable-wasmtime +release/bin/zig build test-behavior -Denable-qemu -Denable-wasmtime +release/bin/zig build test-compiler-rt -Denable-qemu -Denable-wasmtime +release/bin/zig build test-std -Denable-qemu -Denable-wasmtime +release/bin/zig build test-minilibc -Denable-qemu -Denable-wasmtime +release/bin/zig build test-compare-output -Denable-qemu -Denable-wasmtime +release/bin/zig build test-standalone -Denable-qemu -Denable-wasmtime +release/bin/zig build test-stack-traces -Denable-qemu -Denable-wasmtime +release/bin/zig build test-cli -Denable-qemu -Denable-wasmtime +release/bin/zig build test-asm-link -Denable-qemu -Denable-wasmtime +release/bin/zig build test-runtime-safety -Denable-qemu -Denable-wasmtime +release/bin/zig build test-translate-c -Denable-qemu -Denable-wasmtime +release/bin/zig build test-run-translated-c -Denable-qemu -Denable-wasmtime +release/bin/zig build docs -Denable-qemu -Denable-wasmtime release/bin/zig build # test building self-hosted without LLVM -release/bin/zig build test-fmt -Denable-qemu -Denable-wasmtime -release/bin/zig build test-stage2 -Denable-qemu -Denable-wasmtime +release/bin/zig build test-fmt -Denable-qemu -Denable-wasmtime +release/bin/zig build test-stage2 -Denable-qemu -Denable-wasmtime # Look for HTML errors. tidy -qe ../zig-cache/langref.html From 1cc5d4e758a95be373756e7c32f9bb46d21633c9 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Tue, 28 Sep 2021 12:00:35 -0500 Subject: [PATCH 125/160] Stage 2: Support inst.func() syntax (#9827) * Merge call zir instructions to make space for field_call * Fix bug with comptime known anytype args * Delete the param_type zir instruction * Move some passing tests to stage 2 * Implement a.b() function calls * Add field_call_bind support for call and field builtins --- src/AstGen.zig | 161 +++++++++++----- src/Sema.zig | 294 ++++++++++++++++++++++-------- src/Zir.zig | 77 ++++---- src/print_zir.zig | 42 +++-- src/type.zig | 62 ++++++- src/value.zig | 20 ++ test/behavior.zig | 1 + test/behavior/eval.zig | 28 +++ test/behavior/eval_stage1.zig | 28 --- test/behavior/generics.zig | 16 ++ test/behavior/generics_stage1.zig | 16 -- test/behavior/member_func.zig | 103 +++++++++++ 12 files changed, 629 insertions(+), 219 deletions(-) create mode 100644 test/behavior/member_func.zig diff --git a/src/AstGen.zig b/src/AstGen.zig index 469e77037a..15594ac27c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -56,6 +56,7 @@ fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 { u32 => @field(extra, field.name), Zir.Inst.Ref => @enumToInt(@field(extra, field.name)), i32 => @bitCast(u32, @field(extra, field.name)), + Zir.Inst.Call.Flags => @bitCast(u32, @field(extra, field.name)), else => @compileError("bad field type"), }); } @@ -1934,11 +1935,14 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner // in the above while loop. const zir_tags = gz.astgen.instructions.items(.tag); switch (zir_tags[inst]) { - // For some instructions, swap in a slightly different ZIR tag + // For some instructions, modify the zir data // so we can avoid a separate ensure_result_used instruction. - .call_chkused => unreachable, .call => { - zir_tags[inst] = .call_chkused; + const extra_index = gz.astgen.instructions.items(.data)[inst].pl_node.payload_index; + const slot = &gz.astgen.extra.items[extra_index]; + var flags = @bitCast(Zir.Inst.Call.Flags, slot.*); + flags.ensure_result_used = true; + slot.* = @bitCast(u32, flags); break :b true; }, @@ -1976,9 +1980,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .bool_br_and, .bool_br_or, .bool_not, - .call_compile_time, - .call_nosuspend, - .call_async, .cmp_lt, .cmp_lte, .cmp_eq, @@ -1996,8 +1997,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .elem_val_node, .field_ptr, .field_val, + .field_call_bind, .field_ptr_named, .field_val_named, + .field_call_bind_named, .func, .func_inferred, .int, @@ -2012,7 +2015,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .mod_rem, .mul, .mulwrap, - .param_type, .ref, .shl, .shr, @@ -4968,6 +4970,21 @@ fn fieldAccess( scope: *Scope, rl: ResultLoc, node: Ast.Node.Index, +) InnerError!Zir.Inst.Ref { + if (rl == .ref) { + return addFieldAccess(.field_ptr, gz, scope, .ref, node); + } else { + const access = try addFieldAccess(.field_val, gz, scope, .none_or_ref, node); + return rvalue(gz, rl, access, node); + } +} + +fn addFieldAccess( + tag: Zir.Inst.Tag, + gz: *GenZir, + scope: *Scope, + lhs_rl: ResultLoc, + node: Ast.Node.Index, ) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; const tree = astgen.tree; @@ -4978,16 +4995,11 @@ fn fieldAccess( const dot_token = main_tokens[node]; const field_ident = dot_token + 1; const str_index = try astgen.identAsString(field_ident); - switch (rl) { - .ref => return gz.addPlNode(.field_ptr, node, Zir.Inst.Field{ - .lhs = try expr(gz, scope, .ref, object_node), - .field_name_start = str_index, - }), - else => return rvalue(gz, rl, try gz.addPlNode(.field_val, node, Zir.Inst.Field{ - .lhs = try expr(gz, scope, .none_or_ref, object_node), - .field_name_start = str_index, - }), node), - } + + return gz.addPlNode(tag, node, Zir.Inst.Field{ + .lhs = try expr(gz, scope, lhs_rl, object_node), + .field_name_start = str_index, + }); } fn arrayAccess( @@ -7169,16 +7181,15 @@ fn builtinCall( return rvalue(gz, rl, result, node); }, .field => { - const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); if (rl == .ref) { return gz.addPlNode(.field_ptr_named, node, Zir.Inst.FieldNamed{ .lhs = try expr(gz, scope, .ref, params[0]), - .field_name = field_name, + .field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]), }); } const result = try gz.addPlNode(.field_val_named, node, Zir.Inst.FieldNamed{ .lhs = try expr(gz, scope, .none, params[0]), - .field_name = field_name, + .field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]), }); return rvalue(gz, rl, result, node); }, @@ -7554,7 +7565,7 @@ fn builtinCall( }, .call => { const options = try comptimeExpr(gz, scope, .{ .ty = .call_options_type }, params[0]); - const callee = try expr(gz, scope, .none, params[1]); + const callee = try calleeExpr(gz, scope, params[1]); const args = try expr(gz, scope, .none, params[2]); const result = try gz.addPlNode(.builtin_call, node, Zir.Inst.BuiltinCall{ .options = options, @@ -7897,20 +7908,16 @@ fn callExpr( call: Ast.full.Call, ) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; - const lhs = try expr(gz, scope, .none, call.ast.fn_expr); + + const callee = try calleeExpr(gz, scope, call.ast.fn_expr); const args = try astgen.gpa.alloc(Zir.Inst.Ref, call.ast.params.len); defer astgen.gpa.free(args); for (call.ast.params) |param_node, i| { - const param_type = try gz.add(.{ - .tag = .param_type, - .data = .{ .param_type = .{ - .callee = lhs, - .param_index = @intCast(u32, i), - } }, - }); - args[i] = try expr(gz, scope, .{ .coerced_ty = param_type }, param_node); + // Parameters are always temporary values, they have no + // meaningful result location. Sema will coerce them. + args[i] = try expr(gz, scope, .none, param_node); } const modifier: std.builtin.CallOptions.Modifier = blk: { @@ -7925,20 +7932,72 @@ fn callExpr( } break :blk .auto; }; - const result: Zir.Inst.Ref = res: { - const tag: Zir.Inst.Tag = switch (modifier) { - .auto => .call, - .async_kw => .call_async, - .never_tail => unreachable, - .never_inline => unreachable, - .no_async => .call_nosuspend, - .always_tail => unreachable, - .always_inline => unreachable, - .compile_time => .call_compile_time, - }; - break :res try gz.addCall(tag, lhs, args, node); - }; - return rvalue(gz, rl, result, node); // TODO function call with result location + const call_inst = try gz.addCall(modifier, callee, args, node); + return rvalue(gz, rl, call_inst, node); // TODO function call with result location +} + +/// calleeExpr generates the function part of a call expression (f in f(x)), or the +/// callee argument to the @call() builtin. If the lhs is a field access or the +/// @field() builtin, we need to generate a special field_call_bind instruction +/// instead of the normal field_val or field_ptr. If this is a inst.func() call, +/// this instruction will capture the value of the first argument before evaluating +/// the other arguments. We need to use .ref here to guarantee we will be able to +/// promote an lvalue to an address if the first parameter requires it. This +/// unfortunately also means we need to take a reference to any types on the lhs. +fn calleeExpr( + gz: *GenZir, + scope: *Scope, + node: Ast.Node.Index, +) InnerError!Zir.Inst.Ref { + const astgen = gz.astgen; + const tree = astgen.tree; + + const tag = tree.nodes.items(.tag)[node]; + switch (tag) { + .field_access => return addFieldAccess(.field_call_bind, gz, scope, .ref, node), + + .builtin_call_two, + .builtin_call_two_comma, + .builtin_call, + .builtin_call_comma, + => { + const node_datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + const builtin_token = main_tokens[node]; + const builtin_name = tree.tokenSlice(builtin_token); + + var inline_params: [2]Ast.Node.Index = undefined; + var params: []Ast.Node.Index = switch (tag) { + .builtin_call, + .builtin_call_comma, + => tree.extra_data[node_datas[node].lhs..node_datas[node].rhs], + + .builtin_call_two, + .builtin_call_two_comma, + => blk: { + inline_params = .{ node_datas[node].lhs, node_datas[node].rhs }; + const len: usize = if (inline_params[0] == 0) @as(usize, 0) else if (inline_params[1] == 0) @as(usize, 1) else @as(usize, 2); + break :blk inline_params[0..len]; + }, + + else => unreachable, + }; + + // If anything is wrong, fall back to builtinCall. + // It will emit any necessary compile errors and notes. + if (std.mem.eql(u8, builtin_name, "@field") and params.len == 2) { + const lhs = try expr(gz, scope, .ref, params[0]); + const field_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); + return gz.addPlNode(.field_call_bind_named, node, Zir.Inst.FieldNamed{ + .lhs = lhs, + .field_name = field_name, + }); + } + + return builtinCall(gz, scope, .none, node, params); + }, + else => return expr(gz, scope, .none, node), + } } pub const simple_types = std.ComptimeStringMap(Zir.Inst.Ref, .{ @@ -9607,7 +9666,7 @@ const GenZir = struct { fn addCall( gz: *GenZir, - tag: Zir.Inst.Tag, + modifier: std.builtin.CallOptions.Modifier, callee: Zir.Inst.Ref, args: []const Zir.Inst.Ref, /// Absolute node index. This function does the conversion to offset from Decl. @@ -9616,20 +9675,24 @@ const GenZir = struct { assert(callee != .none); assert(src_node != 0); const gpa = gz.astgen.gpa; + const Call = Zir.Inst.Call; try gz.instructions.ensureUnusedCapacity(gpa, 1); try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1); - try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Call).Struct.fields.len + + try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Call).Struct.fields.len + args.len); - const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Call{ + const payload_index = gz.astgen.addExtraAssumeCapacity(Call{ .callee = callee, - .args_len = @intCast(u32, args.len), + .flags = .{ + .packed_modifier = @intCast(Call.Flags.PackedModifier, @enumToInt(modifier)), + .args_len = @intCast(Call.Flags.PackedArgsLen, args.len), + }, }); gz.astgen.appendRefsAssumeCapacity(args); const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len); gz.astgen.instructions.appendAssumeCapacity(.{ - .tag = tag, + .tag = .call, .data = .{ .pl_node = .{ .src_node = gz.nodeIndexToRelative(src_node), .payload_index = payload_index, diff --git a/src/Sema.zig b/src/Sema.zig index b669cdb979..35a434eb35 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -185,11 +185,7 @@ pub fn analyzeBody( .bool_br_and => try sema.zirBoolBr(block, inst, false), .bool_br_or => try sema.zirBoolBr(block, inst, true), .c_import => try sema.zirCImport(block, inst), - .call => try sema.zirCall(block, inst, .auto, false), - .call_chkused => try sema.zirCall(block, inst, .auto, true), - .call_compile_time => try sema.zirCall(block, inst, .compile_time, false), - .call_nosuspend => try sema.zirCall(block, inst, .no_async, false), - .call_async => try sema.zirCall(block, inst, .async_kw, false), + .call => try sema.zirCall(block, inst), .closure_get => try sema.zirClosureGet(block, inst), .cmp_lt => try sema.zirCmp(block, inst, .lt), .cmp_lte => try sema.zirCmp(block, inst, .lte), @@ -223,6 +219,8 @@ pub fn analyzeBody( .field_ptr_named => try sema.zirFieldPtrNamed(block, inst), .field_val => try sema.zirFieldVal(block, inst), .field_val_named => try sema.zirFieldValNamed(block, inst), + .field_call_bind => try sema.zirFieldCallBind(block, inst), + .field_call_bind_named => try sema.zirFieldCallBindNamed(block, inst), .func => try sema.zirFunc(block, inst, false), .func_inferred => try sema.zirFunc(block, inst, true), .import => try sema.zirImport(block, inst), @@ -244,7 +242,6 @@ pub fn analyzeBody( .optional_payload_unsafe => try sema.zirOptionalPayload(block, inst, false), .optional_payload_unsafe_ptr => try sema.zirOptionalPayloadPtr(block, inst, false), .optional_type => try sema.zirOptionalType(block, inst), - .param_type => try sema.zirParamType(block, inst), .ptr_type => try sema.zirPtrType(block, inst), .ptr_type_simple => try sema.zirPtrTypeSimple(block, inst), .ref => try sema.zirRef(block, inst), @@ -2031,45 +2028,6 @@ fn zirStoreNode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE return sema.storePtr(block, src, ptr, value); } -fn zirParamType(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); - - const src = sema.src; - const fn_inst_src = sema.src; - - const inst_data = sema.code.instructions.items(.data)[inst].param_type; - const fn_inst = sema.resolveInst(inst_data.callee); - const fn_inst_ty = sema.typeOf(fn_inst); - const param_index = inst_data.param_index; - - const fn_ty: Type = switch (fn_inst_ty.zigTypeTag()) { - .Fn => fn_inst_ty, - .BoundFn => { - return sema.mod.fail(&block.base, fn_inst_src, "TODO implement zirParamType for method call syntax", .{}); - }, - else => { - return sema.mod.fail(&block.base, fn_inst_src, "expected function, found '{}'", .{fn_inst_ty}); - }, - }; - - const param_count = fn_ty.fnParamLen(); - if (param_index >= param_count) { - if (fn_ty.fnIsVarArgs()) { - return sema.addType(Type.initTag(.var_args_param)); - } - return sema.mod.fail(&block.base, src, "arg index {d} out of bounds; '{}' has {d} argument(s)", .{ - param_index, - fn_ty, - param_count, - }); - } - - // TODO support generic functions - const param_type = fn_ty.fnParamType(param_index); - return sema.addType(param_type); -} - fn zirStr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -2786,8 +2744,6 @@ fn zirCall( sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, - modifier: std.builtin.CallOptions.Modifier, - ensure_result_used: bool, ) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -2796,14 +2752,31 @@ fn zirCall( const func_src: LazySrcLoc = .{ .node_offset_call_func = inst_data.src_node }; const call_src = inst_data.src(); const extra = sema.code.extraData(Zir.Inst.Call, inst_data.payload_index); - const args = sema.code.refSlice(extra.end, extra.data.args_len); + const args = sema.code.refSlice(extra.end, extra.data.flags.args_len); - const func = sema.resolveInst(extra.data.callee); - // TODO handle function calls of generic functions - const resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len); - for (args) |zir_arg, i| { - // the args are already casted to the result of a param type instruction. - resolved_args[i] = sema.resolveInst(zir_arg); + const modifier = @intToEnum(std.builtin.CallOptions.Modifier, extra.data.flags.packed_modifier); + const ensure_result_used = extra.data.flags.ensure_result_used; + + var func = sema.resolveInst(extra.data.callee); + var resolved_args: []Air.Inst.Ref = undefined; + + const func_type = sema.typeOf(func); + + // Desugar bound functions here + if (func_type.tag() == .bound_fn) { + const bound_func = try sema.resolveValue(block, func_src, func); + const bound_data = &bound_func.cast(Value.Payload.BoundFn).?.data; + func = bound_data.func_inst; + resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len + 1); + resolved_args[0] = bound_data.arg0_inst; + for (args) |zir_arg, i| { + resolved_args[i + 1] = sema.resolveInst(zir_arg); + } + } else { + resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len); + for (args) |zir_arg, i| { + resolved_args[i] = sema.resolveInst(zir_arg); + } } return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args); @@ -3334,14 +3307,16 @@ fn analyzeCall( } const arg_src = call_src; // TODO: better source location const arg = uncasted_args[arg_i]; - if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { - const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else if (is_comptime) { - return sema.failWithNeededComptime(block, arg_src); + if (is_comptime) { + if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { + const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else { + return sema.failWithNeededComptime(block, arg_src); + } } else if (is_anytype) { // We insert into the map an instruction which is runtime-known - // but has the type of the comptime argument. + // but has the type of the argument. const child_arg = try child_block.addArg(sema.typeOf(arg), 0); child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); } @@ -4558,6 +4533,19 @@ fn zirFieldPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); } +fn zirFieldCallBind(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const tracy = trace(@src()); + defer tracy.end(); + + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data; + const field_name = sema.code.nullTerminatedString(extra.field_name_start); + const object_ptr = sema.resolveInst(extra.lhs); + return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src); +} + fn zirFieldValNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -4584,6 +4572,19 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Comp return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); } +fn zirFieldCallBindNamed(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const tracy = trace(@src()); + defer tracy.end(); + + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data; + const object_ptr = sema.resolveInst(extra.lhs); + const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name); + return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src); +} + fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -9484,6 +9485,148 @@ fn fieldPtr( return mod.fail(&block.base, src, "type '{}' does not support field access", .{object_ty}); } +fn fieldCallBind( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + raw_ptr: Air.Inst.Ref, + field_name: []const u8, + field_name_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { + // When editing this function, note that there is corresponding logic to be edited + // in `fieldVal`. This function takes a pointer and returns a pointer. + + const mod = sema.mod; + const raw_ptr_src = src; // TODO better source location + const raw_ptr_ty = sema.typeOf(raw_ptr); + const inner_ty = if (raw_ptr_ty.zigTypeTag() == .Pointer and raw_ptr_ty.ptrSize() == .One) + raw_ptr_ty.childType() + else + return mod.fail(&block.base, raw_ptr_src, "expected single pointer, found '{}'", .{raw_ptr_ty}); + + // Optionally dereference a second pointer to get the concrete type. + const is_double_ptr = inner_ty.zigTypeTag() == .Pointer and inner_ty.ptrSize() == .One; + const concrete_ty = if (is_double_ptr) inner_ty.childType() else inner_ty; + const ptr_ty = if (is_double_ptr) inner_ty else raw_ptr_ty; + const object_ptr = if (is_double_ptr) + try sema.analyzeLoad(block, src, raw_ptr, src) + else + raw_ptr; + + const arena = sema.arena; + find_field: { + switch (concrete_ty.zigTypeTag()) { + .Struct => { + const struct_ty = try sema.resolveTypeFields(block, src, concrete_ty); + const struct_obj = struct_ty.castTag(.@"struct").?.data; + + const field_index = struct_obj.fields.getIndex(field_name) orelse + break :find_field; + const field = struct_obj.fields.values()[field_index]; + + const ptr_field_ty = try Type.ptr(arena, .{ + .pointee_type = field.ty, + .mutable = ptr_ty.ptrIsMutable(), + .@"addrspace" = ptr_ty.ptrAddressSpace(), + }); + + if (try sema.resolveDefinedValue(block, src, object_ptr)) |struct_ptr_val| { + const pointer = try sema.addConstant( + ptr_field_ty, + try Value.Tag.field_ptr.create(arena, .{ + .container_ptr = struct_ptr_val, + .field_index = field_index, + }), + ); + return sema.analyzeLoad(block, src, pointer, src); + } + + try sema.requireRuntimeBlock(block, src); + const ptr_inst = ptr_inst: { + const tag: Air.Inst.Tag = switch (field_index) { + 0 => .struct_field_ptr_index_0, + 1 => .struct_field_ptr_index_1, + 2 => .struct_field_ptr_index_2, + 3 => .struct_field_ptr_index_3, + else => { + break :ptr_inst try block.addInst(.{ + .tag = .struct_field_ptr, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(ptr_field_ty), + .payload = try sema.addExtra(Air.StructField{ + .struct_operand = object_ptr, + .field_index = @intCast(u32, field_index), + }), + } }, + }); + }, + }; + break :ptr_inst try block.addInst(.{ + .tag = tag, + .data = .{ .ty_op = .{ + .ty = try sema.addType(ptr_field_ty), + .operand = object_ptr, + } }, + }); + }; + return sema.analyzeLoad(block, src, ptr_inst, src); + }, + .Union => return sema.mod.fail(&block.base, src, "TODO implement field calls on unions", .{}), + .Type => { + const namespace = try sema.analyzeLoad(block, src, object_ptr, src); + return sema.fieldVal(block, src, namespace, field_name, field_name_src); + }, + else => {}, + } + } + + // If we get here, we need to look for a decl in the struct type instead. + switch (concrete_ty.zigTypeTag()) { + .Struct, .Opaque, .Union, .Enum => { + if (concrete_ty.getNamespace()) |namespace| { + if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| { + const decl_val = try sema.analyzeLoad(block, src, inst, src); + const decl_type = sema.typeOf(decl_val); + if (decl_type.zigTypeTag() == .Fn and + decl_type.fnParamLen() >= 1) + { + const first_param_type = decl_type.fnParamType(0); + const first_param_tag = first_param_type.tag(); + // zig fmt: off + if (first_param_tag == .var_args_param or + first_param_tag == .generic_poison or ( + first_param_type.zigTypeTag() == .Pointer and + first_param_type.ptrSize() == .One and + first_param_type.childType().eql(concrete_ty))) + { + // zig fmt: on + // TODO: bound fn calls on rvalues should probably + // generate a by-value argument somehow. + const ty = Type.Tag.bound_fn.init(); + const value = try Value.Tag.bound_fn.create(arena, .{ + .func_inst = decl_val, + .arg0_inst = object_ptr, + }); + return sema.addConstant(ty, value); + } else if (first_param_type.eql(concrete_ty)) { + var deref = try sema.analyzeLoad(block, src, object_ptr, src); + const ty = Type.Tag.bound_fn.init(); + const value = try Value.Tag.bound_fn.create(arena, .{ + .func_inst = decl_val, + .arg0_inst = deref, + }); + return sema.addConstant(ty, value); + } + } + } + } + }, + else => {}, + } + + return mod.fail(&block.base, src, "type '{}' has no field or member function named '{s}'", .{ concrete_ty, field_name }); +} + fn namespaceLookup( sema: *Sema, block: *Scope.Block, @@ -9850,14 +9993,14 @@ fn coerce( if (dest_type.eql(inst_ty)) return inst; - const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty, false); + const mod = sema.mod; + const arena = sema.arena; + + const in_memory_result = coerceInMemoryAllowed(dest_type, inst_ty, false, mod.getTarget()); if (in_memory_result == .ok) { return sema.bitcast(block, dest_type, inst, inst_src); } - const mod = sema.mod; - const arena = sema.arena; - // undefined to anything if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { if (val.isUndef() or inst_ty.zigTypeTag() == .Undefined) { @@ -9898,7 +10041,7 @@ fn coerce( if (inst_ty.ptrAddressSpace() != dest_type.ptrAddressSpace()) break :src_array_ptr; const dst_elem_type = dest_type.elemType(); - switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut)) { + switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut, mod.getTarget())) { .ok => {}, .no_match => break :src_array_ptr, } @@ -10024,7 +10167,7 @@ const InMemoryCoercionResult = enum { /// * sentinel-terminated pointers can coerce into `[*]` /// TODO improve this function to report recursive compile errors like it does in stage1. /// look at the function types_match_const_cast_only -fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InMemoryCoercionResult { +fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool, target: std.Target) InMemoryCoercionResult { if (dest_type.eql(src_type)) return .ok; @@ -10034,7 +10177,7 @@ fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InM const dest_info = dest_type.ptrInfo().data; const src_info = src_type.ptrInfo().data; - const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable); + const child = coerceInMemoryAllowed(dest_info.pointee_type, src_info.pointee_type, dest_info.mutable, target); if (child == .no_match) { return child; } @@ -10081,11 +10224,19 @@ fn coerceInMemoryAllowed(dest_type: Type, src_type: Type, dest_is_mut: bool) InM return .no_match; } - assert(src_info.@"align" != 0); - assert(dest_info.@"align" != 0); + // If both pointers have alignment 0, it means they both want ABI alignment. + // In this case, if they share the same child type, no need to resolve + // pointee type alignment. Otherwise both pointee types must have their alignment + // resolved and we compare the alignment numerically. + if (src_info.@"align" != 0 or dest_info.@"align" != 0 or + !dest_info.pointee_type.eql(src_info.pointee_type)) + { + const src_align = src_type.ptrAlignment(target); + const dest_align = dest_type.ptrAlignment(target); - if (dest_info.@"align" > src_info.@"align") { - return .no_match; + if (dest_align > src_align) { + return .no_match; + } } return .ok; @@ -11606,6 +11757,7 @@ fn typeHasOnePossibleValue( .single_const_pointer, .single_mut_pointer, .pointer, + .bound_fn, => return null, .@"struct" => { diff --git a/src/Zir.zig b/src/Zir.zig index 83c43fd7f2..483880c9b6 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -70,6 +70,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, en u32 => code.extra[i], Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]), i32 => @bitCast(i32, code.extra[i]), + Inst.Call.Flags => @bitCast(Inst.Call.Flags, code.extra[i]), else => @compileError("bad field type"), }; i += 1; @@ -222,17 +223,9 @@ pub const Inst = struct { break_inline, /// Uses the `node` union field. breakpoint, - /// Function call with modifier `.auto`. + /// Function call. /// Uses `pl_node`. AST node is the function call. Payload is `Call`. call, - /// Same as `call` but it also does `ensure_result_used` on the return value. - call_chkused, - /// Same as `call` but with modifier `.compile_time`. - call_compile_time, - /// Same as `call` but with modifier `.no_suspend`. - call_nosuspend, - /// Same as `call` but with modifier `.async_kw`. - call_async, /// `<` /// Uses the `pl_node` union field. Payload is `Bin`. cmp_lt, @@ -327,6 +320,15 @@ pub const Inst = struct { /// This instruction also accepts a pointer. /// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field. field_val, + /// Given a pointer to a struct or object that contains virtual fields, returns the + /// named field. If there is no named field, searches in the type for a decl that + /// matches the field name. The decl is resolved and we ensure that it's a function + /// which can accept the object as the first parameter, with one pointer fixup. If + /// all of that works, this instruction produces a special "bound function" value + /// which contains both the function and the saved first parameter value. + /// Bound functions may only be used as the function parameter to a `call` or + /// `builtin_call` instruction. Any other use is invalid zir and may crash the compiler. + field_call_bind, /// Given a pointer to a struct or object that contains virtual fields, returns a pointer /// to the named field. The field name is a comptime instruction. Used by @field. /// Uses `pl_node` field. The AST node is the builtin call. Payload is FieldNamed. @@ -335,6 +337,15 @@ pub const Inst = struct { /// The field name is a comptime instruction. Used by @field. /// Uses `pl_node` field. The AST node is the builtin call. Payload is FieldNamed. field_val_named, + /// Given a pointer to a struct or object that contains virtual fields, returns the + /// named field. If there is no named field, searches in the type for a decl that + /// matches the field name. The decl is resolved and we ensure that it's a function + /// which can accept the object as the first parameter, with one pointer fixup. If + /// all of that works, this instruction produces a special "bound function" value + /// which contains both the function and the saved first parameter value. + /// Bound functions may only be used as the function parameter to a `call` or + /// `builtin_call` instruction. Any other use is invalid zir and may crash the compiler. + field_call_bind_named, /// Returns a function type, or a function instance, depending on whether /// the body_len is 0. Calling convention is auto. /// Uses the `pl_node` union field. `payload_index` points to a `Func`. @@ -395,14 +406,6 @@ pub const Inst = struct { /// Twos complement wrapping integer multiplication. /// Uses the `pl_node` union field. Payload is `Bin`. mulwrap, - /// Given a reference to a function and a parameter index, returns the - /// type of the parameter. The only usage of this instruction is for the - /// result location of parameters of function calls. In the case of a function's - /// parameter type being `anytype`, it is the type coercion's job to detect this - /// scenario and skip the coercion, so that semantic analysis of this instruction - /// is not in a position where it must create an invalid type. - /// Uses the `param_type` union field. - param_type, /// Turns an R-Value into a const L-Value. In other words, it takes a value, /// stores it in a memory location, and returns a const pointer to it. If the value /// is `comptime`, the memory location is global static constant data. Otherwise, @@ -988,10 +991,6 @@ pub const Inst = struct { .breakpoint, .fence, .call, - .call_chkused, - .call_compile_time, - .call_nosuspend, - .call_async, .cmp_lt, .cmp_lte, .cmp_eq, @@ -1017,8 +1016,10 @@ pub const Inst = struct { .export_value, .field_ptr, .field_val, + .field_call_bind, .field_ptr_named, .field_val_named, + .field_call_bind_named, .func, .func_inferred, .has_decl, @@ -1034,7 +1035,6 @@ pub const Inst = struct { .mod_rem, .mul, .mulwrap, - .param_type, .ref, .shl, .shr, @@ -1247,10 +1247,6 @@ pub const Inst = struct { .break_inline = .@"break", .breakpoint = .node, .call = .pl_node, - .call_chkused = .pl_node, - .call_compile_time = .pl_node, - .call_nosuspend = .pl_node, - .call_async = .pl_node, .cmp_lt = .pl_node, .cmp_lte = .pl_node, .cmp_eq = .pl_node, @@ -1282,6 +1278,8 @@ pub const Inst = struct { .field_val = .pl_node, .field_ptr_named = .pl_node, .field_val_named = .pl_node, + .field_call_bind = .pl_node, + .field_call_bind_named = .pl_node, .func = .pl_node, .func_inferred = .pl_node, .import = .str_tok, @@ -1301,7 +1299,6 @@ pub const Inst = struct { .mod_rem = .pl_node, .mul = .pl_node, .mulwrap = .pl_node, - .param_type = .param_type, .ref = .un_tok, .ret_node = .un_node, .ret_load = .un_node, @@ -2170,10 +2167,6 @@ pub const Inst = struct { /// Points to a `Block`. payload_index: u32, }, - param_type: struct { - callee: Ref, - param_index: u32, - }, @"unreachable": struct { /// Offset from Decl AST node index. /// `Tag` determines which kind of AST node this points to. @@ -2244,7 +2237,6 @@ pub const Inst = struct { ptr_type, int_type, bool_br, - param_type, @"unreachable", @"break", switch_capture, @@ -2372,8 +2364,27 @@ pub const Inst = struct { /// Stored inside extra, with trailing arguments according to `args_len`. /// Each argument is a `Ref`. pub const Call = struct { + // Note: Flags *must* come first so that unusedResultExpr + // can find it when it goes to modify them. + flags: Flags, callee: Ref, - args_len: u32, + + pub const Flags = packed struct { + /// std.builtin.CallOptions.Modifier in packed form + pub const PackedModifier = u3; + pub const PackedArgsLen = u28; + + packed_modifier: PackedModifier, + ensure_result_used: bool = false, + args_len: PackedArgsLen, + + comptime { + if (@sizeOf(Flags) != 4 or @bitSizeOf(Flags) != 32) + @compileError("Layout of Call.Flags needs to be updated!"); + if (@bitSizeOf(std.builtin.CallOptions.Modifier) != @bitSizeOf(PackedModifier)) + @compileError("Call.Flags.PackedModifier needs to be updated!"); + } + }; }; pub const BuiltinCall = struct { diff --git a/src/print_zir.zig b/src/print_zir.zig index 6ae218ed22..3834a694e9 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -179,7 +179,6 @@ const Writer = struct { => try self.writeBoolBr(stream, inst), .array_type_sentinel => try self.writeArrayTypeSentinel(stream, inst), - .param_type => try self.writeParamType(stream, inst), .ptr_type_simple => try self.writePtrTypeSimple(stream, inst), .ptr_type => try self.writePtrType(stream, inst), .int => try self.writeInt(stream, inst), @@ -195,8 +194,6 @@ const Writer = struct { .elem_ptr_node, .elem_val_node, - .field_ptr_named, - .field_val_named, .slice_start, .slice_end, .slice_sentinel, @@ -288,12 +285,7 @@ const Writer = struct { .@"export" => try self.writePlNodeExport(stream, inst), .export_value => try self.writePlNodeExportValue(stream, inst), - .call, - .call_chkused, - .call_compile_time, - .call_nosuspend, - .call_async, - => try self.writePlNodeCall(stream, inst), + .call => try self.writePlNodeCall(stream, inst), .block, .block_inline, @@ -328,8 +320,14 @@ const Writer = struct { .field_ptr, .field_val, + .field_call_bind, => try self.writePlNodeField(stream, inst), + .field_ptr_named, + .field_val_named, + .field_call_bind_named, + => try self.writePlNodeFieldNamed(stream, inst), + .as_node => try self.writeAs(stream, inst), .breakpoint, @@ -481,16 +479,6 @@ const Writer = struct { try stream.writeAll("TODO)"); } - fn writeParamType( - self: *Writer, - stream: anytype, - inst: Zir.Inst.Index, - ) (@TypeOf(stream).Error || error{OutOfMemory})!void { - const inst_data = self.code.instructions.items(.data)[inst].param_type; - try self.writeInstRef(stream, inst_data.callee); - try stream.print(", {d})", .{inst_data.param_index}); - } - fn writePtrTypeSimple( self: *Writer, stream: anytype, @@ -881,8 +869,12 @@ const Writer = struct { fn writePlNodeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index); - const args = self.code.refSlice(extra.end, extra.data.args_len); + const args = self.code.refSlice(extra.end, extra.data.flags.args_len); + if (extra.data.flags.ensure_result_used) { + try stream.writeAll("nodiscard "); + } + try stream.print(".{s}, ", .{@tagName(@intToEnum(std.builtin.CallOptions.Modifier, extra.data.flags.packed_modifier))}); try self.writeInstRef(stream, extra.data.callee); try stream.writeAll(", ["); for (args) |arg, i| { @@ -1637,6 +1629,16 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } + fn writePlNodeFieldNamed(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + const extra = self.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data; + try self.writeInstRef(stream, extra.lhs); + try stream.writeAll(", "); + try self.writeInstRef(stream, extra.field_name); + try stream.writeAll(") "); + try self.writeSrc(stream, inst_data.src()); + } + fn writeAs(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.As, inst_data.payload_index).data; diff --git a/src/type.zig b/src/type.zig index 781fe74d45..e13ede852a 100644 --- a/src/type.zig +++ b/src/type.zig @@ -138,6 +138,7 @@ pub const Type = extern union { .type_info, => return .Union, + .bound_fn => unreachable, .var_args_param => unreachable, // can be any type } } @@ -771,6 +772,7 @@ pub const Type = extern union { .type_info, .@"anyframe", .generic_poison, + .bound_fn, => unreachable, .array_u8, @@ -936,6 +938,7 @@ pub const Type = extern union { .comptime_float, .noreturn, .var_args_param, + .bound_fn, => return writer.writeAll(@tagName(t)), .enum_literal => return writer.writeAll("@Type(.EnumLiteral)"), @@ -1248,6 +1251,7 @@ pub const Type = extern union { .var_args_param => unreachable, .inferred_alloc_mut => unreachable, .inferred_alloc_const => unreachable, + .bound_fn => unreachable, .array_u8, .array_u8_sentinel_0, @@ -1479,6 +1483,7 @@ pub const Type = extern union { .empty_struct_literal, .@"opaque", .type_info, + .bound_fn, => false, .inferred_alloc_const => unreachable, @@ -1489,7 +1494,9 @@ pub const Type = extern union { } pub fn isNoReturn(self: Type) bool { - const definitely_correct_result = self.zigTypeTag() == .NoReturn; + const definitely_correct_result = + self.tag_if_small_enough != .bound_fn and + self.zigTypeTag() == .NoReturn; const fast_result = self.tag_if_small_enough == Tag.noreturn; assert(fast_result == definitely_correct_result); return fast_result; @@ -1736,6 +1743,7 @@ pub const Type = extern union { .@"opaque", .var_args_param, .type_info, + .bound_fn, => unreachable, .generic_poison => unreachable, @@ -1768,6 +1776,7 @@ pub const Type = extern union { .var_args_param => unreachable, .generic_poison => unreachable, .type_info => unreachable, + .bound_fn => unreachable, .@"struct" => { const s = self.castTag(.@"struct").?.data; @@ -1951,6 +1960,7 @@ pub const Type = extern union { .@"opaque" => unreachable, .var_args_param => unreachable, .generic_poison => unreachable, + .bound_fn => unreachable, .@"struct" => { @panic("TODO bitSize struct"); @@ -2353,6 +2363,51 @@ pub const Type = extern union { } } + /// Returns if type can be used for a runtime variable + pub fn isValidVarType(self: Type, is_extern: bool) bool { + var ty = self; + while (true) switch (ty.zigTypeTag()) { + .Bool, + .Int, + .Float, + .ErrorSet, + .Enum, + .Frame, + .AnyFrame, + => return true, + + .Opaque => return is_extern, + .BoundFn, + .ComptimeFloat, + .ComptimeInt, + .EnumLiteral, + .NoReturn, + .Type, + .Void, + .Undefined, + .Null, + => return false, + + .Optional => { + var buf: Payload.ElemType = undefined; + return ty.optionalChild(&buf).isValidVarType(is_extern); + }, + .Pointer, .Array, .Vector => ty = ty.elemType(), + .ErrorUnion => ty = ty.errorUnionPayload(), + + .Fn => @panic("TODO fn isValidVarType"), + .Struct => { + // TODO this is not always correct; introduce lazy value mechanism + // and here we need to force a resolve of "type requires comptime". + return true; + }, + .Union => @panic("TODO union isValidVarType"), + }; + } + + /// For *[N]T, returns [N]T. + /// For *T, returns T. + /// For [*]T, returns T. pub fn childType(ty: Type) Type { return switch (ty.tag()) { .vector => ty.castTag(.vector).?.data.elem_type, @@ -2934,6 +2989,7 @@ pub const Type = extern union { .single_const_pointer, .single_mut_pointer, .pointer, + .bound_fn, => return null, .@"struct" => { @@ -3480,6 +3536,7 @@ pub const Type = extern union { inferred_alloc_mut, /// Same as `inferred_alloc_mut` but the local is `var` not `const`. inferred_alloc_const, // See last_no_payload_tag below. + bound_fn, // After this, the tag requires a payload. array_u8, @@ -3518,7 +3575,7 @@ pub const Type = extern union { enum_full, enum_nonexhaustive, - pub const last_no_payload_tag = Tag.inferred_alloc_const; + pub const last_no_payload_tag = Tag.bound_fn; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; pub fn Type(comptime t: Tag) type { @@ -3585,6 +3642,7 @@ pub const Type = extern union { .extern_options, .type_info, .@"anyframe", + .bound_fn, => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"), .array_u8, diff --git a/src/value.zig b/src/value.zig index 69f8945e01..336f5f9cf7 100644 --- a/src/value.zig +++ b/src/value.zig @@ -159,6 +159,10 @@ pub const Value = extern union { /// Used to coordinate alloc_inferred, store_to_inferred_ptr, and resolve_inferred_alloc /// instructions for comptime code. inferred_alloc_comptime, + /// Used sometimes as the result of field_call_bind. This value is always temporary, + /// and refers directly to the air. It will never be referenced by the air itself. + /// TODO: This is probably a bad encoding, maybe put temp data in the sema instead. + bound_fn, pub const last_no_payload_tag = Tag.empty_array; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -279,6 +283,7 @@ pub const Value = extern union { .inferred_alloc => Payload.InferredAlloc, .@"struct" => Payload.Struct, .@"union" => Payload.Union, + .bound_fn => Payload.BoundFn, }; } @@ -422,6 +427,7 @@ pub const Value = extern union { .extern_options_type, .type_info_type, .generic_poison, + .bound_fn, => unreachable, .ty => { @@ -716,6 +722,10 @@ pub const Value = extern union { try out_stream.writeAll("(opt_payload_ptr)"); val = val.castTag(.opt_payload_ptr).?.data; }, + .bound_fn => { + const bound_func = val.castTag(.bound_fn).?.data; + return out_stream.print("(bound_fn %{}(%{})", .{ bound_func.func_inst, bound_func.arg0_inst }); + }, }; } @@ -2199,6 +2209,16 @@ pub const Value = extern union { val: Value, }, }; + + pub const BoundFn = struct { + pub const base_tag = Tag.bound_fn; + + base: Payload = Payload{ .tag = base_tag }, + data: struct { + func_inst: Air.Inst.Ref, + arg0_inst: Air.Inst.Ref, + }, + }; }; /// Big enough to fit any non-BigInt value diff --git a/test/behavior.zig b/test/behavior.zig index 3a5c0fe589..4bfd947fcf 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -10,6 +10,7 @@ test { _ = @import("behavior/eval.zig"); _ = @import("behavior/generics.zig"); _ = @import("behavior/if.zig"); + _ = @import("behavior/member_func.zig"); _ = @import("behavior/pointers.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); _ = @import("behavior/struct.zig"); diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index 0d103bc49a..6acdf15e89 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -155,3 +155,31 @@ fn MakeType(comptime T: type) type { field: T, }; } + +test "try to trick eval with runtime if" { + try expect(testTryToTrickEvalWithRuntimeIf(true) == 10); +} + +fn testTryToTrickEvalWithRuntimeIf(b: bool) usize { + comptime var i: usize = 0; + inline while (i < 10) : (i += 1) { + const result = if (b) false else true; + _ = result; + } + comptime { + return i; + } +} + +test "@setEvalBranchQuota" { + comptime { + // 1001 for the loop and then 1 more for the expect fn call + @setEvalBranchQuota(1002); + var i = 0; + var sum = 0; + while (i < 1001) : (i += 1) { + sum += i; + } + try expect(sum == 500500); + } +} diff --git a/test/behavior/eval_stage1.zig b/test/behavior/eval_stage1.zig index 743f7af69e..4e945d7af0 100644 --- a/test/behavior/eval_stage1.zig +++ b/test/behavior/eval_stage1.zig @@ -109,21 +109,6 @@ test "const slice" { } } -test "try to trick eval with runtime if" { - try expect(testTryToTrickEvalWithRuntimeIf(true) == 10); -} - -fn testTryToTrickEvalWithRuntimeIf(b: bool) usize { - comptime var i: usize = 0; - inline while (i < 10) : (i += 1) { - const result = if (b) false else true; - _ = result; - } - comptime { - return i; - } -} - test "inlined loop has array literal with elided runtime scope on first iteration but not second iteration" { var runtime = [1]i32{3}; comptime var i: usize = 0; @@ -276,19 +261,6 @@ fn assertEqualPtrs(ptr1: *const u8, ptr2: *const u8) !void { try expect(ptr1 == ptr2); } -test "@setEvalBranchQuota" { - comptime { - // 1001 for the loop and then 1 more for the expect fn call - @setEvalBranchQuota(1002); - var i = 0; - var sum = 0; - while (i < 1001) : (i += 1) { - sum += i; - } - try expect(sum == 500500); - } -} - test "float literal at compile time not lossy" { try expect(16777216.0 + 1.0 == 16777217.0); try expect(9007199254740992.0 + 1.0 == 9007199254740993.0); diff --git a/test/behavior/generics.zig b/test/behavior/generics.zig index f642028e54..76ad5cede7 100644 --- a/test/behavior/generics.zig +++ b/test/behavior/generics.zig @@ -118,3 +118,19 @@ pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) type { prealloc_items: [STATIC_SIZE]T, }; } + +test "const decls in struct" { + try expect(GenericDataThing(3).count_plus_one == 4); +} +fn GenericDataThing(comptime count: isize) type { + return struct { + const count_plus_one = count + 1; + }; +} + +test "use generic param in generic param" { + try expect(aGenericFn(i32, 3, 4) == 7); +} +fn aGenericFn(comptime T: type, comptime a: T, b: T) T { + return a + b; +} diff --git a/test/behavior/generics_stage1.zig b/test/behavior/generics_stage1.zig index 4fa52f5377..c4b9687aa6 100644 --- a/test/behavior/generics_stage1.zig +++ b/test/behavior/generics_stage1.zig @@ -26,22 +26,6 @@ fn GenNode(comptime T: type) type { }; } -test "const decls in struct" { - try expect(GenericDataThing(3).count_plus_one == 4); -} -fn GenericDataThing(comptime count: isize) type { - return struct { - const count_plus_one = count + 1; - }; -} - -test "use generic param in generic param" { - try expect(aGenericFn(i32, 3, 4) == 7); -} -fn aGenericFn(comptime T: type, comptime a: T, b: T) T { - return a + b; -} - test "generic fn with implicit cast" { try expect(getFirstByte(u8, &[_]u8{13}) == 13); try expect(getFirstByte(u16, &[_]u16{ diff --git a/test/behavior/member_func.zig b/test/behavior/member_func.zig new file mode 100644 index 0000000000..092a691901 --- /dev/null +++ b/test/behavior/member_func.zig @@ -0,0 +1,103 @@ +const expect = @import("std").testing.expect; + +const HasFuncs = struct { + state: u32, + func_field: fn (u32) u32, + + fn inc(self: *HasFuncs) void { + self.state += 1; + } + + fn get(self: HasFuncs) u32 { + return self.state; + } + + fn getPtr(self: *const HasFuncs) *const u32 { + return &self.state; + } + + fn one(_: u32) u32 { + return 1; + } + fn two(_: u32) u32 { + return 2; + } +}; + +test "standard field calls" { + try expect(HasFuncs.one(0) == 1); + try expect(HasFuncs.two(0) == 2); + + var v: HasFuncs = undefined; + v.state = 0; + v.func_field = HasFuncs.one; + + const pv = &v; + const pcv: *const HasFuncs = pv; + + try expect(v.get() == 0); + v.inc(); + try expect(v.state == 1); + try expect(v.get() == 1); + + pv.inc(); + try expect(v.state == 2); + try expect(pv.get() == 2); + try expect(v.getPtr().* == 2); + try expect(pcv.get() == 2); + try expect(pcv.getPtr().* == 2); + + v.func_field = HasFuncs.one; + try expect(v.func_field(0) == 1); + try expect(pv.func_field(0) == 1); + try expect(pcv.func_field(0) == 1); + + try expect(pcv.func_field(blk: { + pv.func_field = HasFuncs.two; + break :blk 0; + }) == 1); + + v.func_field = HasFuncs.two; + try expect(v.func_field(0) == 2); + try expect(pv.func_field(0) == 2); + try expect(pcv.func_field(0) == 2); +} + +test "@field field calls" { + try expect(@field(HasFuncs, "one")(0) == 1); + try expect(@field(HasFuncs, "two")(0) == 2); + + var v: HasFuncs = undefined; + v.state = 0; + v.func_field = HasFuncs.one; + + const pv = &v; + const pcv: *const HasFuncs = pv; + + try expect(@field(v, "get")() == 0); + @field(v, "inc")(); + try expect(v.state == 1); + try expect(@field(v, "get")() == 1); + + @field(pv, "inc")(); + try expect(v.state == 2); + try expect(@field(pv, "get")() == 2); + try expect(@field(v, "getPtr")().* == 2); + try expect(@field(pcv, "get")() == 2); + try expect(@field(pcv, "getPtr")().* == 2); + + v.func_field = HasFuncs.one; + try expect(@field(v, "func_field")(0) == 1); + try expect(@field(pv, "func_field")(0) == 1); + try expect(@field(pcv, "func_field")(0) == 1); + + try expect(@field(pcv, "func_field")(blk: { + pv.func_field = HasFuncs.two; + break :blk 0; + }) == 1); + + v.func_field = HasFuncs.two; + try expect(@field(v, "func_field")(0) == 2); + try expect(@field(pv, "func_field")(0) == 2); + try expect(@field(pcv, "func_field")(0) == 2); +} From 754ea118bc7192b75c337ae95ad54dbe619dea56 Mon Sep 17 00:00:00 2001 From: rgreenblatt Date: Sat, 26 Jun 2021 11:28:03 -0500 Subject: [PATCH 126/160] improve panic hierarchy by always using builtin.panic --- lib/std/builtin.zig | 2 +- lib/std/debug.zig | 46 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index f01e7605ab..c0d2e9f725 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -710,7 +710,7 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn }, else => { const first_trace_addr = @returnAddress(); - std.debug.panicExtra(error_return_trace, first_trace_addr, "{s}", .{msg}); + std.debug.panicImpl(error_return_trace, first_trace_addr, msg); }, } } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 6e2cef1f0c..2194886c0a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -228,9 +228,32 @@ pub fn assert(ok: bool) void { 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 (native_os == .wasi) null else @returnAddress(); - panicExtra(null, first_trace_addr, format, args); + + panicExtra(null, format, args); +} + +/// `panicExtra` is useful when you want to print out an `@errorReturnTrace` +/// and also print out some values. +pub fn panicExtra( + trace: ?*builtin.StackTrace, + comptime format: []const u8, + args: anytype, +) noreturn { + @setCold(true); + + const size = 0x1000; + const trunc_msg = "(msg truncated)"; + var buf: [size + trunc_msg.len]u8 = undefined; + // a minor annoyance with this is that it will result in the NoSpaceLeft + // error being part of the @panic stack trace (but that error should + // only happen rarely) + const msg = std.fmt.bufPrint(buf[0..size], format, args) catch |err| switch (err) { + std.fmt.BufPrintError.NoSpaceLeft => blk: { + std.mem.copy(u8, buf[size..], trunc_msg); + break :blk &buf; + }, + }; + builtin.panic(msg, trace); } /// Non-zero whenever the program triggered a panic. @@ -244,7 +267,9 @@ var panic_mutex = std.Thread.Mutex{}; /// 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: anytype) noreturn { +// `panicImpl` could be useful in implementing a custom panic handler which +// calls the default handler (on supported platforms) +pub fn panicImpl(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, msg: []const u8) noreturn { @setCold(true); if (enable_segfault_handler) { @@ -271,7 +296,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c const current_thread_id = std.Thread.getCurrentId(); stderr.print("thread {} panic: ", .{current_thread_id}) catch os.abort(); } - stderr.print(format ++ "\n", args) catch os.abort(); + stderr.print("{s}\n", .{msg}) catch os.abort(); if (trace) |t| { dumpStackTrace(t.*); } @@ -1626,9 +1651,14 @@ fn handleSegfaultWindowsExtra(info: *windows.EXCEPTION_POINTERS, comptime msg: u os.abort(); } else { switch (msg) { - 0 => panicExtra(null, exception_address, format.?, .{}), - 1 => panicExtra(null, exception_address, "Segmentation fault at address 0x{x}", .{info.ExceptionRecord.ExceptionInformation[1]}), - 2 => panicExtra(null, exception_address, "Illegal Instruction", .{}), + 0 => panicImpl(null, exception_address, format.?), + 1 => { + const format_item = "Segmentation fault at address 0x{x}"; + var buf: [format_item.len + 64]u8 = undefined; // 64 is arbitrary, but sufficiently large + const to_print = std.fmt.bufPrint(buf[0..buf.len], format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable; + panicImpl(null, exception_address, to_print); + }, + 2 => panicImpl(null, exception_address, "Illegal Instruction"), else => unreachable, } } From 1e805df81d51a338eefaff0535e5f1cca9e22028 Mon Sep 17 00:00:00 2001 From: Matthew Borkowski Date: Mon, 27 Sep 2021 14:06:14 -0400 Subject: [PATCH 127/160] deflate.zig: fix bits_left overflow at EndOfStream and @intCast truncation with empty Huffman table --- lib/std/compress/deflate.zig | 38 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/lib/std/compress/deflate.zig b/lib/std/compress/deflate.zig index b443c2971f..2fef0b32bc 100644 --- a/lib/std/compress/deflate.zig +++ b/lib/std/compress/deflate.zig @@ -58,8 +58,10 @@ const Huffman = struct { } // All zero. - if (self.count[0] == code_length.len) + if (self.count[0] == code_length.len) { + self.min_code_len = 0; return; + } var left: isize = 1; for (self.count[1..]) |val| { @@ -280,7 +282,7 @@ pub fn InflateStream(comptime ReaderType: type) type { return self.bits & mask; } fn readBits(self: *Self, bits: usize) !u32 { - const val = self.peekBits(bits); + const val = try self.peekBits(bits); self.discardBits(bits); return val; } @@ -487,6 +489,8 @@ pub fn InflateStream(comptime ReaderType: type) type { // We can't read PREFIX_LUT_BITS as we don't want to read past the // deflate stream end, use an incremental approach instead. var code_len = h.min_code_len; + if (code_len == 0) + return error.OutOfCodes; while (true) { _ = try self.peekBits(code_len); // Small optimization win, use as many bits as possible in the @@ -658,11 +662,27 @@ test "lengths overflow" { // f dy hlit hdist hclen 16 17 18 0 (18) x138 (18) x138 (18) x39 (16) x6 // 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11 const stream = [_]u8{ 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 }; - - const reader = std.io.fixedBufferStream(&stream).reader(); - var window: [0x8000]u8 = undefined; - var inflate = inflateStream(reader, &window); - - var buf: [1]u8 = undefined; - try std.testing.expectError(error.InvalidLength, inflate.read(&buf)); + try std.testing.expectError(error.InvalidLength, testInflate(stream[0..])); +} + +test "empty distance alphabet" { + // dynamic block with empty distance alphabet is valid if end of data symbol is used immediately + // f dy hlit hdist hclen 16 17 18 0 8 7 9 6 10 5 11 4 12 3 13 2 14 1 15 (18) x128 (18) x128 (1) ( 0) (256) + // 1 10 00000 00000 1111 000 000 010 010 000 000 000 000 000 000 000 000 000 000 000 000 000 001 000 (11) 1110101 (11) 1110101 (0) (10) (0) + const stream = [_]u8{ 0b00000101, 0b11100000, 0b00000001, 0b00001001, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b01011100, 0b10111111, 0b00101110 }; + try testInflate(stream[0..]); +} + +test "inflateStream fuzzing" { + // see https://github.com/ziglang/zig/issues/9842 + try std.testing.expectError(error.EndOfStream, testInflate("\x950000")); + try std.testing.expectError(error.OutOfCodes, testInflate("\x950\x00\x0000000")); +} + +fn testInflate(data: []const u8) !void { + var window: [0x8000]u8 = undefined; + const reader = std.io.fixedBufferStream(data).reader(); + var inflate = inflateStream(reader, &window); + var inflated = try inflate.reader().readAllAlloc(std.testing.allocator, std.math.maxInt(usize)); + defer std.testing.allocator.free(inflated); } From 79bc5891c1c4cde0592fe1b10b6c9a85914155cf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Sep 2021 15:45:58 -0700 Subject: [PATCH 128/160] stage2: more arithmetic support * AIR: add `mod` instruction for modulus division - Implement for LLVM backend * Sema: implement `@mod`, `@rem`, and `%`. * Sema: fix comptime switch evaluation * Sema: implement comptime shift left * Sema: fix the logic inside analyzeArithmetic to handle all the nuances between the different mathematical operations. - Implement comptime wrapping operations --- src/Air.zig | 11 +- src/Liveness.zig | 1 + src/Sema.zig | 661 +++++++++++++++++++++----- src/Zir.zig | 22 +- src/codegen.zig | 9 + src/codegen/c.zig | 2 + src/codegen/llvm.zig | 29 ++ src/print_air.zig | 1 + src/value.zig | 138 ++++++ test/behavior.zig | 3 +- test/behavior/math.zig | 848 --------------------------------- test/behavior/math_stage1.zig | 855 ++++++++++++++++++++++++++++++++++ 12 files changed, 1605 insertions(+), 975 deletions(-) create mode 100644 test/behavior/math_stage1.zig diff --git a/src/Air.zig b/src/Air.zig index b4552f9d7b..b5d19127a0 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -69,10 +69,16 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. div, - /// Integer or float remainder. - /// Both operands are guaranteed to be the same type, and the result type is the same as both operands. + /// Integer or float remainder division. + /// Both operands are guaranteed to be the same type, and the result type + /// is the same as both operands. /// Uses the `bin_op` field. rem, + /// Integer or float modulus division. + /// Both operands are guaranteed to be the same type, and the result type + /// is the same as both operands. + /// Uses the `bin_op` field. + mod, /// Add an offset to a pointer, returning a new pointer. /// The offset is in element type units, not bytes. /// Wrapping is undefined behavior. @@ -568,6 +574,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .mulwrap, .div, .rem, + .mod, .bit_and, .bit_or, .xor, diff --git a/src/Liveness.zig b/src/Liveness.zig index a9ff586aeb..25dd29b0f6 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -232,6 +232,7 @@ fn analyzeInst( .mulwrap, .div, .rem, + .mod, .ptr_add, .ptr_sub, .bit_and, diff --git a/src/Sema.zig b/src/Sema.zig index 35a434eb35..de94a8c6b8 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -319,8 +319,6 @@ pub fn analyzeBody( .div_exact => try sema.zirDivExact(block, inst), .div_floor => try sema.zirDivFloor(block, inst), .div_trunc => try sema.zirDivTrunc(block, inst), - .mod => try sema.zirMod(block, inst), - .rem => try sema.zirRem(block, inst), .shl_exact => try sema.zirShlExact(block, inst), .shr_exact => try sema.zirShrExact(block, inst), .bit_offset_of => try sema.zirBitOffsetOf(block, inst), @@ -363,14 +361,16 @@ pub fn analyzeBody( .error_set_decl_anon => try sema.zirErrorSetDecl(block, inst, .anon), .error_set_decl_func => try sema.zirErrorSetDecl(block, inst, .func), - .add => try sema.zirArithmetic(block, inst), - .addwrap => try sema.zirArithmetic(block, inst), - .div => try sema.zirArithmetic(block, inst), - .mod_rem => try sema.zirArithmetic(block, inst), - .mul => try sema.zirArithmetic(block, inst), - .mulwrap => try sema.zirArithmetic(block, inst), - .sub => try sema.zirArithmetic(block, inst), - .subwrap => try sema.zirArithmetic(block, inst), + .add => try sema.zirArithmetic(block, inst, .add), + .addwrap => try sema.zirArithmetic(block, inst, .addwrap), + .div => try sema.zirArithmetic(block, inst, .div), + .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem), + .mod => try sema.zirArithmetic(block, inst, .mod), + .rem => try sema.zirArithmetic(block, inst, .rem), + .mul => try sema.zirArithmetic(block, inst, .mul), + .mulwrap => try sema.zirArithmetic(block, inst, .mulwrap), + .sub => try sema.zirArithmetic(block, inst, .sub), + .subwrap => try sema.zirArithmetic(block, inst, .subwrap), // Instructions that we know to *always* be noreturn based solely on their tag. // These functions match the return type of analyzeBody so that we can @@ -886,6 +886,14 @@ fn failWithUseOfUndef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) Compile return sema.mod.fail(&block.base, src, "use of undefined value here causes undefined behavior", .{}); } +fn failWithDivideByZero(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) CompileError { + return sema.mod.fail(&block.base, src, "division by zero here causes undefined behavior", .{}); +} + +fn failWithModRemNegative(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, lhs_ty: Type, rhs_ty: Type) CompileError { + return sema.mod.fail(&block.base, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty }); +} + /// Appropriate to call when the coercion has already been done by result /// location semantics. Asserts the value fits in the provided `Int` type. /// Only supports `Int` types 64 bits or less. @@ -2366,8 +2374,12 @@ fn resolveBlockBody( body: []const Zir.Inst.Index, merges: *Scope.Block.Merges, ) CompileError!Air.Inst.Ref { - _ = try sema.analyzeBody(child_block, body); - return sema.analyzeBlockBody(parent_block, src, child_block, merges); + if (child_block.is_comptime) { + return sema.resolveBody(child_block, body); + } else { + _ = try sema.analyzeBody(child_block, body); + return sema.analyzeBlockBody(parent_block, src, child_block, merges); + } } fn analyzeBlockBody( @@ -5867,23 +5879,36 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const lhs = sema.resolveInst(extra.lhs); const rhs = sema.resolveInst(extra.rhs); - if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(sema.typeOf(lhs)); - } - return sema.mod.fail(&block.base, src, "TODO implement comptime shl", .{}); - } - } + const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); - try sema.requireRuntimeBlock(block, src); + const runtime_src = if (maybe_lhs_val) |lhs_val| rs: { + const lhs_ty = sema.typeOf(lhs); + + if (lhs_val.isUndef()) return sema.addConstUndef(lhs_ty); + const rhs_val = maybe_rhs_val orelse break :rs rhs_src; + if (rhs_val.isUndef()) return sema.addConstUndef(lhs_ty); + + // If rhs is 0, return lhs without doing any calculations. + if (rhs_val.compareWithZero(.eq)) { + return sema.addConstant(lhs_ty, lhs_val); + } + const val = try lhs_val.shl(rhs_val, sema.arena); + return sema.addConstant(lhs_ty, val); + } else rs: { + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) return sema.addConstUndef(sema.typeOf(lhs)); + } + break :rs lhs_src; + }; + + try sema.requireRuntimeBlock(block, runtime_src); return block.addBinOp(.shl, lhs, rhs); } @@ -6141,11 +6166,15 @@ fn zirNegate( return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src); } -fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirArithmetic( + sema: *Sema, + block: *Scope.Block, + inst: Zir.Inst.Index, + zir_tag: Zir.Inst.Tag, +) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); - const tag_override = block.sema.code.instructions.items(.tag)[inst]; const inst_data = sema.code.instructions.items(.data)[inst].pl_node; sema.src = .{ .node_offset_bin_op = inst_data.src_node }; const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; @@ -6154,7 +6183,7 @@ fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile const lhs = sema.resolveInst(extra.lhs); const rhs = sema.resolveInst(extra.rhs); - return sema.analyzeArithmetic(block, tag_override, lhs, rhs, sema.src, lhs_src, rhs_src); + return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); } fn zirOverflowArithmetic( @@ -6187,6 +6216,7 @@ fn zirSatArithmetic( fn analyzeArithmetic( sema: *Sema, block: *Scope.Block, + /// TODO performance investigation: make this comptime? zir_tag: Zir.Inst.Tag, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref, @@ -6204,7 +6234,7 @@ fn analyzeArithmetic( lhs_ty.arrayLen(), rhs_ty.arrayLen(), }); } - return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{}); + return sema.mod.fail(&block.base, src, "TODO implement support for vectors in Sema.analyzeArithmetic", .{}); } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) { return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{ lhs_ty, rhs_ty, @@ -6247,7 +6277,9 @@ fn analyzeArithmetic( }; const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; - const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } }); + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ + .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, + }); const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); @@ -6267,86 +6299,499 @@ fn analyzeArithmetic( }); } - if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(resolved_type); - } - // incase rhs is 0, simply return lhs without doing any calculations - // TODO Once division is implemented we should throw an error when dividing by 0. - if (rhs_val.compareWithZero(.eq)) { - switch (zir_tag) { - .add, .addwrap, .sub, .subwrap => { - return sema.addConstant(scalar_type, lhs_val); - }, - else => {}, + const target = sema.mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs); + const rs: struct { src: LazySrcLoc, air_tag: Air.Inst.Tag } = rs: { + switch (zir_tag) { + .add => { + // For integers: + // If either of the operands are zero, then the other operand is + // returned, even if it is undefined. + // If either of the operands are undefined, it's a compile error + // because there is a possible value for which the addition would + // overflow (max_int), causing illegal behavior. + // For floats: either operand being undef makes the result undef. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { + return casted_rhs; + } } - } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, rhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, lhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return sema.addConstant( + scalar_type, + try lhs_val.intAdd(rhs_val, sema.arena), + ); + } else { + return sema.addConstant( + scalar_type, + try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena), + ); + } + } else break :rs .{ .src = rhs_src, .air_tag = .add }; + } else break :rs .{ .src = lhs_src, .air_tag = .add }; + }, + .addwrap => { + // Integers only; floats are checked above. + // If either of the operands are zero, then the other operand is + // returned, even if it is undefined. + // If either of the operands are undefined, the result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { + return casted_rhs; + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.numberAddWrap(rhs_val, scalar_type, sema.arena, target), + ); + } else break :rs .{ .src = lhs_src, .air_tag = .addwrap }; + } else break :rs .{ .src = rhs_src, .air_tag = .addwrap }; + }, + .sub => { + // For integers: + // If the rhs is zero, then the other operand is + // returned, even if it is undefined. + // If either of the operands are undefined, it's a compile error + // because there is a possible value for which the subtraction would + // overflow, causing illegal behavior. + // For floats: either operand being undef makes the result undef. + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, rhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, lhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return sema.addConstant( + scalar_type, + try lhs_val.intSub(rhs_val, sema.arena), + ); + } else { + return sema.addConstant( + scalar_type, + try lhs_val.floatSub(rhs_val, scalar_type, sema.arena), + ); + } + } else break :rs .{ .src = rhs_src, .air_tag = .sub }; + } else break :rs .{ .src = lhs_src, .air_tag = .sub }; + }, + .subwrap => { + // Integers only; floats are checked above. + // If the RHS is zero, then the other operand is returned, even if it is undefined. + // If either of the operands are undefined, the result is undefined. + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (maybe_rhs_val) |rhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.numberSubWrap(rhs_val, scalar_type, sema.arena, target), + ); + } else break :rs .{ .src = rhs_src, .air_tag = .subwrap }; + } else break :rs .{ .src = lhs_src, .air_tag = .subwrap }; + }, + .div => { + // For integers: + // If the lhs is zero, then zero is returned regardless of rhs. + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined: + // * if lhs type is signed: + // * if rhs is comptime-known and not -1, result is undefined + // * if rhs is -1 or runtime-known, compile error because there is a + // possible value (-min_int * -1) for which division would be + // illegal behavior. + // * if lhs type is unsigned, undef is returned regardless of rhs. + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (lhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (lhs_ty.isSignedInt() and rhs_ty.isSignedInt()) { + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.compare(.neq, Value.negative_one, scalar_type)) { + return sema.addConstUndef(scalar_type); + } + } + return sema.failWithUseOfUndef(block, rhs_src); + } + return sema.addConstUndef(scalar_type); + } - const value = switch (zir_tag) { - .add => blk: { - const val = if (is_int) - try lhs_val.intAdd(rhs_val, sema.arena) - else - try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena); - break :blk val; - }, - .sub => blk: { - const val = if (is_int) - try lhs_val.intSub(rhs_val, sema.arena) - else - try lhs_val.floatSub(rhs_val, scalar_type, sema.arena); - break :blk val; - }, - .div => blk: { - const val = if (is_int) - try lhs_val.intDiv(rhs_val, sema.arena) - else - try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena); - break :blk val; - }, - .mul => blk: { - const val = if (is_int) - try lhs_val.intMul(rhs_val, sema.arena) - else - try lhs_val.floatMul(rhs_val, scalar_type, sema.arena); - break :blk val; - }, - else => return sema.mod.fail(&block.base, src, "TODO implement comptime arithmetic for operand '{s}'", .{@tagName(zir_tag)}), - }; - - log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value }); - - return sema.addConstant(scalar_type, value); - } else { - try sema.requireRuntimeBlock(block, rhs_src); + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return sema.addConstant( + scalar_type, + try lhs_val.intDiv(rhs_val, sema.arena), + ); + } else { + return sema.addConstant( + scalar_type, + try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena), + ); + } + } else break :rs .{ .src = rhs_src, .air_tag = .div }; + } else break :rs .{ .src = lhs_src, .air_tag = .div }; + }, + .mul => { + // For integers: + // If either of the operands are zero, the result is zero. + // If either of the operands are one, the result is the other + // operand, even if it is undefined. + // If either of the operands are undefined, it's a compile error + // because there is a possible value for which the addition would + // overflow (max_int), causing illegal behavior. + // For floats: either operand being undef makes the result undef. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (lhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (lhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_rhs; + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, rhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (rhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (rhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_lhs; + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, lhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (is_int) { + return sema.addConstant( + scalar_type, + try lhs_val.intMul(rhs_val, sema.arena), + ); + } else { + return sema.addConstant( + scalar_type, + try lhs_val.floatMul(rhs_val, scalar_type, sema.arena), + ); + } + } else break :rs .{ .src = lhs_src, .air_tag = .mul }; + } else break :rs .{ .src = rhs_src, .air_tag = .mul }; + }, + .mulwrap => { + // Integers only; floats are handled above. + // If either of the operands are zero, the result is zero. + // If either of the operands are one, the result is the other + // operand, even if it is undefined. + // If either of the operands are undefined, the result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (lhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (lhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_rhs; + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (rhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_lhs; + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + return sema.addConstant( + scalar_type, + try lhs_val.numberMulWrap(rhs_val, scalar_type, sema.arena, target), + ); + } else break :rs .{ .src = lhs_src, .air_tag = .mulwrap }; + } else break :rs .{ .src = rhs_src, .air_tag = .mulwrap }; + }, + .mod_rem => { + // For integers: + // Either operand being undef is a compile error because there exists + // a possible value (TODO what is it?) that would invoke illegal behavior. + // TODO: can lhs zero be handled better? + // TODO: can lhs undef be handled better? + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + // + // For either one: if the result would be different between @mod and @rem, + // then emit a compile error saying you have to pick one. + if (is_int) { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, lhs_src); + } + if (lhs_val.compareWithZero(.lt)) { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + } else if (lhs_ty.isSignedInt()) { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (rhs_val.compareWithZero(.lt)) { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.intRem(rhs_val, sema.arena), + ); + } + break :rs .{ .src = lhs_src, .air_tag = .rem }; + } else if (rhs_ty.isSignedInt()) { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } else { + break :rs .{ .src = rhs_src, .air_tag = .rem }; + } + } + // float operands + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (rhs_val.compareWithZero(.lt)) { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef() or lhs_val.compareWithZero(.lt)) { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + return sema.addConstant( + scalar_type, + try lhs_val.floatRem(rhs_val, sema.arena), + ); + } else { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + } else { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } + }, + .rem => { + // For integers: + // Either operand being undef is a compile error because there exists + // a possible value (TODO what is it?) that would invoke illegal behavior. + // TODO: can lhs zero be handled better? + // TODO: can lhs undef be handled better? + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + if (is_int) { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, lhs_src); + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.intRem(rhs_val, sema.arena), + ); + } + break :rs .{ .src = lhs_src, .air_tag = .rem }; + } else { + break :rs .{ .src = rhs_src, .air_tag = .rem }; + } + } + // float operands + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (maybe_rhs_val) |rhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.floatRem(rhs_val, sema.arena), + ); + } else break :rs .{ .src = rhs_src, .air_tag = .rem }; + } else break :rs .{ .src = lhs_src, .air_tag = .rem }; + }, + .mod => { + // For integers: + // Either operand being undef is a compile error because there exists + // a possible value (TODO what is it?) that would invoke illegal behavior. + // TODO: can lhs zero be handled better? + // TODO: can lhs undef be handled better? + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + if (is_int) { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, lhs_src); + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.intMod(rhs_val, sema.arena), + ); + } + break :rs .{ .src = lhs_src, .air_tag = .mod }; + } else { + break :rs .{ .src = rhs_src, .air_tag = .mod }; + } + } + // float operands + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (maybe_rhs_val) |rhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.floatMod(rhs_val, sema.arena), + ); + } else break :rs .{ .src = rhs_src, .air_tag = .mod }; + } else break :rs .{ .src = lhs_src, .air_tag = .mod }; + }, + else => unreachable, } - } else { - try sema.requireRuntimeBlock(block, lhs_src); - } - - if (zir_tag == .mod_rem) { - const dirty_lhs = lhs_ty.isSignedInt() or lhs_ty.isRuntimeFloat(); - const dirty_rhs = rhs_ty.isSignedInt() or rhs_ty.isRuntimeFloat(); - if (dirty_lhs or dirty_rhs) { - return sema.mod.fail(&block.base, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty }); - } - } - - const air_tag: Air.Inst.Tag = switch (zir_tag) { - .add => .add, - .addwrap => .addwrap, - .sub => .sub, - .subwrap => .subwrap, - .mul => .mul, - .mulwrap => .mulwrap, - .div => .div, - .mod_rem => .rem, - .rem => .rem, - else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for operand '{s}'", .{@tagName(zir_tag)}), }; - return block.addBinOp(air_tag, casted_lhs, casted_rhs); + try sema.requireRuntimeBlock(block, rs.src); + return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs); } fn zirLoad(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -7401,7 +7846,7 @@ fn analyzeRet( fn floatOpAllowed(tag: Zir.Inst.Tag) bool { // extend this swich as additional operators are implemented return switch (tag) { - .add, .sub, .mul, .div => true, + .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true, else => false, }; } @@ -8068,16 +8513,6 @@ fn zirDivTrunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.mod.fail(&block.base, src, "TODO: Sema.zirDivTrunc", .{}); } -fn zirMod(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirMod", .{}); -} - -fn zirRem(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - return sema.zirArithmetic(block, inst); -} - fn zirShlExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); diff --git a/src/Zir.zig b/src/Zir.zig index 483880c9b6..7c171e736d 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -395,17 +395,6 @@ pub const Inst = struct { /// Merge two error sets into one, `E1 || E2`. /// Uses the `pl_node` field with payload `Bin`. merge_error_sets, - /// Ambiguously remainder division or modulus. If the computation would possibly have - /// a different value depending on whether the operation is remainder division or modulus, - /// a compile error is emitted. Otherwise the computation is performed. - /// Uses the `pl_node` union field. Payload is `Bin`. - mod_rem, - /// Arithmetic multiplication. Asserts no integer overflow. - /// Uses the `pl_node` union field. Payload is `Bin`. - mul, - /// Twos complement wrapping integer multiplication. - /// Uses the `pl_node` union field. Payload is `Bin`. - mulwrap, /// Turns an R-Value into a const L-Value. In other words, it takes a value, /// stores it in a memory location, and returns a const pointer to it. If the value /// is `comptime`, the memory location is global static constant data. Otherwise, @@ -828,6 +817,17 @@ pub const Inst = struct { /// Implements the `@rem` builtin. /// Uses the `pl_node` union field with payload `Bin`. rem, + /// Ambiguously remainder division or modulus. If the computation would possibly have + /// a different value depending on whether the operation is remainder division or modulus, + /// a compile error is emitted. Otherwise the computation is performed. + /// Uses the `pl_node` union field. Payload is `Bin`. + mod_rem, + /// Arithmetic multiplication. Asserts no integer overflow. + /// Uses the `pl_node` union field. Payload is `Bin`. + mul, + /// Twos complement wrapping integer multiplication. + /// Uses the `pl_node` union field. Payload is `Bin`. + mulwrap, /// Integer shift-left. Zeroes are shifted in from the right hand side. /// Uses the `pl_node` union field. Payload is `Bin`. diff --git a/src/codegen.zig b/src/codegen.zig index 4eda3f2594..7c359e90c0 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -832,6 +832,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .mulwrap => try self.airMulWrap(inst), .div => try self.airDiv(inst), .rem => try self.airRem(inst), + .mod => try self.airMod(inst), .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), @@ -1353,6 +1354,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airMod(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement mod for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn airBitAnd(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a6534b1eba..4964f17cd3 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -897,6 +897,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO // that wrapping is UB. .div => try airBinOp( f, inst, " / "), .rem => try airBinOp( f, inst, " % "), + // TODO implement modulus division + .mod => try airBinOp( f, inst, " mod "), .cmp_eq => try airBinOp(f, inst, " == "), .cmp_gt => try airBinOp(f, inst, " > "), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4a0d218ead..f65e0f6d72 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1244,6 +1244,7 @@ pub const FuncGen = struct { .mulwrap => try self.airMul(inst, true), .div => try self.airDiv(inst), .rem => try self.airRem(inst), + .mod => try self.airMod(inst), .ptr_add => try self.airPtrAdd(inst), .ptr_sub => try self.airPtrSub(inst), @@ -2095,6 +2096,34 @@ pub const FuncGen = struct { return self.builder.buildURem(lhs, rhs, ""); } + fn airMod(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const inst_ty = self.air.typeOfIndex(inst); + const inst_llvm_ty = try self.dg.llvmType(inst_ty); + + if (inst_ty.isRuntimeFloat()) { + const a = self.builder.buildFRem(lhs, rhs, ""); + const b = self.builder.buildFAdd(a, rhs, ""); + const c = self.builder.buildFRem(b, rhs, ""); + const zero = inst_llvm_ty.constNull(); + const ltz = self.builder.buildFCmp(.OLT, lhs, zero, ""); + return self.builder.buildSelect(ltz, c, a, ""); + } + if (inst_ty.isSignedInt()) { + const a = self.builder.buildSRem(lhs, rhs, ""); + const b = self.builder.buildNSWAdd(a, rhs, ""); + const c = self.builder.buildSRem(b, rhs, ""); + const zero = inst_llvm_ty.constNull(); + const ltz = self.builder.buildICmp(.SLT, lhs, zero, ""); + return self.builder.buildSelect(ltz, c, a, ""); + } + return self.builder.buildURem(lhs, rhs, ""); + } + fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/print_air.zig b/src/print_air.zig index 2a7538f81a..90df06760b 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -110,6 +110,7 @@ const Writer = struct { .mulwrap, .div, .rem, + .mod, .ptr_add, .ptr_sub, .bit_and, diff --git a/src/value.zig b/src/value.zig index 336f5f9cf7..29d8fa8db9 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1616,6 +1616,34 @@ pub const Value = extern union { return result; } + /// Supports both floats and ints; handles undefined. + pub fn numberMulWrap( + lhs: Value, + rhs: Value, + ty: Type, + arena: *Allocator, + target: Target, + ) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + if (ty.isAnyFloat()) { + return floatMul(lhs, rhs, ty, arena); + } + const result = try intMul(lhs, rhs, arena); + + const max = try ty.maxInt(arena, target); + if (compare(result, .gt, max, ty)) { + @panic("TODO comptime wrapping integer multiplication"); + } + + const min = try ty.minInt(arena, target); + if (compare(result, .lt, min, ty)) { + @panic("TODO comptime wrapping integer multiplication"); + } + + return result; + } + /// Supports both floats and ints; handles undefined. pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value { if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); @@ -1840,6 +1868,82 @@ pub const Value = extern union { } } + pub fn intRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs_q = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + const limbs_r = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len, + ); + const limbs_buffer = try allocator.alloc( + std.math.big.Limb, + std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; + var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; + result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); + const result_limbs = result_r.limbs[0..result_r.len]; + + if (result_r.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intMod(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs_q = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + const limbs_r = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len, + ); + const limbs_buffer = try allocator.alloc( + std.math.big.Limb, + std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; + var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; + result_q.divFloor(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); + const result_limbs = result_r.limbs[0..result_r.len]; + + if (result_r.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn floatRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + _ = lhs; + _ = rhs; + _ = allocator; + @panic("TODO implement Value.floatRem"); + } + + pub fn floatMod(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + _ = lhs; + _ = rhs; + _ = allocator; + @panic("TODO implement Value.floatMod"); + } + pub fn intMul(lhs: Value, rhs: Value, allocator: *Allocator) !Value { // TODO is this a performance issue? maybe we should try the operation without // resorting to BigInt first. @@ -1875,6 +1979,31 @@ pub const Value = extern union { return Tag.int_u64.create(arena, truncated); } + pub fn shl(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const shift = rhs.toUnsignedInt(); + const limbs = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + (shift / (@sizeOf(std.math.big.Limb) * 8)) + 1, + ); + var result_bigint = BigIntMutable{ + .limbs = limbs, + .positive = undefined, + .len = undefined, + }; + result_bigint.shiftLeft(lhs_bigint, shift); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + pub fn shr(lhs: Value, rhs: Value, allocator: *Allocator) !Value { // TODO is this a performance issue? maybe we should try the operation without // resorting to BigInt first. @@ -2227,4 +2356,13 @@ pub const Value = extern union { /// are possible without using an allocator. limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb, }; + + pub const zero = initTag(.zero); + pub const one = initTag(.one); + pub const negative_one: Value = .{ .ptr_otherwise = &negative_one_payload.base }; +}; + +var negative_one_payload: Value.Payload.I64 = .{ + .base = .{ .tag = .int_i64 }, + .data = -1, }; diff --git a/test/behavior.zig b/test/behavior.zig index 4bfd947fcf..479e1feffc 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -10,6 +10,7 @@ test { _ = @import("behavior/eval.zig"); _ = @import("behavior/generics.zig"); _ = @import("behavior/if.zig"); + _ = @import("behavior/math.zig"); _ = @import("behavior/member_func.zig"); _ = @import("behavior/pointers.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); @@ -119,7 +120,7 @@ test { _ = @import("behavior/incomplete_struct_param_tld.zig"); _ = @import("behavior/inttoptr.zig"); _ = @import("behavior/ir_block_deps.zig"); - _ = @import("behavior/math.zig"); + _ = @import("behavior/math_stage1.zig"); _ = @import("behavior/maximum_minimum.zig"); _ = @import("behavior/merge_error_sets.zig"); _ = @import("behavior/misc.zig"); diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 7a5c31f67a..510cc3d438 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -6,171 +6,6 @@ const maxInt = std.math.maxInt; const minInt = std.math.minInt; const mem = std.mem; -test "division" { - try testDivision(); - comptime try testDivision(); -} -fn testDivision() !void { - try expect(div(u32, 13, 3) == 4); - try expect(div(f16, 1.0, 2.0) == 0.5); - try expect(div(f32, 1.0, 2.0) == 0.5); - - try expect(divExact(u32, 55, 11) == 5); - try expect(divExact(i32, -55, 11) == -5); - try expect(divExact(f16, 55.0, 11.0) == 5.0); - try expect(divExact(f16, -55.0, 11.0) == -5.0); - try expect(divExact(f32, 55.0, 11.0) == 5.0); - try expect(divExact(f32, -55.0, 11.0) == -5.0); - - try expect(divFloor(i32, 5, 3) == 1); - try expect(divFloor(i32, -5, 3) == -2); - try expect(divFloor(f16, 5.0, 3.0) == 1.0); - try expect(divFloor(f16, -5.0, 3.0) == -2.0); - try expect(divFloor(f32, 5.0, 3.0) == 1.0); - try expect(divFloor(f32, -5.0, 3.0) == -2.0); - try expect(divFloor(i32, -0x80000000, -2) == 0x40000000); - try expect(divFloor(i32, 0, -0x80000000) == 0); - try expect(divFloor(i32, -0x40000001, 0x40000000) == -2); - try expect(divFloor(i32, -0x80000000, 1) == -0x80000000); - try expect(divFloor(i32, 10, 12) == 0); - try expect(divFloor(i32, -14, 12) == -2); - try expect(divFloor(i32, -2, 12) == -1); - - try expect(divTrunc(i32, 5, 3) == 1); - try expect(divTrunc(i32, -5, 3) == -1); - try expect(divTrunc(f16, 5.0, 3.0) == 1.0); - try expect(divTrunc(f16, -5.0, 3.0) == -1.0); - try expect(divTrunc(f32, 5.0, 3.0) == 1.0); - try expect(divTrunc(f32, -5.0, 3.0) == -1.0); - try expect(divTrunc(f64, 5.0, 3.0) == 1.0); - try expect(divTrunc(f64, -5.0, 3.0) == -1.0); - try expect(divTrunc(i32, 10, 12) == 0); - try expect(divTrunc(i32, -14, 12) == -1); - try expect(divTrunc(i32, -2, 12) == 0); - - try expect(mod(i32, 10, 12) == 10); - try expect(mod(i32, -14, 12) == 10); - try expect(mod(i32, -2, 12) == 10); - - comptime { - try expect( - 1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600, - ); - try expect( - @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600, - ); - try expect( - 1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2, - ); - try expect( - @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2, - ); - try expect( - @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2, - ); - try expect( - @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2, - ); - try expect( - 4126227191251978491697987544882340798050766755606969681711 % 10 == 1, - ); - } -} -fn div(comptime T: type, a: T, b: T) T { - return a / b; -} -fn divExact(comptime T: type, a: T, b: T) T { - return @divExact(a, b); -} -fn divFloor(comptime T: type, a: T, b: T) T { - return @divFloor(a, b); -} -fn divTrunc(comptime T: type, a: T, b: T) T { - return @divTrunc(a, b); -} -fn mod(comptime T: type, a: T, b: T) T { - return @mod(a, b); -} - -test "@addWithOverflow" { - var result: u8 = undefined; - try expect(@addWithOverflow(u8, 250, 100, &result)); - try expect(!@addWithOverflow(u8, 100, 150, &result)); - try expect(result == 250); -} - -// TODO test mulWithOverflow -// TODO test subWithOverflow - -test "@shlWithOverflow" { - var result: u16 = undefined; - try expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result)); - try expect(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result)); - try expect(result == 0b1011111111111100); -} - -test "@*WithOverflow with u0 values" { - var result: u0 = undefined; - try expect(!@addWithOverflow(u0, 0, 0, &result)); - try expect(!@subWithOverflow(u0, 0, 0, &result)); - try expect(!@mulWithOverflow(u0, 0, 0, &result)); - try expect(!@shlWithOverflow(u0, 0, 0, &result)); -} - -test "@clz" { - try testClz(); - comptime try testClz(); -} - -fn testClz() !void { - try expect(@clz(u8, 0b10001010) == 0); - try expect(@clz(u8, 0b00001010) == 4); - try expect(@clz(u8, 0b00011010) == 3); - try expect(@clz(u8, 0b00000000) == 8); - try expect(@clz(u128, 0xffffffffffffffff) == 64); - try expect(@clz(u128, 0x10000000000000000) == 63); -} - -test "@clz vectors" { - try testClzVectors(); - comptime try testClzVectors(); -} - -fn testClzVectors() !void { - @setEvalBranchQuota(10_000); - try expectEqual(@clz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 0))); - try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00001010))), @splat(64, @as(u4, 4))); - try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00011010))), @splat(64, @as(u4, 3))); - try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8))); - try expectEqual(@clz(u128, @splat(64, @as(u128, 0xffffffffffffffff))), @splat(64, @as(u8, 64))); - try expectEqual(@clz(u128, @splat(64, @as(u128, 0x10000000000000000))), @splat(64, @as(u8, 63))); -} - -test "@ctz" { - try testCtz(); - comptime try testCtz(); -} - -fn testCtz() !void { - try expect(@ctz(u8, 0b10100000) == 5); - try expect(@ctz(u8, 0b10001010) == 1); - try expect(@ctz(u8, 0b00000000) == 8); - try expect(@ctz(u16, 0b00000000) == 16); -} - -test "@ctz vectors" { - try testClzVectors(); - comptime try testClzVectors(); -} - -fn testCtzVectors() !void { - @setEvalBranchQuota(10_000); - try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10100000))), @splat(64, @as(u4, 5))); - try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 1))); - try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8))); - try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16))); -} - test "assignment operators" { var i: u32 = 0; i += 5; @@ -218,686 +53,3 @@ fn testThreeExprInARow(f: bool, t: bool) !void { fn assertFalse(b: bool) !void { try expect(!b); } - -test "const number literal" { - const one = 1; - const eleven = ten + one; - - try expect(eleven == 11); -} -const ten = 10; - -test "unsigned wrapping" { - try testUnsignedWrappingEval(maxInt(u32)); - comptime try testUnsignedWrappingEval(maxInt(u32)); -} -fn testUnsignedWrappingEval(x: u32) !void { - const zero = x +% 1; - try expect(zero == 0); - const orig = zero -% 1; - try expect(orig == maxInt(u32)); -} - -test "signed wrapping" { - try testSignedWrappingEval(maxInt(i32)); - comptime try testSignedWrappingEval(maxInt(i32)); -} -fn testSignedWrappingEval(x: i32) !void { - const min_val = x +% 1; - try expect(min_val == minInt(i32)); - const max_val = min_val -% 1; - try expect(max_val == maxInt(i32)); -} - -test "signed negation wrapping" { - try testSignedNegationWrappingEval(minInt(i16)); - comptime try testSignedNegationWrappingEval(minInt(i16)); -} -fn testSignedNegationWrappingEval(x: i16) !void { - try expect(x == -32768); - const neg = -%x; - try expect(neg == -32768); -} - -test "unsigned negation wrapping" { - try testUnsignedNegationWrappingEval(1); - comptime try testUnsignedNegationWrappingEval(1); -} -fn testUnsignedNegationWrappingEval(x: u16) !void { - try expect(x == 1); - const neg = -%x; - try expect(neg == maxInt(u16)); -} - -test "unsigned 64-bit division" { - try test_u64_div(); - comptime try test_u64_div(); -} -fn test_u64_div() !void { - const result = divWithResult(1152921504606846976, 34359738365); - try expect(result.quotient == 33554432); - try expect(result.remainder == 100663296); -} -fn divWithResult(a: u64, b: u64) DivResult { - return DivResult{ - .quotient = a / b, - .remainder = a % b, - }; -} -const DivResult = struct { - quotient: u64, - remainder: u64, -}; - -test "binary not" { - try expect(comptime x: { - break :x ~@as(u16, 0b1010101010101010) == 0b0101010101010101; - }); - try expect(comptime x: { - break :x ~@as(u64, 2147483647) == 18446744071562067968; - }); - try testBinaryNot(0b1010101010101010); -} - -fn testBinaryNot(x: u16) !void { - try expect(~x == 0b0101010101010101); -} - -test "small int addition" { - var x: u2 = 0; - try expect(x == 0); - - x += 1; - try expect(x == 1); - - x += 1; - try expect(x == 2); - - x += 1; - try expect(x == 3); - - var result: @TypeOf(x) = 3; - try expect(@addWithOverflow(@TypeOf(x), x, 1, &result)); - - try expect(result == 0); -} - -test "float equality" { - const x: f64 = 0.012; - const y: f64 = x + 1.0; - - try testFloatEqualityImpl(x, y); - comptime try testFloatEqualityImpl(x, y); -} - -fn testFloatEqualityImpl(x: f64, y: f64) !void { - const y2 = x + 1.0; - try expect(y == y2); -} - -test "allow signed integer division/remainder when values are comptime known and positive or exact" { - try expect(5 / 3 == 1); - try expect(-5 / -3 == 1); - try expect(-6 / 3 == -2); - - try expect(5 % 3 == 2); - try expect(-6 % 3 == 0); -} - -test "hex float literal parsing" { - comptime try expect(0x1.0 == 1.0); -} - -test "quad hex float literal parsing in range" { - const a = 0x1.af23456789bbaaab347645365cdep+5; - const b = 0x1.dedafcff354b6ae9758763545432p-9; - const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534; - const d = 0x1.edcbff8ad76ab5bf46463233214fp-435; - if (false) { - a; - b; - c; - d; - } -} - -test "quad hex float literal parsing accurate" { - const a: f128 = 0x1.1111222233334444555566667777p+0; - - // implied 1 is dropped, with an exponent of 0 (0x3fff) after biasing. - const expected: u128 = 0x3fff1111222233334444555566667777; - try expect(@bitCast(u128, a) == expected); - - // non-normalized - const b: f128 = 0x11.111222233334444555566667777p-4; - try expect(@bitCast(u128, b) == expected); - - const S = struct { - fn doTheTest() !void { - { - var f: f128 = 0x1.2eab345678439abcdefea56782346p+5; - try expect(@bitCast(u128, f) == 0x40042eab345678439abcdefea5678234); - } - { - var f: f128 = 0x1.edcb34a235253948765432134674fp-1; - try expect(@bitCast(u128, f) == 0x3ffeedcb34a235253948765432134674); - } - { - var f: f128 = 0x1.353e45674d89abacc3a2ebf3ff4ffp-50; - try expect(@bitCast(u128, f) == 0x3fcd353e45674d89abacc3a2ebf3ff50); - } - { - var f: f128 = 0x1.ed8764648369535adf4be3214567fp-9; - try expect(@bitCast(u128, f) == 0x3ff6ed8764648369535adf4be3214568); - } - const exp2ft = [_]f64{ - 0x1.6a09e667f3bcdp-1, - 0x1.7a11473eb0187p-1, - 0x1.8ace5422aa0dbp-1, - 0x1.9c49182a3f090p-1, - 0x1.ae89f995ad3adp-1, - 0x1.c199bdd85529cp-1, - 0x1.d5818dcfba487p-1, - 0x1.ea4afa2a490dap-1, - 0x1.0000000000000p+0, - 0x1.0b5586cf9890fp+0, - 0x1.172b83c7d517bp+0, - 0x1.2387a6e756238p+0, - 0x1.306fe0a31b715p+0, - 0x1.3dea64c123422p+0, - 0x1.4bfdad5362a27p+0, - 0x1.5ab07dd485429p+0, - 0x1.8p23, - 0x1.62e430p-1, - 0x1.ebfbe0p-3, - 0x1.c6b348p-5, - 0x1.3b2c9cp-7, - 0x1.0p127, - -0x1.0p-149, - }; - - const answers = [_]u64{ - 0x3fe6a09e667f3bcd, - 0x3fe7a11473eb0187, - 0x3fe8ace5422aa0db, - 0x3fe9c49182a3f090, - 0x3feae89f995ad3ad, - 0x3fec199bdd85529c, - 0x3fed5818dcfba487, - 0x3feea4afa2a490da, - 0x3ff0000000000000, - 0x3ff0b5586cf9890f, - 0x3ff172b83c7d517b, - 0x3ff2387a6e756238, - 0x3ff306fe0a31b715, - 0x3ff3dea64c123422, - 0x3ff4bfdad5362a27, - 0x3ff5ab07dd485429, - 0x4168000000000000, - 0x3fe62e4300000000, - 0x3fcebfbe00000000, - 0x3fac6b3480000000, - 0x3f83b2c9c0000000, - 0x47e0000000000000, - 0xb6a0000000000000, - }; - - for (exp2ft) |x, i| { - try expect(@bitCast(u64, x) == answers[i]); - } - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "underscore separator parsing" { - try expect(0_0_0_0 == 0); - try expect(1_234_567 == 1234567); - try expect(001_234_567 == 1234567); - try expect(0_0_1_2_3_4_5_6_7 == 1234567); - - try expect(0b0_0_0_0 == 0); - try expect(0b1010_1010 == 0b10101010); - try expect(0b0000_1010_1010 == 0b10101010); - try expect(0b1_0_1_0_1_0_1_0 == 0b10101010); - - try expect(0o0_0_0_0 == 0); - try expect(0o1010_1010 == 0o10101010); - try expect(0o0000_1010_1010 == 0o10101010); - try expect(0o1_0_1_0_1_0_1_0 == 0o10101010); - - try expect(0x0_0_0_0 == 0); - try expect(0x1010_1010 == 0x10101010); - try expect(0x0000_1010_1010 == 0x10101010); - try expect(0x1_0_1_0_1_0_1_0 == 0x10101010); - - try expect(123_456.789_000e1_0 == 123456.789000e10); - try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10); - - try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10); - try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10); -} - -test "hex float literal within range" { - const a = 0x1.0p16383; - const b = 0x0.1p16387; - const c = 0x1.0p-16382; - if (false) { - a; - b; - c; - } -} - -test "truncating shift left" { - try testShlTrunc(maxInt(u16)); - comptime try testShlTrunc(maxInt(u16)); -} -fn testShlTrunc(x: u16) !void { - const shifted = x << 1; - try expect(shifted == 65534); -} - -test "truncating shift right" { - try testShrTrunc(maxInt(u16)); - comptime try testShrTrunc(maxInt(u16)); -} -fn testShrTrunc(x: u16) !void { - const shifted = x >> 1; - try expect(shifted == 32767); -} - -test "exact shift left" { - try testShlExact(0b00110101); - comptime try testShlExact(0b00110101); -} -fn testShlExact(x: u8) !void { - const shifted = @shlExact(x, 2); - try expect(shifted == 0b11010100); -} - -test "exact shift right" { - try testShrExact(0b10110100); - comptime try testShrExact(0b10110100); -} -fn testShrExact(x: u8) !void { - const shifted = @shrExact(x, 2); - try expect(shifted == 0b00101101); -} - -test "shift left/right on u0 operand" { - const S = struct { - fn doTheTest() !void { - var x: u0 = 0; - var y: u0 = 0; - try expectEqual(@as(u0, 0), x << 0); - try expectEqual(@as(u0, 0), x >> 0); - try expectEqual(@as(u0, 0), x << y); - try expectEqual(@as(u0, 0), x >> y); - try expectEqual(@as(u0, 0), @shlExact(x, 0)); - try expectEqual(@as(u0, 0), @shrExact(x, 0)); - try expectEqual(@as(u0, 0), @shlExact(x, y)); - try expectEqual(@as(u0, 0), @shrExact(x, y)); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "comptime_int addition" { - comptime { - try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950); - try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380); - } -} - -test "comptime_int multiplication" { - comptime { - try expect( - 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567, - ); - try expect( - 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016, - ); - } -} - -test "comptime_int shifting" { - comptime { - try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000); - } -} - -test "comptime_int multi-limb shift and mask" { - comptime { - var a = 0xefffffffa0000001eeeeeeefaaaaaaab; - - try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab); - a >>= 32; - try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef); - a >>= 32; - try expect(@as(u32, a & 0xffffffff) == 0xa0000001); - a >>= 32; - try expect(@as(u32, a & 0xffffffff) == 0xefffffff); - a >>= 32; - - try expect(a == 0); - } -} - -test "comptime_int multi-limb partial shift right" { - comptime { - var a = 0x1ffffffffeeeeeeee; - a >>= 16; - try expect(a == 0x1ffffffffeeee); - } -} - -test "xor" { - try test_xor(); - comptime try test_xor(); -} - -fn test_xor() !void { - try expect(0xFF ^ 0x00 == 0xFF); - try expect(0xF0 ^ 0x0F == 0xFF); - try expect(0xFF ^ 0xF0 == 0x0F); - try expect(0xFF ^ 0x0F == 0xF0); - try expect(0xFF ^ 0xFF == 0x00); -} - -test "comptime_int xor" { - comptime { - try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF); - try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000); - try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000); - try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF); - try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000); - } -} - -test "f128" { - try test_f128(); - comptime try test_f128(); -} - -fn make_f128(x: f128) f128 { - return x; -} - -fn test_f128() !void { - try expect(@sizeOf(f128) == 16); - try expect(make_f128(1.0) == 1.0); - try expect(make_f128(1.0) != 1.1); - try expect(make_f128(1.0) > 0.9); - try expect(make_f128(1.0) >= 0.9); - try expect(make_f128(1.0) >= 1.0); - try should_not_be_zero(1.0); -} - -fn should_not_be_zero(x: f128) !void { - try expect(x != 0.0); -} - -test "comptime float rem int" { - comptime { - var x = @as(f32, 1) % 2; - try expect(x == 1.0); - } -} - -test "remainder division" { - comptime try remdiv(f16); - comptime try remdiv(f32); - comptime try remdiv(f64); - comptime try remdiv(f128); - try remdiv(f16); - try remdiv(f64); - try remdiv(f128); -} - -fn remdiv(comptime T: type) !void { - try expect(@as(T, 1) == @as(T, 1) % @as(T, 2)); - try expect(@as(T, 1) == @as(T, 7) % @as(T, 3)); -} - -test "@sqrt" { - try testSqrt(f64, 12.0); - comptime try testSqrt(f64, 12.0); - try testSqrt(f32, 13.0); - comptime try testSqrt(f32, 13.0); - try testSqrt(f16, 13.0); - comptime try testSqrt(f16, 13.0); - - const x = 14.0; - const y = x * x; - const z = @sqrt(y); - comptime try expect(z == x); -} - -fn testSqrt(comptime T: type, x: T) !void { - try expect(@sqrt(x * x) == x); -} - -test "@fabs" { - try testFabs(f128, 12.0); - comptime try testFabs(f128, 12.0); - try testFabs(f64, 12.0); - comptime try testFabs(f64, 12.0); - try testFabs(f32, 12.0); - comptime try testFabs(f32, 12.0); - try testFabs(f16, 12.0); - comptime try testFabs(f16, 12.0); - - const x = 14.0; - const y = -x; - const z = @fabs(y); - comptime try expectEqual(x, z); -} - -fn testFabs(comptime T: type, x: T) !void { - const y = -x; - const z = @fabs(y); - try expectEqual(x, z); -} - -test "@floor" { - // FIXME: Generates a floorl function call - // testFloor(f128, 12.0); - comptime try testFloor(f128, 12.0); - try testFloor(f64, 12.0); - comptime try testFloor(f64, 12.0); - try testFloor(f32, 12.0); - comptime try testFloor(f32, 12.0); - try testFloor(f16, 12.0); - comptime try testFloor(f16, 12.0); - - const x = 14.0; - const y = x + 0.7; - const z = @floor(y); - comptime try expectEqual(x, z); -} - -fn testFloor(comptime T: type, x: T) !void { - const y = x + 0.6; - const z = @floor(y); - try expectEqual(x, z); -} - -test "@ceil" { - // FIXME: Generates a ceill function call - //testCeil(f128, 12.0); - comptime try testCeil(f128, 12.0); - try testCeil(f64, 12.0); - comptime try testCeil(f64, 12.0); - try testCeil(f32, 12.0); - comptime try testCeil(f32, 12.0); - try testCeil(f16, 12.0); - comptime try testCeil(f16, 12.0); - - const x = 14.0; - const y = x - 0.7; - const z = @ceil(y); - comptime try expectEqual(x, z); -} - -fn testCeil(comptime T: type, x: T) !void { - const y = x - 0.8; - const z = @ceil(y); - try expectEqual(x, z); -} - -test "@trunc" { - // FIXME: Generates a truncl function call - //testTrunc(f128, 12.0); - comptime try testTrunc(f128, 12.0); - try testTrunc(f64, 12.0); - comptime try testTrunc(f64, 12.0); - try testTrunc(f32, 12.0); - comptime try testTrunc(f32, 12.0); - try testTrunc(f16, 12.0); - comptime try testTrunc(f16, 12.0); - - const x = 14.0; - const y = x + 0.7; - const z = @trunc(y); - comptime try expectEqual(x, z); -} - -fn testTrunc(comptime T: type, x: T) !void { - { - const y = x + 0.8; - const z = @trunc(y); - try expectEqual(x, z); - } - - { - const y = -x - 0.8; - const z = @trunc(y); - try expectEqual(-x, z); - } -} - -test "@round" { - // FIXME: Generates a roundl function call - //testRound(f128, 12.0); - comptime try testRound(f128, 12.0); - try testRound(f64, 12.0); - comptime try testRound(f64, 12.0); - try testRound(f32, 12.0); - comptime try testRound(f32, 12.0); - try testRound(f16, 12.0); - comptime try testRound(f16, 12.0); - - const x = 14.0; - const y = x + 0.4; - const z = @round(y); - comptime try expectEqual(x, z); -} - -fn testRound(comptime T: type, x: T) !void { - const y = x - 0.5; - const z = @round(y); - try expectEqual(x, z); -} - -test "comptime_int param and return" { - const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702); - try expect(a == 137114567242441932203689521744947848950); - - const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768); - try expect(b == 985095453608931032642182098849559179469148836107390954364380); -} - -fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int { - return a + b; -} - -test "vector integer addition" { - const S = struct { - fn doTheTest() !void { - var a: std.meta.Vector(4, i32) = [_]i32{ 1, 2, 3, 4 }; - var b: std.meta.Vector(4, i32) = [_]i32{ 5, 6, 7, 8 }; - var result = a + b; - var result_array: [4]i32 = result; - const expected = [_]i32{ 6, 8, 10, 12 }; - try expectEqualSlices(i32, &expected, &result_array); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "NaN comparison" { - try testNanEqNan(f16); - try testNanEqNan(f32); - try testNanEqNan(f64); - try testNanEqNan(f128); - comptime try testNanEqNan(f16); - comptime try testNanEqNan(f32); - comptime try testNanEqNan(f64); - comptime try testNanEqNan(f128); -} - -fn testNanEqNan(comptime F: type) !void { - var nan1 = std.math.nan(F); - var nan2 = std.math.nan(F); - try expect(nan1 != nan2); - try expect(!(nan1 == nan2)); - try expect(!(nan1 > nan2)); - try expect(!(nan1 >= nan2)); - try expect(!(nan1 < nan2)); - try expect(!(nan1 <= nan2)); -} - -test "128-bit multiplication" { - var a: i128 = 3; - var b: i128 = 2; - var c = a * b; - try expect(c == 6); -} - -test "vector comparison" { - const S = struct { - fn doTheTest() !void { - var a: std.meta.Vector(6, i32) = [_]i32{ 1, 3, -1, 5, 7, 9 }; - var b: std.meta.Vector(6, i32) = [_]i32{ -1, 3, 0, 6, 10, -10 }; - try expect(mem.eql(bool, &@as([6]bool, a < b), &[_]bool{ false, false, true, true, true, false })); - try expect(mem.eql(bool, &@as([6]bool, a <= b), &[_]bool{ false, true, true, true, true, false })); - try expect(mem.eql(bool, &@as([6]bool, a == b), &[_]bool{ false, true, false, false, false, false })); - try expect(mem.eql(bool, &@as([6]bool, a != b), &[_]bool{ true, false, true, true, true, true })); - try expect(mem.eql(bool, &@as([6]bool, a > b), &[_]bool{ true, false, false, false, false, true })); - try expect(mem.eql(bool, &@as([6]bool, a >= b), &[_]bool{ true, true, false, false, false, true })); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "compare undefined literal with comptime_int" { - var x = undefined == 1; - // x is now undefined with type bool - x = true; - try expect(x); -} - -test "signed zeros are represented properly" { - const S = struct { - fn doTheTest() !void { - inline for ([_]type{ f16, f32, f64, f128 }) |T| { - const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); - var as_fp_val = -@as(T, 0.0); - var as_uint_val = @bitCast(ST, as_fp_val); - // Ensure the sign bit is set. - try expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1); - } - } - }; - - try S.doTheTest(); - comptime try S.doTheTest(); -} diff --git a/test/behavior/math_stage1.zig b/test/behavior/math_stage1.zig new file mode 100644 index 0000000000..9f412930b5 --- /dev/null +++ b/test/behavior/math_stage1.zig @@ -0,0 +1,855 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualSlices = std.testing.expectEqualSlices; +const maxInt = std.math.maxInt; +const minInt = std.math.minInt; +const mem = std.mem; + +test "division" { + try testDivision(); + comptime try testDivision(); +} +fn testDivision() !void { + try expect(div(u32, 13, 3) == 4); + try expect(div(f16, 1.0, 2.0) == 0.5); + try expect(div(f32, 1.0, 2.0) == 0.5); + + try expect(divExact(u32, 55, 11) == 5); + try expect(divExact(i32, -55, 11) == -5); + try expect(divExact(f16, 55.0, 11.0) == 5.0); + try expect(divExact(f16, -55.0, 11.0) == -5.0); + try expect(divExact(f32, 55.0, 11.0) == 5.0); + try expect(divExact(f32, -55.0, 11.0) == -5.0); + + try expect(divFloor(i32, 5, 3) == 1); + try expect(divFloor(i32, -5, 3) == -2); + try expect(divFloor(f16, 5.0, 3.0) == 1.0); + try expect(divFloor(f16, -5.0, 3.0) == -2.0); + try expect(divFloor(f32, 5.0, 3.0) == 1.0); + try expect(divFloor(f32, -5.0, 3.0) == -2.0); + try expect(divFloor(i32, -0x80000000, -2) == 0x40000000); + try expect(divFloor(i32, 0, -0x80000000) == 0); + try expect(divFloor(i32, -0x40000001, 0x40000000) == -2); + try expect(divFloor(i32, -0x80000000, 1) == -0x80000000); + try expect(divFloor(i32, 10, 12) == 0); + try expect(divFloor(i32, -14, 12) == -2); + try expect(divFloor(i32, -2, 12) == -1); + + try expect(divTrunc(i32, 5, 3) == 1); + try expect(divTrunc(i32, -5, 3) == -1); + try expect(divTrunc(f16, 5.0, 3.0) == 1.0); + try expect(divTrunc(f16, -5.0, 3.0) == -1.0); + try expect(divTrunc(f32, 5.0, 3.0) == 1.0); + try expect(divTrunc(f32, -5.0, 3.0) == -1.0); + try expect(divTrunc(f64, 5.0, 3.0) == 1.0); + try expect(divTrunc(f64, -5.0, 3.0) == -1.0); + try expect(divTrunc(i32, 10, 12) == 0); + try expect(divTrunc(i32, -14, 12) == -1); + try expect(divTrunc(i32, -2, 12) == 0); + + try expect(mod(i32, 10, 12) == 10); + try expect(mod(i32, -14, 12) == 10); + try expect(mod(i32, -2, 12) == 10); + + comptime { + try expect( + 1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600, + ); + try expect( + @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600, + ); + try expect( + 1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2, + ); + try expect( + @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2, + ); + try expect( + @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2, + ); + try expect( + @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2, + ); + try expect( + 4126227191251978491697987544882340798050766755606969681711 % 10 == 1, + ); + } +} +fn div(comptime T: type, a: T, b: T) T { + return a / b; +} +fn divExact(comptime T: type, a: T, b: T) T { + return @divExact(a, b); +} +fn divFloor(comptime T: type, a: T, b: T) T { + return @divFloor(a, b); +} +fn divTrunc(comptime T: type, a: T, b: T) T { + return @divTrunc(a, b); +} +fn mod(comptime T: type, a: T, b: T) T { + return @mod(a, b); +} + +test "@addWithOverflow" { + var result: u8 = undefined; + try expect(@addWithOverflow(u8, 250, 100, &result)); + try expect(!@addWithOverflow(u8, 100, 150, &result)); + try expect(result == 250); +} + +// TODO test mulWithOverflow +// TODO test subWithOverflow + +test "@shlWithOverflow" { + var result: u16 = undefined; + try expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result)); + try expect(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result)); + try expect(result == 0b1011111111111100); +} + +test "@*WithOverflow with u0 values" { + var result: u0 = undefined; + try expect(!@addWithOverflow(u0, 0, 0, &result)); + try expect(!@subWithOverflow(u0, 0, 0, &result)); + try expect(!@mulWithOverflow(u0, 0, 0, &result)); + try expect(!@shlWithOverflow(u0, 0, 0, &result)); +} + +test "@clz" { + try testClz(); + comptime try testClz(); +} + +fn testClz() !void { + try expect(@clz(u8, 0b10001010) == 0); + try expect(@clz(u8, 0b00001010) == 4); + try expect(@clz(u8, 0b00011010) == 3); + try expect(@clz(u8, 0b00000000) == 8); + try expect(@clz(u128, 0xffffffffffffffff) == 64); + try expect(@clz(u128, 0x10000000000000000) == 63); +} + +test "@clz vectors" { + try testClzVectors(); + comptime try testClzVectors(); +} + +fn testClzVectors() !void { + @setEvalBranchQuota(10_000); + try expectEqual(@clz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 0))); + try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00001010))), @splat(64, @as(u4, 4))); + try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00011010))), @splat(64, @as(u4, 3))); + try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8))); + try expectEqual(@clz(u128, @splat(64, @as(u128, 0xffffffffffffffff))), @splat(64, @as(u8, 64))); + try expectEqual(@clz(u128, @splat(64, @as(u128, 0x10000000000000000))), @splat(64, @as(u8, 63))); +} + +test "@ctz" { + try testCtz(); + comptime try testCtz(); +} + +fn testCtz() !void { + try expect(@ctz(u8, 0b10100000) == 5); + try expect(@ctz(u8, 0b10001010) == 1); + try expect(@ctz(u8, 0b00000000) == 8); + try expect(@ctz(u16, 0b00000000) == 16); +} + +test "@ctz vectors" { + try testClzVectors(); + comptime try testClzVectors(); +} + +fn testCtzVectors() !void { + @setEvalBranchQuota(10_000); + try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10100000))), @splat(64, @as(u4, 5))); + try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 1))); + try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8))); + try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16))); +} + +test "const number literal" { + const one = 1; + const eleven = ten + one; + + try expect(eleven == 11); +} +const ten = 10; + +test "unsigned wrapping" { + try testUnsignedWrappingEval(maxInt(u32)); + comptime try testUnsignedWrappingEval(maxInt(u32)); +} +fn testUnsignedWrappingEval(x: u32) !void { + const zero = x +% 1; + try expect(zero == 0); + const orig = zero -% 1; + try expect(orig == maxInt(u32)); +} + +test "signed wrapping" { + try testSignedWrappingEval(maxInt(i32)); + comptime try testSignedWrappingEval(maxInt(i32)); +} +fn testSignedWrappingEval(x: i32) !void { + const min_val = x +% 1; + try expect(min_val == minInt(i32)); + const max_val = min_val -% 1; + try expect(max_val == maxInt(i32)); +} + +test "signed negation wrapping" { + try testSignedNegationWrappingEval(minInt(i16)); + comptime try testSignedNegationWrappingEval(minInt(i16)); +} +fn testSignedNegationWrappingEval(x: i16) !void { + try expect(x == -32768); + const neg = -%x; + try expect(neg == -32768); +} + +test "unsigned negation wrapping" { + try testUnsignedNegationWrappingEval(1); + comptime try testUnsignedNegationWrappingEval(1); +} +fn testUnsignedNegationWrappingEval(x: u16) !void { + try expect(x == 1); + const neg = -%x; + try expect(neg == maxInt(u16)); +} + +test "unsigned 64-bit division" { + try test_u64_div(); + comptime try test_u64_div(); +} +fn test_u64_div() !void { + const result = divWithResult(1152921504606846976, 34359738365); + try expect(result.quotient == 33554432); + try expect(result.remainder == 100663296); +} +fn divWithResult(a: u64, b: u64) DivResult { + return DivResult{ + .quotient = a / b, + .remainder = a % b, + }; +} +const DivResult = struct { + quotient: u64, + remainder: u64, +}; + +test "binary not" { + try expect(comptime x: { + break :x ~@as(u16, 0b1010101010101010) == 0b0101010101010101; + }); + try expect(comptime x: { + break :x ~@as(u64, 2147483647) == 18446744071562067968; + }); + try testBinaryNot(0b1010101010101010); +} + +fn testBinaryNot(x: u16) !void { + try expect(~x == 0b0101010101010101); +} + +test "small int addition" { + var x: u2 = 0; + try expect(x == 0); + + x += 1; + try expect(x == 1); + + x += 1; + try expect(x == 2); + + x += 1; + try expect(x == 3); + + var result: @TypeOf(x) = 3; + try expect(@addWithOverflow(@TypeOf(x), x, 1, &result)); + + try expect(result == 0); +} + +test "float equality" { + const x: f64 = 0.012; + const y: f64 = x + 1.0; + + try testFloatEqualityImpl(x, y); + comptime try testFloatEqualityImpl(x, y); +} + +fn testFloatEqualityImpl(x: f64, y: f64) !void { + const y2 = x + 1.0; + try expect(y == y2); +} + +test "allow signed integer division/remainder when values are comptime known and positive or exact" { + try expect(5 / 3 == 1); + try expect(-5 / -3 == 1); + try expect(-6 / 3 == -2); + + try expect(5 % 3 == 2); + try expect(-6 % 3 == 0); +} + +test "hex float literal parsing" { + comptime try expect(0x1.0 == 1.0); +} + +test "quad hex float literal parsing in range" { + const a = 0x1.af23456789bbaaab347645365cdep+5; + const b = 0x1.dedafcff354b6ae9758763545432p-9; + const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534; + const d = 0x1.edcbff8ad76ab5bf46463233214fp-435; + if (false) { + a; + b; + c; + d; + } +} + +test "quad hex float literal parsing accurate" { + const a: f128 = 0x1.1111222233334444555566667777p+0; + + // implied 1 is dropped, with an exponent of 0 (0x3fff) after biasing. + const expected: u128 = 0x3fff1111222233334444555566667777; + try expect(@bitCast(u128, a) == expected); + + // non-normalized + const b: f128 = 0x11.111222233334444555566667777p-4; + try expect(@bitCast(u128, b) == expected); + + const S = struct { + fn doTheTest() !void { + { + var f: f128 = 0x1.2eab345678439abcdefea56782346p+5; + try expect(@bitCast(u128, f) == 0x40042eab345678439abcdefea5678234); + } + { + var f: f128 = 0x1.edcb34a235253948765432134674fp-1; + try expect(@bitCast(u128, f) == 0x3ffeedcb34a235253948765432134674); + } + { + var f: f128 = 0x1.353e45674d89abacc3a2ebf3ff4ffp-50; + try expect(@bitCast(u128, f) == 0x3fcd353e45674d89abacc3a2ebf3ff50); + } + { + var f: f128 = 0x1.ed8764648369535adf4be3214567fp-9; + try expect(@bitCast(u128, f) == 0x3ff6ed8764648369535adf4be3214568); + } + const exp2ft = [_]f64{ + 0x1.6a09e667f3bcdp-1, + 0x1.7a11473eb0187p-1, + 0x1.8ace5422aa0dbp-1, + 0x1.9c49182a3f090p-1, + 0x1.ae89f995ad3adp-1, + 0x1.c199bdd85529cp-1, + 0x1.d5818dcfba487p-1, + 0x1.ea4afa2a490dap-1, + 0x1.0000000000000p+0, + 0x1.0b5586cf9890fp+0, + 0x1.172b83c7d517bp+0, + 0x1.2387a6e756238p+0, + 0x1.306fe0a31b715p+0, + 0x1.3dea64c123422p+0, + 0x1.4bfdad5362a27p+0, + 0x1.5ab07dd485429p+0, + 0x1.8p23, + 0x1.62e430p-1, + 0x1.ebfbe0p-3, + 0x1.c6b348p-5, + 0x1.3b2c9cp-7, + 0x1.0p127, + -0x1.0p-149, + }; + + const answers = [_]u64{ + 0x3fe6a09e667f3bcd, + 0x3fe7a11473eb0187, + 0x3fe8ace5422aa0db, + 0x3fe9c49182a3f090, + 0x3feae89f995ad3ad, + 0x3fec199bdd85529c, + 0x3fed5818dcfba487, + 0x3feea4afa2a490da, + 0x3ff0000000000000, + 0x3ff0b5586cf9890f, + 0x3ff172b83c7d517b, + 0x3ff2387a6e756238, + 0x3ff306fe0a31b715, + 0x3ff3dea64c123422, + 0x3ff4bfdad5362a27, + 0x3ff5ab07dd485429, + 0x4168000000000000, + 0x3fe62e4300000000, + 0x3fcebfbe00000000, + 0x3fac6b3480000000, + 0x3f83b2c9c0000000, + 0x47e0000000000000, + 0xb6a0000000000000, + }; + + for (exp2ft) |x, i| { + try expect(@bitCast(u64, x) == answers[i]); + } + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "underscore separator parsing" { + try expect(0_0_0_0 == 0); + try expect(1_234_567 == 1234567); + try expect(001_234_567 == 1234567); + try expect(0_0_1_2_3_4_5_6_7 == 1234567); + + try expect(0b0_0_0_0 == 0); + try expect(0b1010_1010 == 0b10101010); + try expect(0b0000_1010_1010 == 0b10101010); + try expect(0b1_0_1_0_1_0_1_0 == 0b10101010); + + try expect(0o0_0_0_0 == 0); + try expect(0o1010_1010 == 0o10101010); + try expect(0o0000_1010_1010 == 0o10101010); + try expect(0o1_0_1_0_1_0_1_0 == 0o10101010); + + try expect(0x0_0_0_0 == 0); + try expect(0x1010_1010 == 0x10101010); + try expect(0x0000_1010_1010 == 0x10101010); + try expect(0x1_0_1_0_1_0_1_0 == 0x10101010); + + try expect(123_456.789_000e1_0 == 123456.789000e10); + try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10); + + try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10); + try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10); +} + +test "hex float literal within range" { + const a = 0x1.0p16383; + const b = 0x0.1p16387; + const c = 0x1.0p-16382; + if (false) { + a; + b; + c; + } +} + +test "truncating shift left" { + try testShlTrunc(maxInt(u16)); + comptime try testShlTrunc(maxInt(u16)); +} +fn testShlTrunc(x: u16) !void { + const shifted = x << 1; + try expect(shifted == 65534); +} + +test "truncating shift right" { + try testShrTrunc(maxInt(u16)); + comptime try testShrTrunc(maxInt(u16)); +} +fn testShrTrunc(x: u16) !void { + const shifted = x >> 1; + try expect(shifted == 32767); +} + +test "exact shift left" { + try testShlExact(0b00110101); + comptime try testShlExact(0b00110101); +} +fn testShlExact(x: u8) !void { + const shifted = @shlExact(x, 2); + try expect(shifted == 0b11010100); +} + +test "exact shift right" { + try testShrExact(0b10110100); + comptime try testShrExact(0b10110100); +} +fn testShrExact(x: u8) !void { + const shifted = @shrExact(x, 2); + try expect(shifted == 0b00101101); +} + +test "shift left/right on u0 operand" { + const S = struct { + fn doTheTest() !void { + var x: u0 = 0; + var y: u0 = 0; + try expectEqual(@as(u0, 0), x << 0); + try expectEqual(@as(u0, 0), x >> 0); + try expectEqual(@as(u0, 0), x << y); + try expectEqual(@as(u0, 0), x >> y); + try expectEqual(@as(u0, 0), @shlExact(x, 0)); + try expectEqual(@as(u0, 0), @shrExact(x, 0)); + try expectEqual(@as(u0, 0), @shlExact(x, y)); + try expectEqual(@as(u0, 0), @shrExact(x, y)); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "comptime_int addition" { + comptime { + try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950); + try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380); + } +} + +test "comptime_int multiplication" { + comptime { + try expect( + 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567, + ); + try expect( + 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016, + ); + } +} + +test "comptime_int shifting" { + comptime { + try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000); + } +} + +test "comptime_int multi-limb shift and mask" { + comptime { + var a = 0xefffffffa0000001eeeeeeefaaaaaaab; + + try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab); + a >>= 32; + try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef); + a >>= 32; + try expect(@as(u32, a & 0xffffffff) == 0xa0000001); + a >>= 32; + try expect(@as(u32, a & 0xffffffff) == 0xefffffff); + a >>= 32; + + try expect(a == 0); + } +} + +test "comptime_int multi-limb partial shift right" { + comptime { + var a = 0x1ffffffffeeeeeeee; + a >>= 16; + try expect(a == 0x1ffffffffeeee); + } +} + +test "xor" { + try test_xor(); + comptime try test_xor(); +} + +fn test_xor() !void { + try expect(0xFF ^ 0x00 == 0xFF); + try expect(0xF0 ^ 0x0F == 0xFF); + try expect(0xFF ^ 0xF0 == 0x0F); + try expect(0xFF ^ 0x0F == 0xF0); + try expect(0xFF ^ 0xFF == 0x00); +} + +test "comptime_int xor" { + comptime { + try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF); + try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000); + try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000); + try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF); + try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000); + } +} + +test "f128" { + try test_f128(); + comptime try test_f128(); +} + +fn make_f128(x: f128) f128 { + return x; +} + +fn test_f128() !void { + try expect(@sizeOf(f128) == 16); + try expect(make_f128(1.0) == 1.0); + try expect(make_f128(1.0) != 1.1); + try expect(make_f128(1.0) > 0.9); + try expect(make_f128(1.0) >= 0.9); + try expect(make_f128(1.0) >= 1.0); + try should_not_be_zero(1.0); +} + +fn should_not_be_zero(x: f128) !void { + try expect(x != 0.0); +} + +test "comptime float rem int" { + comptime { + var x = @as(f32, 1) % 2; + try expect(x == 1.0); + } +} + +test "remainder division" { + comptime try remdiv(f16); + comptime try remdiv(f32); + comptime try remdiv(f64); + comptime try remdiv(f128); + try remdiv(f16); + try remdiv(f64); + try remdiv(f128); +} + +fn remdiv(comptime T: type) !void { + try expect(@as(T, 1) == @as(T, 1) % @as(T, 2)); + try expect(@as(T, 1) == @as(T, 7) % @as(T, 3)); +} + +test "@sqrt" { + try testSqrt(f64, 12.0); + comptime try testSqrt(f64, 12.0); + try testSqrt(f32, 13.0); + comptime try testSqrt(f32, 13.0); + try testSqrt(f16, 13.0); + comptime try testSqrt(f16, 13.0); + + const x = 14.0; + const y = x * x; + const z = @sqrt(y); + comptime try expect(z == x); +} + +fn testSqrt(comptime T: type, x: T) !void { + try expect(@sqrt(x * x) == x); +} + +test "@fabs" { + try testFabs(f128, 12.0); + comptime try testFabs(f128, 12.0); + try testFabs(f64, 12.0); + comptime try testFabs(f64, 12.0); + try testFabs(f32, 12.0); + comptime try testFabs(f32, 12.0); + try testFabs(f16, 12.0); + comptime try testFabs(f16, 12.0); + + const x = 14.0; + const y = -x; + const z = @fabs(y); + comptime try expectEqual(x, z); +} + +fn testFabs(comptime T: type, x: T) !void { + const y = -x; + const z = @fabs(y); + try expectEqual(x, z); +} + +test "@floor" { + // FIXME: Generates a floorl function call + // testFloor(f128, 12.0); + comptime try testFloor(f128, 12.0); + try testFloor(f64, 12.0); + comptime try testFloor(f64, 12.0); + try testFloor(f32, 12.0); + comptime try testFloor(f32, 12.0); + try testFloor(f16, 12.0); + comptime try testFloor(f16, 12.0); + + const x = 14.0; + const y = x + 0.7; + const z = @floor(y); + comptime try expectEqual(x, z); +} + +fn testFloor(comptime T: type, x: T) !void { + const y = x + 0.6; + const z = @floor(y); + try expectEqual(x, z); +} + +test "@ceil" { + // FIXME: Generates a ceill function call + //testCeil(f128, 12.0); + comptime try testCeil(f128, 12.0); + try testCeil(f64, 12.0); + comptime try testCeil(f64, 12.0); + try testCeil(f32, 12.0); + comptime try testCeil(f32, 12.0); + try testCeil(f16, 12.0); + comptime try testCeil(f16, 12.0); + + const x = 14.0; + const y = x - 0.7; + const z = @ceil(y); + comptime try expectEqual(x, z); +} + +fn testCeil(comptime T: type, x: T) !void { + const y = x - 0.8; + const z = @ceil(y); + try expectEqual(x, z); +} + +test "@trunc" { + // FIXME: Generates a truncl function call + //testTrunc(f128, 12.0); + comptime try testTrunc(f128, 12.0); + try testTrunc(f64, 12.0); + comptime try testTrunc(f64, 12.0); + try testTrunc(f32, 12.0); + comptime try testTrunc(f32, 12.0); + try testTrunc(f16, 12.0); + comptime try testTrunc(f16, 12.0); + + const x = 14.0; + const y = x + 0.7; + const z = @trunc(y); + comptime try expectEqual(x, z); +} + +fn testTrunc(comptime T: type, x: T) !void { + { + const y = x + 0.8; + const z = @trunc(y); + try expectEqual(x, z); + } + + { + const y = -x - 0.8; + const z = @trunc(y); + try expectEqual(-x, z); + } +} + +test "@round" { + // FIXME: Generates a roundl function call + //testRound(f128, 12.0); + comptime try testRound(f128, 12.0); + try testRound(f64, 12.0); + comptime try testRound(f64, 12.0); + try testRound(f32, 12.0); + comptime try testRound(f32, 12.0); + try testRound(f16, 12.0); + comptime try testRound(f16, 12.0); + + const x = 14.0; + const y = x + 0.4; + const z = @round(y); + comptime try expectEqual(x, z); +} + +fn testRound(comptime T: type, x: T) !void { + const y = x - 0.5; + const z = @round(y); + try expectEqual(x, z); +} + +test "comptime_int param and return" { + const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702); + try expect(a == 137114567242441932203689521744947848950); + + const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768); + try expect(b == 985095453608931032642182098849559179469148836107390954364380); +} + +fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int { + return a + b; +} + +test "vector integer addition" { + const S = struct { + fn doTheTest() !void { + var a: std.meta.Vector(4, i32) = [_]i32{ 1, 2, 3, 4 }; + var b: std.meta.Vector(4, i32) = [_]i32{ 5, 6, 7, 8 }; + var result = a + b; + var result_array: [4]i32 = result; + const expected = [_]i32{ 6, 8, 10, 12 }; + try expectEqualSlices(i32, &expected, &result_array); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "NaN comparison" { + try testNanEqNan(f16); + try testNanEqNan(f32); + try testNanEqNan(f64); + try testNanEqNan(f128); + comptime try testNanEqNan(f16); + comptime try testNanEqNan(f32); + comptime try testNanEqNan(f64); + comptime try testNanEqNan(f128); +} + +fn testNanEqNan(comptime F: type) !void { + var nan1 = std.math.nan(F); + var nan2 = std.math.nan(F); + try expect(nan1 != nan2); + try expect(!(nan1 == nan2)); + try expect(!(nan1 > nan2)); + try expect(!(nan1 >= nan2)); + try expect(!(nan1 < nan2)); + try expect(!(nan1 <= nan2)); +} + +test "128-bit multiplication" { + var a: i128 = 3; + var b: i128 = 2; + var c = a * b; + try expect(c == 6); +} + +test "vector comparison" { + const S = struct { + fn doTheTest() !void { + var a: std.meta.Vector(6, i32) = [_]i32{ 1, 3, -1, 5, 7, 9 }; + var b: std.meta.Vector(6, i32) = [_]i32{ -1, 3, 0, 6, 10, -10 }; + try expect(mem.eql(bool, &@as([6]bool, a < b), &[_]bool{ false, false, true, true, true, false })); + try expect(mem.eql(bool, &@as([6]bool, a <= b), &[_]bool{ false, true, true, true, true, false })); + try expect(mem.eql(bool, &@as([6]bool, a == b), &[_]bool{ false, true, false, false, false, false })); + try expect(mem.eql(bool, &@as([6]bool, a != b), &[_]bool{ true, false, true, true, true, true })); + try expect(mem.eql(bool, &@as([6]bool, a > b), &[_]bool{ true, false, false, false, false, true })); + try expect(mem.eql(bool, &@as([6]bool, a >= b), &[_]bool{ true, true, false, false, false, true })); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "compare undefined literal with comptime_int" { + var x = undefined == 1; + // x is now undefined with type bool + x = true; + try expect(x); +} + +test "signed zeros are represented properly" { + const S = struct { + fn doTheTest() !void { + inline for ([_]type{ f16, f32, f64, f128 }) |T| { + const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); + var as_fp_val = -@as(T, 0.0); + var as_uint_val = @bitCast(ST, as_fp_val); + // Ensure the sign bit is set. + try expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1); + } + } + }; + + try S.doTheTest(); + comptime try S.doTheTest(); +} From 29f41896ed9d99e82a88f4b63efa182ca0d2f93c Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Thu, 2 Sep 2021 13:50:24 -0700 Subject: [PATCH 129/160] sat-arithmetic: add operator support - adds initial support for the operators +|, -|, *|, <<|, +|=, -|=, *|=, <<|= - uses operators in addition to builtins in behavior test - adds binOpExt() and assignBinOpExt() to AstGen.zig. these need to be audited --- lib/std/zig/Ast.zig | 32 ++++++ lib/std/zig/parse.zig | 8 ++ lib/std/zig/render.zig | 8 ++ lib/std/zig/tokenizer.zig | 79 +++++++++++++++ src/Air.zig | 22 +++++ src/AstGen.zig | 124 +++++++++++++++++++++++- src/Liveness.zig | 4 + src/codegen.zig | 12 +++ src/codegen/c.zig | 3 + src/codegen/llvm.zig | 66 +++++++++---- src/codegen/llvm/bindings.zig | 24 +++++ src/print_air.zig | 4 + src/stage1/all_types.hpp | 16 ++- src/stage1/astgen.cpp | 24 ++++- src/stage1/codegen.cpp | 8 +- src/stage1/ir.cpp | 24 ++--- src/stage1/ir_print.cpp | 8 +- src/stage1/parser.cpp | 16 +++ src/stage1/tokenizer.cpp | 85 ++++++++++++++++ src/stage1/tokenizer.hpp | 8 ++ test/behavior/saturating_arithmetic.zig | 35 +++++-- 21 files changed, 556 insertions(+), 54 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 5838dcd37a..3632551d17 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -396,6 +396,7 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex { .assign_add, .assign_sub, .assign_bit_shift_left, + .assign_bit_shift_left_sat, .assign_bit_shift_right, .assign_bit_and, .assign_bit_xor, @@ -403,6 +404,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex { .assign_mul_wrap, .assign_add_wrap, .assign_sub_wrap, + .assign_mul_sat, + .assign_add_sat, + .assign_sub_sat, .assign, .merge_error_sets, .mul, @@ -410,12 +414,16 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex { .mod, .array_mult, .mul_wrap, + .mul_sat, .add, .sub, .array_cat, .add_wrap, .sub_wrap, + .add_sat, + .sub_sat, .bit_shift_left, + .bit_shift_left_sat, .bit_shift_right, .bit_and, .bit_xor, @@ -652,6 +660,7 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { .assign_add, .assign_sub, .assign_bit_shift_left, + .assign_bit_shift_left_sat, .assign_bit_shift_right, .assign_bit_and, .assign_bit_xor, @@ -659,6 +668,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { .assign_mul_wrap, .assign_add_wrap, .assign_sub_wrap, + .assign_mul_sat, + .assign_add_sat, + .assign_sub_sat, .assign, .merge_error_sets, .mul, @@ -666,12 +678,16 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { .mod, .array_mult, .mul_wrap, + .mul_sat, .add, .sub, .array_cat, .add_wrap, .sub_wrap, + .add_sat, + .sub_sat, .bit_shift_left, + .bit_shift_left_sat, .bit_shift_right, .bit_and, .bit_xor, @@ -2525,6 +2541,8 @@ pub const Node = struct { assign_sub, /// `lhs <<= rhs`. main_token is op. assign_bit_shift_left, + /// `lhs <<|= rhs`. main_token is op. + assign_bit_shift_left_sat, /// `lhs >>= rhs`. main_token is op. assign_bit_shift_right, /// `lhs &= rhs`. main_token is op. @@ -2539,6 +2557,12 @@ pub const Node = struct { assign_add_wrap, /// `lhs -%= rhs`. main_token is op. assign_sub_wrap, + /// `lhs *|= rhs`. main_token is op. + assign_mul_sat, + /// `lhs +|= rhs`. main_token is op. + assign_add_sat, + /// `lhs -|= rhs`. main_token is op. + assign_sub_sat, /// `lhs = rhs`. main_token is op. assign, /// `lhs || rhs`. main_token is the `||`. @@ -2553,6 +2577,8 @@ pub const Node = struct { array_mult, /// `lhs *% rhs`. main_token is the `*%`. mul_wrap, + /// `lhs *| rhs`. main_token is the `*%`. + mul_sat, /// `lhs + rhs`. main_token is the `+`. add, /// `lhs - rhs`. main_token is the `-`. @@ -2563,8 +2589,14 @@ pub const Node = struct { add_wrap, /// `lhs -% rhs`. main_token is the `-%`. sub_wrap, + /// `lhs +| rhs`. main_token is the `+|`. + add_sat, + /// `lhs -| rhs`. main_token is the `-|`. + sub_sat, /// `lhs << rhs`. main_token is the `<<`. bit_shift_left, + /// `lhs <<| rhs`. main_token is the `<<|`. + bit_shift_left_sat, /// `lhs >> rhs`. main_token is the `>>`. bit_shift_right, /// `lhs & rhs`. main_token is the `&`. diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index f7697027a3..a2780b5225 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1269,6 +1269,7 @@ const Parser = struct { .plus_equal => .assign_add, .minus_equal => .assign_sub, .angle_bracket_angle_bracket_left_equal => .assign_bit_shift_left, + .angle_bracket_angle_bracket_left_pipe_equal => .assign_bit_shift_left_sat, .angle_bracket_angle_bracket_right_equal => .assign_bit_shift_right, .ampersand_equal => .assign_bit_and, .caret_equal => .assign_bit_xor, @@ -1276,6 +1277,9 @@ const Parser = struct { .asterisk_percent_equal => .assign_mul_wrap, .plus_percent_equal => .assign_add_wrap, .minus_percent_equal => .assign_sub_wrap, + .asterisk_pipe_equal => .assign_mul_sat, + .plus_pipe_equal => .assign_add_sat, + .minus_pipe_equal => .assign_sub_sat, .equal => .assign, else => return expr, }; @@ -1343,6 +1347,7 @@ const Parser = struct { .keyword_catch = .{ .prec = 40, .tag = .@"catch" }, .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .bit_shift_left }, + .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .bit_shift_left_sat }, .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .bit_shift_right }, .plus = .{ .prec = 60, .tag = .add }, @@ -1350,6 +1355,8 @@ const Parser = struct { .plus_plus = .{ .prec = 60, .tag = .array_cat }, .plus_percent = .{ .prec = 60, .tag = .add_wrap }, .minus_percent = .{ .prec = 60, .tag = .sub_wrap }, + .plus_pipe = .{ .prec = 60, .tag = .add_sat }, + .minus_pipe = .{ .prec = 60, .tag = .sub_sat }, .pipe_pipe = .{ .prec = 70, .tag = .merge_error_sets }, .asterisk = .{ .prec = 70, .tag = .mul }, @@ -1357,6 +1364,7 @@ const Parser = struct { .percent = .{ .prec = 70, .tag = .mod }, .asterisk_asterisk = .{ .prec = 70, .tag = .array_mult }, .asterisk_percent = .{ .prec = 70, .tag = .mul_wrap }, + .asterisk_pipe = .{ .prec = 70, .tag = .mul_sat }, }); fn parseExprPrecedence(p: *Parser, min_prec: i32) Error!Node.Index { diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 3029d38cb9..47f019d1cf 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -333,26 +333,32 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, .add, .add_wrap, + .add_sat, .array_cat, .array_mult, .assign, .assign_bit_and, .assign_bit_or, .assign_bit_shift_left, + .assign_bit_shift_left_sat, .assign_bit_shift_right, .assign_bit_xor, .assign_div, .assign_sub, .assign_sub_wrap, + .assign_sub_sat, .assign_mod, .assign_add, .assign_add_wrap, + .assign_add_sat, .assign_mul, .assign_mul_wrap, + .assign_mul_sat, .bang_equal, .bit_and, .bit_or, .bit_shift_left, + .bit_shift_left_sat, .bit_shift_right, .bit_xor, .bool_and, @@ -367,8 +373,10 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, .mod, .mul, .mul_wrap, + .mul_sat, .sub, .sub_wrap, + .sub_sat, .@"orelse", => { const infix = datas[node]; diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 3ef6c9a6ba..6afe7750d3 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -103,15 +103,21 @@ pub const Token = struct { plus_equal, plus_percent, plus_percent_equal, + plus_pipe, + plus_pipe_equal, minus, minus_equal, minus_percent, minus_percent_equal, + minus_pipe, + minus_pipe_equal, asterisk, asterisk_equal, asterisk_asterisk, asterisk_percent, asterisk_percent_equal, + asterisk_pipe, + asterisk_pipe_equal, arrow, colon, slash, @@ -124,6 +130,8 @@ pub const Token = struct { angle_bracket_left_equal, angle_bracket_angle_bracket_left, angle_bracket_angle_bracket_left_equal, + angle_bracket_angle_bracket_left_pipe, + angle_bracket_angle_bracket_left_pipe_equal, angle_bracket_right, angle_bracket_right_equal, angle_bracket_angle_bracket_right, @@ -227,15 +235,21 @@ pub const Token = struct { .plus_equal => "+=", .plus_percent => "+%", .plus_percent_equal => "+%=", + .plus_pipe => "+|", + .plus_pipe_equal => "+|=", .minus => "-", .minus_equal => "-=", .minus_percent => "-%", .minus_percent_equal => "-%=", + .minus_pipe => "-|", + .minus_pipe_equal => "-|=", .asterisk => "*", .asterisk_equal => "*=", .asterisk_asterisk => "**", .asterisk_percent => "*%", .asterisk_percent_equal => "*%=", + .asterisk_pipe => "*|", + .asterisk_pipe_equal => "*|=", .arrow => "->", .colon => ":", .slash => "/", @@ -248,6 +262,8 @@ pub const Token = struct { .angle_bracket_left_equal => "<=", .angle_bracket_angle_bracket_left => "<<", .angle_bracket_angle_bracket_left_equal => "<<=", + .angle_bracket_angle_bracket_left_pipe => "<<|", + .angle_bracket_angle_bracket_left_pipe_equal => "<<|=", .angle_bracket_right => ">", .angle_bracket_right_equal => ">=", .angle_bracket_angle_bracket_right => ">>", @@ -352,8 +368,10 @@ pub const Tokenizer = struct { pipe, minus, minus_percent, + minus_pipe, asterisk, asterisk_percent, + asterisk_pipe, slash, line_comment_start, line_comment, @@ -382,8 +400,10 @@ pub const Tokenizer = struct { percent, plus, plus_percent, + plus_pipe, angle_bracket_left, angle_bracket_angle_bracket_left, + angle_bracket_angle_bracket_left_pipe, angle_bracket_right, angle_bracket_angle_bracket_right, period, @@ -584,6 +604,9 @@ pub const Tokenizer = struct { '%' => { state = .asterisk_percent; }, + '|' => { + state = .asterisk_pipe; + }, else => { result.tag = .asterisk; break; @@ -602,6 +625,18 @@ pub const Tokenizer = struct { }, }, + .asterisk_pipe => switch (c) { + '=' => { + result.tag = .asterisk_pipe_equal; + self.index += 1; + break; + }, + else => { + result.tag = .asterisk_pipe; + break; + }, + }, + .percent => switch (c) { '=' => { result.tag = .percent_equal; @@ -628,6 +663,9 @@ pub const Tokenizer = struct { '%' => { state = .plus_percent; }, + '|' => { + state = .plus_pipe; + }, else => { result.tag = .plus; break; @@ -646,6 +684,18 @@ pub const Tokenizer = struct { }, }, + .plus_pipe => switch (c) { + '=' => { + result.tag = .plus_pipe_equal; + self.index += 1; + break; + }, + else => { + result.tag = .plus_pipe; + break; + }, + }, + .caret => switch (c) { '=' => { result.tag = .caret_equal; @@ -903,6 +953,9 @@ pub const Tokenizer = struct { '%' => { state = .minus_percent; }, + '|' => { + state = .minus_pipe; + }, else => { result.tag = .minus; break; @@ -920,6 +973,17 @@ pub const Tokenizer = struct { break; }, }, + .minus_pipe => switch (c) { + '=' => { + result.tag = .minus_pipe_equal; + self.index += 1; + break; + }, + else => { + result.tag = .minus_pipe; + break; + }, + }, .angle_bracket_left => switch (c) { '<' => { @@ -942,12 +1006,27 @@ pub const Tokenizer = struct { self.index += 1; break; }, + '|' => { + result.tag = .angle_bracket_angle_bracket_left_pipe; + }, else => { result.tag = .angle_bracket_angle_bracket_left; break; }, }, + .angle_bracket_angle_bracket_left_pipe => switch (c) { + '=' => { + result.tag = .angle_bracket_angle_bracket_left_pipe_equal; + self.index += 1; + break; + }, + else => { + result.tag = .angle_bracket_angle_bracket_left_pipe; + break; + }, + }, + .angle_bracket_right => switch (c) { '>' => { state = .angle_bracket_angle_bracket_right; diff --git a/src/Air.zig b/src/Air.zig index b5d19127a0..b7d3938352 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -44,6 +44,11 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. addwrap, + /// Saturating integer addition. + /// Both operands are guaranteed to be the same type, and the result type + /// is the same as both operands. + /// Uses the `bin_op` field. + addsat, /// Float or integer subtraction. For integers, wrapping is undefined behavior. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. @@ -54,6 +59,11 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. subwrap, + /// Saturating integer subtraction. + /// Both operands are guaranteed to be the same type, and the result type + /// is the same as both operands. + /// Uses the `bin_op` field. + subsat, /// Float or integer multiplication. For integers, wrapping is undefined behavior. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. @@ -64,6 +74,11 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. mulwrap, + /// Saturating integer multiplication. + /// Both operands are guaranteed to be the same type, and the result type + /// is the same as both operands. + /// Uses the `bin_op` field. + mulsat, /// Integer or float division. For integers, wrapping is undefined behavior. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. @@ -110,6 +125,9 @@ pub const Inst = struct { /// Shift left. `<<` /// Uses the `bin_op` field. shl, + /// Shift left saturating. `<<|` + /// Uses the `bin_op` field. + shl_sat, /// Bitwise XOR. `^` /// Uses the `bin_op` field. xor, @@ -568,10 +586,13 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .add, .addwrap, + .addsat, .sub, .subwrap, + .subsat, .mul, .mulwrap, + .mulsat, .div, .rem, .mod, @@ -582,6 +603,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .ptr_sub, .shr, .shl, + .shl_sat, => return air.typeOf(datas[inst].bin_op.lhs), .cmp_lt, diff --git a/src/AstGen.zig b/src/AstGen.zig index 15594ac27c..b3af3eb86b 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -318,27 +318,35 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins .assign_bit_and, .assign_bit_or, .assign_bit_shift_left, + .assign_bit_shift_left_sat, .assign_bit_shift_right, .assign_bit_xor, .assign_div, .assign_sub, .assign_sub_wrap, + .assign_sub_sat, .assign_mod, .assign_add, .assign_add_wrap, + .assign_add_sat, .assign_mul, .assign_mul_wrap, + .assign_mul_sat, .add, .add_wrap, + .add_sat, .sub, .sub_wrap, + .sub_sat, .mul, .mul_wrap, + .mul_sat, .div, .mod, .bit_and, .bit_or, .bit_shift_left, + .bit_shift_left_sat, .bit_shift_right, .bit_xor, .bang_equal, @@ -526,6 +534,10 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr try assignShift(gz, scope, node, .shl); return rvalue(gz, rl, .void_value, node); }, + .assign_bit_shift_left_sat => { + try assignBinOpExt(gz, scope, node, .shl_with_saturation); + return rvalue(gz, rl, .void_value, node); + }, .assign_bit_shift_right => { try assignShift(gz, scope, node, .shr); return rvalue(gz, rl, .void_value, node); @@ -555,6 +567,10 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr try assignOp(gz, scope, node, .subwrap); return rvalue(gz, rl, .void_value, node); }, + .assign_sub_sat => { + try assignBinOpExt(gz, scope, node, .sub_with_saturation); + return rvalue(gz, rl, .void_value, node); + }, .assign_mod => { try assignOp(gz, scope, node, .mod_rem); return rvalue(gz, rl, .void_value, node); @@ -567,6 +583,10 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr try assignOp(gz, scope, node, .addwrap); return rvalue(gz, rl, .void_value, node); }, + .assign_add_sat => { + try assignBinOpExt(gz, scope, node, .add_with_saturation); + return rvalue(gz, rl, .void_value, node); + }, .assign_mul => { try assignOp(gz, scope, node, .mul); return rvalue(gz, rl, .void_value, node); @@ -575,17 +595,25 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr try assignOp(gz, scope, node, .mulwrap); return rvalue(gz, rl, .void_value, node); }, + .assign_mul_sat => { + try assignBinOpExt(gz, scope, node, .mul_with_saturation); + return rvalue(gz, rl, .void_value, node); + }, // zig fmt: off - .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl), - .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr), + .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl), + .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation), + .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr), .add => return simpleBinOp(gz, scope, rl, node, .add), .add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap), + .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation), .sub => return simpleBinOp(gz, scope, rl, node, .sub), .sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap), + .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation), .mul => return simpleBinOp(gz, scope, rl, node, .mul), .mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap), + .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation), .div => return simpleBinOp(gz, scope, rl, node, .div), .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem), .bit_and => { @@ -2685,6 +2713,31 @@ fn assignOp( _ = try gz.addBin(.store, lhs_ptr, result); } +// TODO: is there an existing method to accomplish this? +// TODO: likely rename this to indicate rhs type coercion or add more params to make it more general +fn assignBinOpExt( + gz: *GenZir, + scope: *Scope, + infix_node: Ast.Node.Index, + op_inst_tag: Zir.Inst.Extended, +) InnerError!void { + try emitDbgNode(gz, infix_node); + const astgen = gz.astgen; + const tree = astgen.tree; + const node_datas = tree.nodes.items(.data); + + const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs); + const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); + const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node); + const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs); + const result = try gz.addExtendedPayload(op_inst_tag, Zir.Inst.BinNode{ + .node = gz.nodeIndexToRelative(infix_node), + .lhs = lhs, + .rhs = rhs, + }); + _ = try gz.addBin(.store, lhs_ptr, result); +} + fn assignShift( gz: *GenZir, scope: *Scope, @@ -2708,6 +2761,29 @@ fn assignShift( _ = try gz.addBin(.store, lhs_ptr, result); } +fn assignShiftSat( + gz: *GenZir, + scope: *Scope, + infix_node: ast.Node.Index, + op_inst_tag: Zir.Inst.Tag, +) InnerError!void { + try emitDbgNode(gz, infix_node); + const astgen = gz.astgen; + const tree = astgen.tree; + const node_datas = tree.nodes.items(.data); + + const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs); + const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); + const rhs_type = try gz.addUnNode(.typeof, lhs, infix_node); + const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs); + + const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{ + .lhs = lhs, + .rhs = rhs, + }); + _ = try gz.addBin(.store, lhs_ptr, result); +} + fn boolNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; const tree = astgen.tree; @@ -7827,6 +7903,26 @@ fn shiftOp( return rvalue(gz, rl, result, node); } +// TODO: is there an existing way to do this? +// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general +fn binOpExt( + gz: *GenZir, + scope: *Scope, + rl: ResultLoc, + node: Ast.Node.Index, + lhs_node: Ast.Node.Index, + rhs_node: Ast.Node.Index, + tag: Zir.Inst.Extended, +) InnerError!Zir.Inst.Ref { + const lhs = try expr(gz, scope, .none, lhs_node); + const rhs = try expr(gz, scope, .none, rhs_node); + const result = try gz.addExtendedPayload(tag, Zir.Inst.Bin{ + .lhs = lhs, + .rhs = rhs, + }); + return rvalue(gz, rl, result, node); +} + fn cImport( gz: *GenZir, scope: *Scope, @@ -8119,26 +8215,32 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool .asm_simple, .add, .add_wrap, + .add_sat, .array_cat, .array_mult, .assign, .assign_bit_and, .assign_bit_or, .assign_bit_shift_left, + .assign_bit_shift_left_sat, .assign_bit_shift_right, .assign_bit_xor, .assign_div, .assign_sub, .assign_sub_wrap, + .assign_sub_sat, .assign_mod, .assign_add, .assign_add_wrap, + .assign_add_sat, .assign_mul, .assign_mul_wrap, + .assign_mul_sat, .bang_equal, .bit_and, .bit_or, .bit_shift_left, + .bit_shift_left_sat, .bit_shift_right, .bit_xor, .bool_and, @@ -8154,10 +8256,12 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool .mod, .mul, .mul_wrap, + .mul_sat, .switch_range, .field_access, .sub, .sub_wrap, + .sub_sat, .slice, .slice_open, .slice_sentinel, @@ -8352,26 +8456,32 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never .tagged_union_enum_tag_trailing, .add, .add_wrap, + .add_sat, .array_cat, .array_mult, .assign, .assign_bit_and, .assign_bit_or, .assign_bit_shift_left, + .assign_bit_shift_left_sat, .assign_bit_shift_right, .assign_bit_xor, .assign_div, .assign_sub, .assign_sub_wrap, + .assign_sub_sat, .assign_mod, .assign_add, .assign_add_wrap, + .assign_add_sat, .assign_mul, .assign_mul_wrap, + .assign_mul_sat, .bang_equal, .bit_and, .bit_or, .bit_shift_left, + .bit_shift_left_sat, .bit_shift_right, .bit_xor, .bool_and, @@ -8387,9 +8497,11 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never .mod, .mul, .mul_wrap, + .mul_sat, .switch_range, .sub, .sub_wrap, + .sub_sat, .slice, .slice_open, .slice_sentinel, @@ -8524,26 +8636,32 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool { .asm_simple, .add, .add_wrap, + .add_sat, .array_cat, .array_mult, .assign, .assign_bit_and, .assign_bit_or, .assign_bit_shift_left, + .assign_bit_shift_left_sat, .assign_bit_shift_right, .assign_bit_xor, .assign_div, .assign_sub, .assign_sub_wrap, + .assign_sub_sat, .assign_mod, .assign_add, .assign_add_wrap, + .assign_add_sat, .assign_mul, .assign_mul_wrap, + .assign_mul_sat, .bang_equal, .bit_and, .bit_or, .bit_shift_left, + .bit_shift_left_sat, .bit_shift_right, .bit_xor, .bool_and, @@ -8559,10 +8677,12 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool { .mod, .mul, .mul_wrap, + .mul_sat, .switch_range, .field_access, .sub, .sub_wrap, + .sub_sat, .slice, .slice_open, .slice_sentinel, diff --git a/src/Liveness.zig b/src/Liveness.zig index 25dd29b0f6..c34153b76f 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -226,10 +226,13 @@ fn analyzeInst( switch (inst_tags[inst]) { .add, .addwrap, + .addsat, .sub, .subwrap, + .subsat, .mul, .mulwrap, + .mulsat, .div, .rem, .mod, @@ -252,6 +255,7 @@ fn analyzeInst( .ptr_elem_val, .ptr_ptr_elem_val, .shl, + .shl_sat, .shr, .atomic_store_unordered, .atomic_store_monotonic, diff --git a/src/codegen.zig b/src/codegen.zig index 7c359e90c0..a1f812388f 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -826,10 +826,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // zig fmt: off .add, .ptr_add => try self.airAdd(inst), .addwrap => try self.airAddWrap(inst), + .addsat => try self.airArithmeticOpSat(inst, "addsat"), .sub, .ptr_sub => try self.airSub(inst), .subwrap => try self.airSubWrap(inst), + .subsat => try self.airArithmeticOpSat(inst, "subsat"), .mul => try self.airMul(inst), .mulwrap => try self.airMulWrap(inst), + .mulsat => try self.airArithmeticOpSat(inst, "mulsat"), .div => try self.airDiv(inst), .rem => try self.airRem(inst), .mod => try self.airMod(inst), @@ -848,6 +851,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .xor => try self.airXor(inst), .shr => try self.airShr(inst), .shl => try self.airShl(inst), + .shl_sat => try self.airArithmeticOpSat(inst, "shl_sat"), .alloc => try self.airAlloc(inst), .arg => try self.airArg(inst), @@ -1320,6 +1324,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airArithmeticOpSat(self: *Self, inst: Air.Inst.Index, comptime name: []const u8) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement " ++ name ++ " for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn airMul(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 4964f17cd3..dce0c10b4c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -885,14 +885,17 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO // that wrapping is UB. .add, .ptr_add => try airBinOp( f, inst, " + "), .addwrap => try airWrapOp(f, inst, " + ", "addw_"), + .addsat => return o.dg.fail("TODO: C backend: implement codegen for addsat", .{}), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. .sub, .ptr_sub => try airBinOp( f, inst, " - "), .subwrap => try airWrapOp(f, inst, " - ", "subw_"), + .subsat => return o.dg.fail("TODO: C backend: implement codegen for subsat", .{}), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. .mul => try airBinOp( f, inst, " * "), .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"), + .mulsat => return o.dg.fail("TODO: C backend: implement codegen for mulsat", .{}), // TODO use a different strategy for div that communicates to the optimizer // that wrapping is UB. .div => try airBinOp( f, inst, " / "), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index f65e0f6d72..c21985d1c5 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1236,12 +1236,15 @@ pub const FuncGen = struct { for (body) |inst| { const opt_value: ?*const llvm.Value = switch (air_tags[inst]) { // zig fmt: off - .add => try self.airAdd(inst, false), - .addwrap => try self.airAdd(inst, true), - .sub => try self.airSub(inst, false), - .subwrap => try self.airSub(inst, true), - .mul => try self.airMul(inst, false), - .mulwrap => try self.airMul(inst, true), + .add => try self.airAdd(inst, .standard), + .addwrap => try self.airAdd(inst, .wrapping), + .addsat => try self.airAdd(inst, .saturated), + .sub => try self.airSub(inst, .standard), + .subwrap => try self.airSub(inst, .wrapping), + .subsat => try self.airSub(inst, .saturated), + .mul => try self.airMul(inst, .standard), + .mulwrap => try self.airMul(inst, .wrapping), + .mulsat => try self.airMul(inst, .saturated), .div => try self.airDiv(inst), .rem => try self.airRem(inst), .mod => try self.airMod(inst), @@ -1252,7 +1255,8 @@ pub const FuncGen = struct { .bit_or, .bool_or => try self.airOr(inst), .xor => try self.airXor(inst), - .shl => try self.airShl(inst), + .shl => try self.airShl(inst, false), + .shl_sat => try self.airShl(inst, true), .shr => try self.airShr(inst), .cmp_eq => try self.airCmp(inst, .eq), @@ -2024,7 +2028,8 @@ pub const FuncGen = struct { return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{}); } - fn airAdd(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value { + const ArithmeticType = enum { standard, wrapping, saturated }; + fn airAdd(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -2033,13 +2038,20 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isRuntimeFloat()) return self.builder.buildFAdd(lhs, rhs, ""); - if (wrap) return self.builder.buildAdd(lhs, rhs, ""); + if (inst_ty.isFloat()) return self.builder.buildFAdd(lhs, rhs, ""); + if (ty == .wrapping) + return self.builder.buildAdd(lhs, rhs, "") + else if (ty == .saturated) { + if (inst_ty.isSignedInt()) + return self.builder.buildSAddSat(lhs, rhs, "") + else + return self.builder.buildUAddSat(lhs, rhs, ""); + } if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, ""); return self.builder.buildNUWAdd(lhs, rhs, ""); } - fn airSub(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value { + fn airSub(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -2048,13 +2060,20 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isRuntimeFloat()) return self.builder.buildFSub(lhs, rhs, ""); - if (wrap) return self.builder.buildSub(lhs, rhs, ""); + if (inst_ty.isFloat()) return self.builder.buildFSub(lhs, rhs, ""); + if (ty == .wrapping) + return self.builder.buildSub(lhs, rhs, "") + else if (ty == .saturated) { + if (inst_ty.isSignedInt()) + return self.builder.buildSSubSat(lhs, rhs, "") + else + return self.builder.buildUSubSat(lhs, rhs, ""); + } if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, ""); return self.builder.buildNUWSub(lhs, rhs, ""); } - fn airMul(self: *FuncGen, inst: Air.Inst.Index, wrap: bool) !?*const llvm.Value { + fn airMul(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -2063,8 +2082,15 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isRuntimeFloat()) return self.builder.buildFMul(lhs, rhs, ""); - if (wrap) return self.builder.buildMul(lhs, rhs, ""); + if (inst_ty.isFloat()) return self.builder.buildFMul(lhs, rhs, ""); + if (ty == .wrapping) + return self.builder.buildMul(lhs, rhs, "") + else if (ty == .saturated) { + if (inst_ty.isSignedInt()) + return self.builder.buildSMulFixSat(lhs, rhs, "") + else + return self.builder.buildUMulFixSat(lhs, rhs, ""); + } if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, ""); return self.builder.buildNUWMul(lhs, rhs, ""); } @@ -2174,7 +2200,7 @@ pub const FuncGen = struct { return self.builder.buildXor(lhs, rhs, ""); } - fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + fn airShl(self: *FuncGen, inst: Air.Inst.Index, sat: bool) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; @@ -2186,6 +2212,12 @@ pub const FuncGen = struct { self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "") else rhs; + if (sat) { + return if (lhs_type.isSignedInt()) + self.builder.buildSShlSat(lhs, casted_rhs, "") + else + self.builder.buildUShlSat(lhs, casted_rhs, ""); + } return self.builder.buildShl(lhs, casted_rhs, ""); } diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 9d32682260..178c381235 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -397,6 +397,12 @@ pub const Builder = opaque { pub const buildNUWAdd = LLVMBuildNUWAdd; extern fn LLVMBuildNUWAdd(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildSAddSat = ZigLLVMBuildSAddSat; + extern fn ZigLLVMBuildSAddSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildUAddSat = ZigLLVMBuildUAddSat; + extern fn ZigLLVMBuildUAddSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildFSub = LLVMBuildFSub; extern fn LLVMBuildFSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; @@ -409,6 +415,12 @@ pub const Builder = opaque { pub const buildNUWSub = LLVMBuildNUWSub; extern fn LLVMBuildNUWSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildSSubSat = ZigLLVMBuildSSubSat; + extern fn ZigLLVMBuildSSubSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildUSubSat = ZigLLVMBuildUSubSat; + extern fn ZigLLVMBuildUSubSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildFMul = LLVMBuildFMul; extern fn LLVMBuildFMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; @@ -421,6 +433,12 @@ pub const Builder = opaque { pub const buildNUWMul = LLVMBuildNUWMul; extern fn LLVMBuildNUWMul(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildSMulFixSat = ZigLLVMBuildSMulFixSat; + extern fn ZigLLVMBuildSMulFixSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildUMulFixSat = ZigLLVMBuildUMulFixSat; + extern fn ZigLLVMBuildUMulFixSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildUDiv = LLVMBuildUDiv; extern fn LLVMBuildUDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; @@ -451,6 +469,12 @@ pub const Builder = opaque { pub const buildShl = LLVMBuildShl; extern fn LLVMBuildShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildSShlSat = ZigLLVMBuildSShlSat; + extern fn ZigLLVMBuildSShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildUShlSat = ZigLLVMBuildUShlSat; + extern fn ZigLLVMBuildUShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildOr = LLVMBuildOr; extern fn LLVMBuildOr(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; diff --git a/src/print_air.zig b/src/print_air.zig index 90df06760b..7d178b52f3 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -104,10 +104,13 @@ const Writer = struct { .add, .addwrap, + .addsat, .sub, .subwrap, + .subsat, .mul, .mulwrap, + .mulsat, .div, .rem, .mod, @@ -130,6 +133,7 @@ const Writer = struct { .ptr_elem_val, .ptr_ptr_elem_val, .shl, + .shl_sat, .shr, .set_union_tag, => try w.writeBinOp(s, inst), diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 13c37fc839..e31a7015b0 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -812,14 +812,18 @@ enum BinOpType { BinOpTypeInvalid, BinOpTypeAssign, BinOpTypeAssignTimes, + BinOpTypeAssignTimesSat, BinOpTypeAssignTimesWrap, BinOpTypeAssignDiv, BinOpTypeAssignMod, BinOpTypeAssignPlus, + BinOpTypeAssignPlusSat, BinOpTypeAssignPlusWrap, BinOpTypeAssignMinus, + BinOpTypeAssignMinusSat, BinOpTypeAssignMinusWrap, BinOpTypeAssignBitShiftLeft, + BinOpTypeAssignBitShiftLeftSat, BinOpTypeAssignBitShiftRight, BinOpTypeAssignBitAnd, BinOpTypeAssignBitXor, @@ -836,12 +840,16 @@ enum BinOpType { BinOpTypeBinXor, BinOpTypeBinAnd, BinOpTypeBitShiftLeft, + BinOpTypeBitShiftLeftSat, BinOpTypeBitShiftRight, BinOpTypeAdd, + BinOpTypeAddSat, BinOpTypeAddWrap, BinOpTypeSub, + BinOpTypeSubSat, BinOpTypeSubWrap, BinOpTypeMult, + BinOpTypeMultSat, BinOpTypeMultWrap, BinOpTypeDiv, BinOpTypeMod, @@ -2958,10 +2966,10 @@ enum IrBinOp { IrBinOpArrayMult, IrBinOpMaximum, IrBinOpMinimum, - IrBinOpSatAdd, - IrBinOpSatSub, - IrBinOpSatMul, - IrBinOpSatShl, + IrBinOpAddSat, + IrBinOpSubSat, + IrBinOpMultSat, + IrBinOpShlSat, }; struct Stage1ZirInstBinOp { diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp index 9e5d9da9ee..14808dd0a2 100644 --- a/src/stage1/astgen.cpp +++ b/src/stage1/astgen.cpp @@ -3672,6 +3672,8 @@ static Stage1ZirInst *astgen_bin_op(Stage1AstGen *ag, Scope *scope, AstNode *nod return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpMult), lval, result_loc); case BinOpTypeAssignTimesWrap: return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpMultWrap), lval, result_loc); + case BinOpTypeAssignTimesSat: + return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpMultSat), lval, result_loc); case BinOpTypeAssignDiv: return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpDivUnspecified), lval, result_loc); case BinOpTypeAssignMod: @@ -3680,12 +3682,18 @@ static Stage1ZirInst *astgen_bin_op(Stage1AstGen *ag, Scope *scope, AstNode *nod return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpAdd), lval, result_loc); case BinOpTypeAssignPlusWrap: return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpAddWrap), lval, result_loc); + case BinOpTypeAssignPlusSat: + return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpAddSat), lval, result_loc); case BinOpTypeAssignMinus: return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpSub), lval, result_loc); case BinOpTypeAssignMinusWrap: return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpSubWrap), lval, result_loc); + case BinOpTypeAssignMinusSat: + return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpSubSat), lval, result_loc); case BinOpTypeAssignBitShiftLeft: return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpBitShiftLeftLossy), lval, result_loc); + case BinOpTypeAssignBitShiftLeftSat: + return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpShlSat), lval, result_loc); case BinOpTypeAssignBitShiftRight: return ir_lval_wrap(ag, scope, astgen_assign_op(ag, scope, node, IrBinOpBitShiftRightLossy), lval, result_loc); case BinOpTypeAssignBitAnd: @@ -3718,20 +3726,28 @@ static Stage1ZirInst *astgen_bin_op(Stage1AstGen *ag, Scope *scope, AstNode *nod return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpBinAnd), lval, result_loc); case BinOpTypeBitShiftLeft: return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpBitShiftLeftLossy), lval, result_loc); + case BinOpTypeBitShiftLeftSat: + return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpShlSat), lval, result_loc); case BinOpTypeBitShiftRight: return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpBitShiftRightLossy), lval, result_loc); case BinOpTypeAdd: return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpAdd), lval, result_loc); case BinOpTypeAddWrap: return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpAddWrap), lval, result_loc); + case BinOpTypeAddSat: + return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpAddSat), lval, result_loc); case BinOpTypeSub: return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpSub), lval, result_loc); case BinOpTypeSubWrap: return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpSubWrap), lval, result_loc); + case BinOpTypeSubSat: + return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpSubSat), lval, result_loc); case BinOpTypeMult: return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpMult), lval, result_loc); case BinOpTypeMultWrap: return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpMultWrap), lval, result_loc); + case BinOpTypeMultSat: + return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpMultSat), lval, result_loc); case BinOpTypeDiv: return ir_lval_wrap(ag, scope, astgen_bin_op_id(ag, scope, node, IrBinOpDivUnspecified), lval, result_loc); case BinOpTypeMod: @@ -4716,7 +4732,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast if (arg1_value == ag->codegen->invalid_inst_src) return arg1_value; - Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatAdd, arg0_value, arg1_value, true); + Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpAddSat, arg0_value, arg1_value, true); return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); } case BuiltinFnIdSatSub: @@ -4731,7 +4747,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast if (arg1_value == ag->codegen->invalid_inst_src) return arg1_value; - Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatSub, arg0_value, arg1_value, true); + Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSubSat, arg0_value, arg1_value, true); return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); } case BuiltinFnIdSatMul: @@ -4746,7 +4762,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast if (arg1_value == ag->codegen->invalid_inst_src) return arg1_value; - Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatMul, arg0_value, arg1_value, true); + Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMultSat, arg0_value, arg1_value, true); return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); } case BuiltinFnIdSatShl: @@ -4761,7 +4777,7 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast if (arg1_value == ag->codegen->invalid_inst_src) return arg1_value; - Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSatShl, arg0_value, arg1_value, true); + Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpShlSat, arg0_value, arg1_value, true); return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); } case BuiltinFnIdMemcpy: diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index f84847a9fe..eade843354 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -3333,7 +3333,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable, } else { zig_unreachable(); } - case IrBinOpSatAdd: + case IrBinOpAddSat: if (scalar_type->id == ZigTypeIdInt) { if (scalar_type->data.integral.is_signed) { return ZigLLVMBuildSAddSat(g->builder, op1_value, op2_value, ""); @@ -3343,7 +3343,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable, } else { zig_unreachable(); } - case IrBinOpSatSub: + case IrBinOpSubSat: if (scalar_type->id == ZigTypeIdInt) { if (scalar_type->data.integral.is_signed) { return ZigLLVMBuildSSubSat(g->builder, op1_value, op2_value, ""); @@ -3353,7 +3353,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable, } else { zig_unreachable(); } - case IrBinOpSatMul: + case IrBinOpMultSat: if (scalar_type->id == ZigTypeIdInt) { if (scalar_type->data.integral.is_signed) { return ZigLLVMBuildSMulFixSat(g->builder, op1_value, op2_value, ""); @@ -3363,7 +3363,7 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, Stage1Air *executable, } else { zig_unreachable(); } - case IrBinOpSatShl: + case IrBinOpShlSat: if (scalar_type->id == ZigTypeIdInt) { if (scalar_type->data.integral.is_signed) { return ZigLLVMBuildSShlSat(g->builder, op1_value, op2_value, ""); diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index b853961beb..2f2cfe08f3 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -9820,28 +9820,28 @@ static ErrorMsg *ir_eval_math_op_scalar(IrAnalyze *ira, Scope *scope, AstNode *s float_min(out_val, op1_val, op2_val); } break; - case IrBinOpSatAdd: + case IrBinOpAddSat: if (is_int) { bigint_add_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed); } else { zig_unreachable(); } break; - case IrBinOpSatSub: + case IrBinOpSubSat: if (is_int) { bigint_sub_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed); } else { zig_unreachable(); } break; - case IrBinOpSatMul: + case IrBinOpMultSat: if (is_int) { bigint_mul_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed); } else { zig_unreachable(); } break; - case IrBinOpSatShl: + case IrBinOpShlSat: if (is_int) { bigint_shl_sat(&out_val->data.x_bigint, &op1_val->data.x_bigint, &op2_val->data.x_bigint, type_entry->data.integral.bit_count, type_entry->data.integral.is_signed); } else { @@ -10069,10 +10069,10 @@ static bool ok_float_op(IrBinOp op) { case IrBinOpBitShiftRightExact: case IrBinOpAddWrap: case IrBinOpSubWrap: - case IrBinOpSatAdd: - case IrBinOpSatSub: - case IrBinOpSatMul: - case IrBinOpSatShl: + case IrBinOpAddSat: + case IrBinOpSubSat: + case IrBinOpMultSat: + case IrBinOpShlSat: case IrBinOpMultWrap: case IrBinOpArrayCat: case IrBinOpArrayMult: @@ -11046,10 +11046,10 @@ static Stage1AirInst *ir_analyze_instruction_bin_op(IrAnalyze *ira, Stage1ZirIns case IrBinOpRemMod: case IrBinOpMaximum: case IrBinOpMinimum: - case IrBinOpSatAdd: - case IrBinOpSatSub: - case IrBinOpSatMul: - case IrBinOpSatShl: + case IrBinOpAddSat: + case IrBinOpSubSat: + case IrBinOpMultSat: + case IrBinOpShlSat: return ir_analyze_bin_op_math(ira, bin_op_instruction); case IrBinOpArrayCat: return ir_analyze_array_cat(ira, bin_op_instruction); diff --git a/src/stage1/ir_print.cpp b/src/stage1/ir_print.cpp index a76d3e4d5a..f92f146d84 100644 --- a/src/stage1/ir_print.cpp +++ b/src/stage1/ir_print.cpp @@ -737,13 +737,13 @@ static const char *ir_bin_op_id_str(IrBinOp op_id) { return "@maximum"; case IrBinOpMinimum: return "@minimum"; - case IrBinOpSatAdd: + case IrBinOpAddSat: return "@addWithSaturation"; - case IrBinOpSatSub: + case IrBinOpSubSat: return "@subWithSaturation"; - case IrBinOpSatMul: + case IrBinOpMultSat: return "@mulWithSaturation"; - case IrBinOpSatShl: + case IrBinOpShlSat: return "@shlWithSaturation"; } zig_unreachable(); diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp index f7061bb232..fdc0777aff 100644 --- a/src/stage1/parser.cpp +++ b/src/stage1/parser.cpp @@ -2381,6 +2381,7 @@ static AstNode *ast_parse_switch_item(ParseContext *pc) { // / PLUSEQUAL // / MINUSEQUAL // / LARROW2EQUAL +// / LARROW2PIPEEQUAL // / RARROW2EQUAL // / AMPERSANDEQUAL // / CARETEQUAL @@ -2388,6 +2389,9 @@ static AstNode *ast_parse_switch_item(ParseContext *pc) { // / ASTERISKPERCENTEQUAL // / PLUSPERCENTEQUAL // / MINUSPERCENTEQUAL +// / ASTERISKPIPEEQUAL +// / PLUSPIPEEQUAL +// / MINUSPIPEEQUAL // / EQUAL static AstNode *ast_parse_assign_op(ParseContext *pc) { // In C, we have `T arr[N] = {[i] = T{}};` but it doesn't @@ -2396,17 +2400,21 @@ static AstNode *ast_parse_assign_op(ParseContext *pc) { table[TokenIdBitAndEq] = BinOpTypeAssignBitAnd; table[TokenIdBitOrEq] = BinOpTypeAssignBitOr; table[TokenIdBitShiftLeftEq] = BinOpTypeAssignBitShiftLeft; + table[TokenIdBitShiftLeftPipeEq] = BinOpTypeAssignBitShiftLeftSat; table[TokenIdBitShiftRightEq] = BinOpTypeAssignBitShiftRight; table[TokenIdBitXorEq] = BinOpTypeAssignBitXor; table[TokenIdDivEq] = BinOpTypeAssignDiv; table[TokenIdEq] = BinOpTypeAssign; table[TokenIdMinusEq] = BinOpTypeAssignMinus; table[TokenIdMinusPercentEq] = BinOpTypeAssignMinusWrap; + table[TokenIdMinusPipeEq] = BinOpTypeAssignMinusSat; table[TokenIdModEq] = BinOpTypeAssignMod; table[TokenIdPlusEq] = BinOpTypeAssignPlus; table[TokenIdPlusPercentEq] = BinOpTypeAssignPlusWrap; + table[TokenIdPlusPipeEq] = BinOpTypeAssignPlusSat; table[TokenIdTimesEq] = BinOpTypeAssignTimes; table[TokenIdTimesPercentEq] = BinOpTypeAssignTimesWrap; + table[TokenIdTimesPipeEq] = BinOpTypeAssignTimesSat; BinOpType op = table[pc->token_ids[pc->current_token]]; if (op != BinOpTypeInvalid) { @@ -2483,10 +2491,12 @@ static AstNode *ast_parse_bitwise_op(ParseContext *pc) { // BitShiftOp // <- LARROW2 +// / LARROW2PIPE // / RARROW2 static AstNode *ast_parse_bit_shift_op(ParseContext *pc) { BinOpType table[TokenIdCount] = {}; table[TokenIdBitShiftLeft] = BinOpTypeBitShiftLeft; + table[TokenIdBitShiftLeftPipe] = BinOpTypeBitShiftLeftSat; table[TokenIdBitShiftRight] = BinOpTypeBitShiftRight; BinOpType op = table[pc->token_ids[pc->current_token]]; @@ -2506,6 +2516,8 @@ static AstNode *ast_parse_bit_shift_op(ParseContext *pc) { // / PLUS2 // / PLUSPERCENT // / MINUSPERCENT +// / PLUSPIPE +// / MINUSPIPE static AstNode *ast_parse_addition_op(ParseContext *pc) { BinOpType table[TokenIdCount] = {}; table[TokenIdPlus] = BinOpTypeAdd; @@ -2513,6 +2525,8 @@ static AstNode *ast_parse_addition_op(ParseContext *pc) { table[TokenIdPlusPlus] = BinOpTypeArrayCat; table[TokenIdPlusPercent] = BinOpTypeAddWrap; table[TokenIdMinusPercent] = BinOpTypeSubWrap; + table[TokenIdPlusPipe] = BinOpTypeAddSat; + table[TokenIdMinusPipe] = BinOpTypeSubSat; BinOpType op = table[pc->token_ids[pc->current_token]]; if (op != BinOpTypeInvalid) { @@ -2532,6 +2546,7 @@ static AstNode *ast_parse_addition_op(ParseContext *pc) { // / PERCENT // / ASTERISK2 // / ASTERISKPERCENT +// / ASTERISKPIPE static AstNode *ast_parse_multiply_op(ParseContext *pc) { BinOpType table[TokenIdCount] = {}; table[TokenIdBarBar] = BinOpTypeMergeErrorSets; @@ -2540,6 +2555,7 @@ static AstNode *ast_parse_multiply_op(ParseContext *pc) { table[TokenIdPercent] = BinOpTypeMod; table[TokenIdStarStar] = BinOpTypeArrayMult; table[TokenIdTimesPercent] = BinOpTypeMultWrap; + table[TokenIdTimesPipe] = BinOpTypeMultSat; BinOpType op = table[pc->token_ids[pc->current_token]]; if (op != BinOpTypeInvalid) { diff --git a/src/stage1/tokenizer.cpp b/src/stage1/tokenizer.cpp index f10579c966..3560193927 100644 --- a/src/stage1/tokenizer.cpp +++ b/src/stage1/tokenizer.cpp @@ -226,8 +226,10 @@ enum TokenizeState { TokenizeState_pipe, TokenizeState_minus, TokenizeState_minus_percent, + TokenizeState_minus_pipe, TokenizeState_asterisk, TokenizeState_asterisk_percent, + TokenizeState_asterisk_pipe, TokenizeState_slash, TokenizeState_line_comment_start, TokenizeState_line_comment, @@ -257,8 +259,10 @@ enum TokenizeState { TokenizeState_percent, TokenizeState_plus, TokenizeState_plus_percent, + TokenizeState_plus_pipe, TokenizeState_angle_bracket_left, TokenizeState_angle_bracket_angle_bracket_left, + TokenizeState_angle_bracket_angle_bracket_left_pipe, TokenizeState_angle_bracket_right, TokenizeState_angle_bracket_angle_bracket_right, TokenizeState_period, @@ -548,6 +552,9 @@ void tokenize(const char *source, Tokenization *out) { case '%': t.state = TokenizeState_asterisk_percent; break; + case '|': + t.state = TokenizeState_asterisk_pipe; + break; default: t.state = TokenizeState_start; continue; @@ -568,6 +575,21 @@ void tokenize(const char *source, Tokenization *out) { continue; } break; + case TokenizeState_asterisk_pipe: + switch (c) { + case 0: + t.out->ids.last() = TokenIdTimesPipe; + goto eof; + case '=': + t.out->ids.last() = TokenIdTimesPipeEq; + t.state = TokenizeState_start; + break; + default: + t.out->ids.last() = TokenIdTimesPipe; + t.state = TokenizeState_start; + continue; + } + break; case TokenizeState_percent: switch (c) { case 0: @@ -596,6 +618,9 @@ void tokenize(const char *source, Tokenization *out) { case '%': t.state = TokenizeState_plus_percent; break; + case '|': + t.state = TokenizeState_plus_pipe; + break; default: t.state = TokenizeState_start; continue; @@ -616,6 +641,21 @@ void tokenize(const char *source, Tokenization *out) { continue; } break; + case TokenizeState_plus_pipe: + switch (c) { + case 0: + t.out->ids.last() = TokenIdPlusPipe; + goto eof; + case '=': + t.out->ids.last() = TokenIdPlusPipeEq; + t.state = TokenizeState_start; + break; + default: + t.out->ids.last() = TokenIdPlusPipe; + t.state = TokenizeState_start; + continue; + } + break; case TokenizeState_caret: switch (c) { case 0: @@ -891,6 +931,9 @@ void tokenize(const char *source, Tokenization *out) { case '%': t.state = TokenizeState_minus_percent; break; + case '|': + t.state = TokenizeState_minus_pipe; + break; default: t.state = TokenizeState_start; continue; @@ -911,6 +954,21 @@ void tokenize(const char *source, Tokenization *out) { continue; } break; + case TokenizeState_minus_pipe: + switch (c) { + case 0: + t.out->ids.last() = TokenIdMinusPipe; + goto eof; + case '=': + t.out->ids.last() = TokenIdMinusPipeEq; + t.state = TokenizeState_start; + break; + default: + t.out->ids.last() = TokenIdMinusPipe; + t.state = TokenizeState_start; + continue; + } + break; case TokenizeState_angle_bracket_left: switch (c) { case 0: @@ -936,12 +994,31 @@ void tokenize(const char *source, Tokenization *out) { t.out->ids.last() = TokenIdBitShiftLeftEq; t.state = TokenizeState_start; break; + case '|': + // t.out->ids.last() = TokenIdBitShiftLeftPipe; + t.state = TokenizeState_angle_bracket_angle_bracket_left_pipe; + break; default: t.out->ids.last() = TokenIdBitShiftLeft; t.state = TokenizeState_start; continue; } break; + case TokenizeState_angle_bracket_angle_bracket_left_pipe: + switch (c) { + case 0: + t.out->ids.last() = TokenIdBitShiftLeftPipe; + goto eof; + case '=': + t.out->ids.last() = TokenIdBitShiftLeftPipeEq; + t.state = TokenizeState_start; + break; + default: + t.out->ids.last() = TokenIdBitShiftLeftPipe; + t.state = TokenizeState_start; + continue; + } + break; case TokenizeState_angle_bracket_right: switch (c) { case 0: @@ -1437,6 +1514,8 @@ const char * token_name(TokenId id) { case TokenIdBitOrEq: return "|="; case TokenIdBitShiftLeft: return "<<"; case TokenIdBitShiftLeftEq: return "<<="; + case TokenIdBitShiftLeftPipe: return "<<|"; + case TokenIdBitShiftLeftPipeEq: return "<<|="; case TokenIdBitShiftRight: return ">>"; case TokenIdBitShiftRightEq: return ">>="; case TokenIdBitXorEq: return "^="; @@ -1521,12 +1600,16 @@ const char * token_name(TokenId id) { case TokenIdMinusEq: return "-="; case TokenIdMinusPercent: return "-%"; case TokenIdMinusPercentEq: return "-%="; + case TokenIdMinusPipe: return "-|"; + case TokenIdMinusPipeEq: return "-|="; case TokenIdModEq: return "%="; case TokenIdPercent: return "%"; case TokenIdPlus: return "+"; case TokenIdPlusEq: return "+="; case TokenIdPlusPercent: return "+%"; case TokenIdPlusPercentEq: return "+%="; + case TokenIdPlusPipe: return "+|"; + case TokenIdPlusPipeEq: return "+|="; case TokenIdPlusPlus: return "++"; case TokenIdRBrace: return "}"; case TokenIdRBracket: return "]"; @@ -1542,6 +1625,8 @@ const char * token_name(TokenId id) { case TokenIdTimesEq: return "*="; case TokenIdTimesPercent: return "*%"; case TokenIdTimesPercentEq: return "*%="; + case TokenIdTimesPipe: return "*|"; + case TokenIdTimesPipeEq: return "*|="; case TokenIdBuiltin: return "Builtin"; case TokenIdCount: zig_unreachable(); diff --git a/src/stage1/tokenizer.hpp b/src/stage1/tokenizer.hpp index 0e196597eb..56605c1764 100644 --- a/src/stage1/tokenizer.hpp +++ b/src/stage1/tokenizer.hpp @@ -23,6 +23,8 @@ enum TokenId : uint8_t { TokenIdBitOrEq, TokenIdBitShiftLeft, TokenIdBitShiftLeftEq, + TokenIdBitShiftLeftPipe, + TokenIdBitShiftLeftPipeEq, TokenIdBitShiftRight, TokenIdBitShiftRightEq, TokenIdBitXorEq, @@ -108,12 +110,16 @@ enum TokenId : uint8_t { TokenIdMinusEq, TokenIdMinusPercent, TokenIdMinusPercentEq, + TokenIdMinusPipe, + TokenIdMinusPipeEq, TokenIdModEq, TokenIdPercent, TokenIdPlus, TokenIdPlusEq, TokenIdPlusPercent, TokenIdPlusPercentEq, + TokenIdPlusPipe, + TokenIdPlusPipeEq, TokenIdPlusPlus, TokenIdRBrace, TokenIdRBracket, @@ -129,6 +135,8 @@ enum TokenId : uint8_t { TokenIdTimesEq, TokenIdTimesPercent, TokenIdTimesPercentEq, + TokenIdTimesPipe, + TokenIdTimesPipeEq, TokenIdCount, }; diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig index 553e9ff21a..7a28ed182d 100644 --- a/test/behavior/saturating_arithmetic.zig +++ b/test/behavior/saturating_arithmetic.zig @@ -11,13 +11,34 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void { const a = test_data[0]; const b = test_data[1]; const expected = test_data[2]; - const actual = switch (op) { - .add => @addWithSaturation(a, b), - .sub => @subWithSaturation(a, b), - .mul => @mulWithSaturation(a, b), - .shl => @shlWithSaturation(a, b), - }; - try expectEqual(expected, actual); + { + const actual = switch (op) { + .add => @addWithSaturation(a, b), + .sub => @subWithSaturation(a, b), + .mul => @mulWithSaturation(a, b), + .shl => @shlWithSaturation(a, b), + }; + try expectEqual(expected, actual); + } + { + const actual = switch (op) { + .add => a +| b, + .sub => a -| b, + .mul => a *| b, + .shl => a <<| b, + }; + try expectEqual(expected, actual); + } + { + var actual = a; + switch (op) { + .add => actual +|= b, + .sub => actual -|= b, + .mul => actual *|= b, + .shl => actual <<|= b, + } + try expectEqual(expected, actual); + } } test "@addWithSaturation" { From b9a95f2dd94e6175322d3388c3936eb600ec90ea Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Wed, 8 Sep 2021 15:19:03 -0700 Subject: [PATCH 130/160] sat-arithmetic: add c backend support - modify AstGen binOpExt()/assignBinOpExt() to accept generic extended payload T - rework Sema zirSatArithmetic() to use existing sema.analyzeArithmetic() by adding an `opt_extended` parameter. - add airSatOp() to codegen/c.zig - add saturating functions to src/link/C/zig.h --- src/AstGen.zig | 62 +++++++++++----------- src/Sema.zig | 25 ++++++--- src/codegen/c.zig | 120 +++++++++++++++++++++++++++++++++++++++++-- src/codegen/llvm.zig | 6 +-- src/link/C/zig.h | 93 +++++++++++++++++++++++++++++++++ 5 files changed, 262 insertions(+), 44 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index b3af3eb86b..25452cb386 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -535,7 +535,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_bit_shift_left_sat => { - try assignBinOpExt(gz, scope, node, .shl_with_saturation); + try assignBinOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic); return rvalue(gz, rl, .void_value, node); }, .assign_bit_shift_right => { @@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_sub_sat => { - try assignBinOpExt(gz, scope, node, .sub_with_saturation); + try assignBinOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic); return rvalue(gz, rl, .void_value, node); }, .assign_mod => { @@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_add_sat => { - try assignBinOpExt(gz, scope, node, .add_with_saturation); + try assignBinOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic); return rvalue(gz, rl, .void_value, node); }, .assign_mul => { @@ -596,24 +596,24 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_mul_sat => { - try assignBinOpExt(gz, scope, node, .mul_with_saturation); + try assignBinOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic); return rvalue(gz, rl, .void_value, node); }, // zig fmt: off .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl), - .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation), + .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic), .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr), .add => return simpleBinOp(gz, scope, rl, node, .add), .add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap), - .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation), + .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic), .sub => return simpleBinOp(gz, scope, rl, node, .sub), .sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap), - .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation), + .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic), .mul => return simpleBinOp(gz, scope, rl, node, .mul), .mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap), - .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation), + .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic), .div => return simpleBinOp(gz, scope, rl, node, .div), .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem), .bit_and => { @@ -2713,6 +2713,28 @@ fn assignOp( _ = try gz.addBin(.store, lhs_ptr, result); } +// TODO: is there an existing way to do this? +// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general +fn binOpExt( + gz: *GenZir, + scope: *Scope, + rl: ResultLoc, + infix_node: Ast.Node.Index, + lhs_node: Ast.Node.Index, + rhs_node: Ast.Node.Index, + tag: Zir.Inst.Extended, + comptime T: type, +) InnerError!Zir.Inst.Ref { + const lhs = try expr(gz, scope, .none, lhs_node); + const rhs = try expr(gz, scope, .none, rhs_node); + const result = try gz.addExtendedPayload(tag, T{ + .node = gz.nodeIndexToRelative(infix_node), + .lhs = lhs, + .rhs = rhs, + }); + return rvalue(gz, rl, result, infix_node); +} + // TODO: is there an existing method to accomplish this? // TODO: likely rename this to indicate rhs type coercion or add more params to make it more general fn assignBinOpExt( @@ -2720,8 +2742,8 @@ fn assignBinOpExt( scope: *Scope, infix_node: Ast.Node.Index, op_inst_tag: Zir.Inst.Extended, + comptime T: type, ) InnerError!void { - try emitDbgNode(gz, infix_node); const astgen = gz.astgen; const tree = astgen.tree; const node_datas = tree.nodes.items(.data); @@ -2730,7 +2752,7 @@ fn assignBinOpExt( const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node); const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs); - const result = try gz.addExtendedPayload(op_inst_tag, Zir.Inst.BinNode{ + const result = try gz.addExtendedPayload(op_inst_tag, T{ .node = gz.nodeIndexToRelative(infix_node), .lhs = lhs, .rhs = rhs, @@ -7903,26 +7925,6 @@ fn shiftOp( return rvalue(gz, rl, result, node); } -// TODO: is there an existing way to do this? -// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general -fn binOpExt( - gz: *GenZir, - scope: *Scope, - rl: ResultLoc, - node: Ast.Node.Index, - lhs_node: Ast.Node.Index, - rhs_node: Ast.Node.Index, - tag: Zir.Inst.Extended, -) InnerError!Zir.Inst.Ref { - const lhs = try expr(gz, scope, .none, lhs_node); - const rhs = try expr(gz, scope, .none, rhs_node); - const result = try gz.addExtendedPayload(tag, Zir.Inst.Bin{ - .lhs = lhs, - .rhs = rhs, - }); - return rvalue(gz, rl, result, node); -} - fn cImport( gz: *GenZir, scope: *Scope, diff --git a/src/Sema.zig b/src/Sema.zig index de94a8c6b8..a41d330285 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -694,10 +694,11 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr .c_define => return sema.zirCDefine( block, extended), .wasm_memory_size => return sema.zirWasmMemorySize( block, extended), .wasm_memory_grow => return sema.zirWasmMemoryGrow( block, extended), - .add_with_saturation=> return sema.zirSatArithmetic( block, extended), - .sub_with_saturation=> return sema.zirSatArithmetic( block, extended), - .mul_with_saturation=> return sema.zirSatArithmetic( block, extended), - .shl_with_saturation=> return sema.zirSatArithmetic( block, extended), + .add_with_saturation, + .sub_with_saturation, + .mul_with_saturation, + .shl_with_saturation, + => return sema.zirSatArithmetic( block, extended), // zig fmt: on } } @@ -6163,7 +6164,7 @@ fn zirNegate( const lhs = sema.resolveInst(.zero); const rhs = sema.resolveInst(inst_data.operand); - return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src); + return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src, null); } fn zirArithmetic( @@ -6183,7 +6184,7 @@ fn zirArithmetic( const lhs = sema.resolveInst(extra.lhs); const rhs = sema.resolveInst(extra.rhs); - return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); + return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src, null); } fn zirOverflowArithmetic( @@ -6209,10 +6210,17 @@ fn zirSatArithmetic( defer tracy.end(); const extra = sema.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; - return sema.mod.fail(&block.base, src, "TODO implement Sema.zirSatArithmetic", .{}); + sema.src = .{ .node_offset_bin_op = extra.node }; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = extra.node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = extra.node }; + const lhs = sema.resolveInst(extra.lhs); + const rhs = sema.resolveInst(extra.rhs); + + return sema.analyzeArithmetic(block, .extended, lhs, rhs, sema.src, lhs_src, rhs_src, extended); } +// TODO: audit - not sure if its a good idea to reuse this, adding `opt_extended` param +// FIXME: somehow, rhs of <<| is required to be Log2T. this should accept T fn analyzeArithmetic( sema: *Sema, block: *Scope.Block, @@ -6223,6 +6231,7 @@ fn analyzeArithmetic( src: LazySrcLoc, lhs_src: LazySrcLoc, rhs_src: LazySrcLoc, + opt_extended: ?Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const lhs_ty = sema.typeOf(lhs); const rhs_ty = sema.typeOf(rhs); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index dce0c10b4c..9ded6fe0e8 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -885,17 +885,17 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO // that wrapping is UB. .add, .ptr_add => try airBinOp( f, inst, " + "), .addwrap => try airWrapOp(f, inst, " + ", "addw_"), - .addsat => return o.dg.fail("TODO: C backend: implement codegen for addsat", .{}), + .addsat => return f.fail("TODO: C backend: implement codegen for addsat", .{}), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. .sub, .ptr_sub => try airBinOp( f, inst, " - "), .subwrap => try airWrapOp(f, inst, " - ", "subw_"), - .subsat => return o.dg.fail("TODO: C backend: implement codegen for subsat", .{}), + .subsat => return f.fail("TODO: C backend: implement codegen for subsat", .{}), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. .mul => try airBinOp( f, inst, " * "), .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"), - .mulsat => return o.dg.fail("TODO: C backend: implement codegen for mulsat", .{}), + .mulsat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}), // TODO use a different strategy for div that communicates to the optimizer // that wrapping is UB. .div => try airBinOp( f, inst, " / "), @@ -919,6 +919,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .shr => try airBinOp(f, inst, " >> "), .shl => try airBinOp(f, inst, " << "), + .shl_sat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}), + .not => try airNot( f, inst), @@ -1312,6 +1314,118 @@ fn airWrapOp( return ret; } +fn airSatOp( + o: *Object, + inst: Air.Inst.Index, + str_op: [*:0]const u8, + fn_op: [*:0]const u8, +) !CValue { + if (o.liveness.isUnused(inst)) + return CValue.none; + + const bin_op = o.air.instructions.items(.data)[inst].bin_op; + const inst_ty = o.air.typeOfIndex(inst); + const int_info = inst_ty.intInfo(o.dg.module.getTarget()); + const bits = int_info.bits; + + // if it's an unsigned int with non-arbitrary bit size then we can just add + const ok_bits = switch (bits) { + 8, 16, 32, 64, 128 => true, + else => false, + }; + + if (bits > 64) { + return f.fail("TODO: C backend: airSatOp for large integers", .{}); + } + + var min_buf: [80]u8 = undefined; + const min = switch (int_info.signedness) { + .unsigned => "0", + else => switch (inst_ty.tag()) { + .c_short => "SHRT_MIN", + .c_int => "INT_MIN", + .c_long => "LONG_MIN", + .c_longlong => "LLONG_MIN", + .isize => "INTPTR_MIN", + else => blk: { + const val = -1 * std.math.pow(i65, 2, @intCast(i65, bits - 1)); + break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + else => |e| return e, + }; + }, + }, + }; + + var max_buf: [80]u8 = undefined; + const max = switch (inst_ty.tag()) { + .c_short => "SHRT_MAX", + .c_ushort => "USHRT_MAX", + .c_int => "INT_MAX", + .c_uint => "UINT_MAX", + .c_long => "LONG_MAX", + .c_ulong => "ULONG_MAX", + .c_longlong => "LLONG_MAX", + .c_ulonglong => "ULLONG_MAX", + .isize => "INTPTR_MAX", + .usize => "UINTPTR_MAX", + else => blk: { + const pow_bits = switch (int_info.signedness) { + .signed => bits - 1, + .unsigned => bits, + }; + const val = std.math.pow(u65, 2, pow_bits) - 1; + break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + else => |e| return e, + }; + }, + }; + + const lhs = try o.resolveInst(bin_op.lhs); + const rhs = try o.resolveInst(bin_op.rhs); + const w = o.writer(); + + const ret = try o.allocLocal(inst_ty, .Mut); + try w.print(" = zig_{s}", .{fn_op}); + + switch (inst_ty.tag()) { + .isize => try w.writeAll("isize"), + .c_short => try w.writeAll("short"), + .c_int => try w.writeAll("int"), + .c_long => try w.writeAll("long"), + .c_longlong => try w.writeAll("longlong"), + else => { + const prefix_byte: u8 = switch (int_info.signedness) { + .signed => 'i', + .unsigned => 'u', + }; + for ([_]u8{ 8, 16, 32, 64 }) |nbits| { + if (bits <= nbits) { + try w.print("{c}{d}", .{ prefix_byte, nbits }); + break; + } + } else { + unreachable; + } + }, + } + + try w.writeByte('('); + try o.writeCValue(w, lhs); + try w.writeAll(", "); + try o.writeCValue(w, rhs); + + if (int_info.signedness == .signed) { + try w.print(", {s}", .{min}); + } + + try w.print(", {s});", .{max}); + try o.indent_writer.insertNewline(); + + return ret; +} + fn airNot(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index c21985d1c5..733984a9d6 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2038,7 +2038,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isFloat()) return self.builder.buildFAdd(lhs, rhs, ""); + if (inst_ty.isAnyFloat()) return self.builder.buildFAdd(lhs, rhs, ""); if (ty == .wrapping) return self.builder.buildAdd(lhs, rhs, "") else if (ty == .saturated) { @@ -2060,7 +2060,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isFloat()) return self.builder.buildFSub(lhs, rhs, ""); + if (inst_ty.isAnyFloat()) return self.builder.buildFSub(lhs, rhs, ""); if (ty == .wrapping) return self.builder.buildSub(lhs, rhs, "") else if (ty == .saturated) { @@ -2082,7 +2082,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); - if (inst_ty.isFloat()) return self.builder.buildFMul(lhs, rhs, ""); + if (inst_ty.isAnyFloat()) return self.builder.buildFMul(lhs, rhs, ""); if (ty == .wrapping) return self.builder.buildMul(lhs, rhs, "") else if (ty == .saturated) { diff --git a/src/link/C/zig.h b/src/link/C/zig.h index b34068d1f2..84b1c3dac6 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -356,3 +356,96 @@ static inline long long zig_subw_longlong(long long lhs, long long rhs, long lon return (long long)(((unsigned long long)lhs) - ((unsigned long long)rhs)); } +/* + * Saturating aritmetic operations: add, sub, mul, shl + */ +#define zig_add_sat_u(ZT, T) static inline T zig_adds_##ZT(T x, T y, T max) { \ + return (x > max - y) ? max : x + y; \ +} + +#define zig_add_sat_s(ZT, T, T2) static inline T zig_adds_##ZT(T2 x, T2 y, T2 min, T2 max) { \ + T2 res = x + y; \ + return (res < min) ? min : (res > max) ? max : res; \ +} + +zig_add_sat_u( u8, uint8_t) +zig_add_sat_s( i8, int8_t, int16_t) +zig_add_sat_u(u16, uint16_t) +zig_add_sat_s(i16, int16_t, int32_t) +zig_add_sat_u(u32, uint32_t) +zig_add_sat_s(i32, int32_t, int64_t) +zig_add_sat_u(u64, uint64_t) +zig_add_sat_s(i64, int64_t, int128_t) +zig_add_sat_s(isize, intptr_t, int128_t) +zig_add_sat_s(short, short, int) +zig_add_sat_s(int, int, long) +zig_add_sat_s(long, long, long long) + +#define zig_sub_sat_u(ZT, T) static inline T zig_subs_##ZT(T x, T y, T max) { \ + return (x > max + y) ? max : x - y; \ +} + +#define zig_sub_sat_s(ZT, T, T2) static inline T zig_subs_##ZT(T2 x, T2 y, T2 min, T2 max) { \ + T2 res = x - y; \ + return (res < min) ? min : (res > max) ? max : res; \ +} + +zig_sub_sat_u( u8, uint8_t) +zig_sub_sat_s( i8, int8_t, int16_t) +zig_sub_sat_u(u16, uint16_t) +zig_sub_sat_s(i16, int16_t, int32_t) +zig_sub_sat_u(u32, uint32_t) +zig_sub_sat_s(i32, int32_t, int64_t) +zig_sub_sat_u(u64, uint64_t) +zig_sub_sat_s(i64, int64_t, int128_t) +zig_sub_sat_s(isize, intptr_t, int128_t) +zig_sub_sat_s(short, short, int) +zig_sub_sat_s(int, int, long) +zig_sub_sat_s(long, long, long long) + + +#define zig_mul_sat_u(ZT, T, T2) static inline T zig_muls_##ZT(T2 x, T2 y, T2 max) { \ + T2 res = x * y; \ + return (res > max) ? max : res; \ +} + +#define zig_mul_sat_s(ZT, T, T2) static inline T zig_muls_##ZT(T2 x, T2 y, T2 min, T2 max) { \ + T2 res = x * y; \ + return (res < min) ? min : (res > max) ? max : res; \ +} + +zig_mul_sat_u(u8, uint8_t, uint16_t) +zig_mul_sat_s(i8, int8_t, int16_t) +zig_mul_sat_u(u16, uint16_t, uint32_t) +zig_mul_sat_s(i16, int16_t, int32_t) +zig_mul_sat_u(u32, uint32_t, uint64_t) +zig_mul_sat_s(i32, int32_t, int64_t) +zig_mul_sat_u(u64, uint64_t, uint128_t) +zig_mul_sat_s(i64, int64_t, int128_t) +zig_mul_sat_s(isize, intptr_t, int128_t) +zig_mul_sat_s(short, short, int) +zig_mul_sat_s(int, int, long) +zig_mul_sat_s(long, long, long long) + +#define zig_shl_sat_u(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T max) { \ + T leading_zeros = __builtin_clz(x); \ + return (leading_zeros + y > bits) ? max : x << y; \ +} + +#define zig_shl_sat_s(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T min, T max) { \ + T leading_zeros = __builtin_clz(x & ~max); \ + return (leading_zeros + y > bits) ? max : x << y; \ +} + +zig_shl_sat_u(u8, uint8_t, 8) +zig_shl_sat_s(i8, int8_t, 7) +zig_shl_sat_u(u16, uint16_t, 16) +zig_shl_sat_s(i16, int16_t, 15) +zig_shl_sat_u(u32, uint32_t, 32) +zig_shl_sat_s(i32, int32_t, 31) +zig_shl_sat_u(u64, uint64_t, 64) +zig_shl_sat_s(i64, int64_t, 63) +zig_shl_sat_s(isize, intptr_t, 63) +zig_shl_sat_s(short, short, 15) +zig_shl_sat_s(int, int, 31) +zig_shl_sat_s(long, long, 63) \ No newline at end of file From dcbc52ec85d00fbd3603c314aaaab98fb3866892 Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Wed, 8 Sep 2021 15:58:37 -0700 Subject: [PATCH 131/160] sat-arithmetic: correctly tokenize <<|, <<|= - set state rather than result.tag in tokenizer.zig - add test to tokenizer.zig for <<, <<|, <<|= --- lib/std/zig/tokenizer.zig | 8 +++++++- src/Air.zig | 6 +++--- src/stage1/tokenizer.cpp | 1 - 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 6afe7750d3..a7442b8b25 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -1007,7 +1007,7 @@ pub const Tokenizer = struct { break; }, '|' => { - result.tag = .angle_bracket_angle_bracket_left_pipe; + state = .angle_bracket_angle_bracket_left_pipe; }, else => { result.tag = .angle_bracket_angle_bracket_left; @@ -2015,6 +2015,12 @@ test "tokenizer - invalid token with unfinished escape right before eof" { try testTokenize("'\\u", &.{.invalid}); } +test "tokenizer - saturating" { + try testTokenize("<<", &.{.angle_bracket_angle_bracket_left}); + try testTokenize("<<|", &.{.angle_bracket_angle_bracket_left_pipe}); + try testTokenize("<<|=", &.{.angle_bracket_angle_bracket_left_pipe_equal}); +} + fn testTokenize(source: [:0]const u8, expected_tokens: []const Token.Tag) !void { var tokenizer = Tokenizer.init(source); for (expected_tokens) |expected_token_id| { diff --git a/src/Air.zig b/src/Air.zig index b7d3938352..00f223ad21 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -44,7 +44,7 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. addwrap, - /// Saturating integer addition. + /// Saturating integer addition. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. @@ -59,7 +59,7 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. subwrap, - /// Saturating integer subtraction. + /// Saturating integer subtraction. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. @@ -74,7 +74,7 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. mulwrap, - /// Saturating integer multiplication. + /// Saturating integer multiplication. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. diff --git a/src/stage1/tokenizer.cpp b/src/stage1/tokenizer.cpp index 3560193927..47e324c933 100644 --- a/src/stage1/tokenizer.cpp +++ b/src/stage1/tokenizer.cpp @@ -995,7 +995,6 @@ void tokenize(const char *source, Tokenization *out) { t.state = TokenizeState_start; break; case '|': - // t.out->ids.last() = TokenIdBitShiftLeftPipe; t.state = TokenizeState_angle_bracket_angle_bracket_left_pipe; break; default: From bdb90a07bbf0fdedca71f5deace7087bc562b437 Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Wed, 8 Sep 2021 16:30:11 -0700 Subject: [PATCH 132/160] sat-arithmetic: fixups zig fmt / astcheck --- src/AstGen.zig | 2 +- src/codegen/c.zig | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 25452cb386..d3235ace53 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2786,7 +2786,7 @@ fn assignShift( fn assignShiftSat( gz: *GenZir, scope: *Scope, - infix_node: ast.Node.Index, + infix_node: Ast.Node.Index, op_inst_tag: Zir.Inst.Tag, ) InnerError!void { try emitDbgNode(gz, infix_node); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 9ded6fe0e8..37e19d9e1a 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1317,7 +1317,6 @@ fn airWrapOp( fn airSatOp( o: *Object, inst: Air.Inst.Index, - str_op: [*:0]const u8, fn_op: [*:0]const u8, ) !CValue { if (o.liveness.isUnused(inst)) @@ -1328,12 +1327,12 @@ fn airSatOp( const int_info = inst_ty.intInfo(o.dg.module.getTarget()); const bits = int_info.bits; - // if it's an unsigned int with non-arbitrary bit size then we can just add - const ok_bits = switch (bits) { - 8, 16, 32, 64, 128 => true, - else => false, - }; + switch (bits) { + 8, 16, 32, 64, 128 => {}, + else => return o.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}), + } + // if it's an unsigned int with non-arbitrary bit size then we can just add if (bits > 64) { return f.fail("TODO: C backend: airSatOp for large integers", .{}); } From 6ba9f7474f6999e9239ce6459549667439945bf2 Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Wed, 8 Sep 2021 18:47:11 -0700 Subject: [PATCH 133/160] sat-arithmetic: fix docgen --- doc/docgen.zig | 8 ++++++++ src/link/C/zig.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index a3ae53a65e..148a8bedb7 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -1058,15 +1058,21 @@ fn tokenizeAndPrintRaw( .plus_equal, .plus_percent, .plus_percent_equal, + .plus_pipe, + .plus_pipe_equal, .minus, .minus_equal, .minus_percent, .minus_percent_equal, + .minus_pipe, + .minus_pipe_equal, .asterisk, .asterisk_equal, .asterisk_asterisk, .asterisk_percent, .asterisk_percent_equal, + .asterisk_pipe, + .asterisk_pipe_equal, .arrow, .colon, .slash, @@ -1079,6 +1085,8 @@ fn tokenizeAndPrintRaw( .angle_bracket_left_equal, .angle_bracket_angle_bracket_left, .angle_bracket_angle_bracket_left_equal, + .angle_bracket_angle_bracket_left_pipe, + .angle_bracket_angle_bracket_left_pipe_equal, .angle_bracket_right, .angle_bracket_right_equal, .angle_bracket_angle_bracket_right, diff --git a/src/link/C/zig.h b/src/link/C/zig.h index 84b1c3dac6..cb23492490 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -448,4 +448,4 @@ zig_shl_sat_s(i64, int64_t, 63) zig_shl_sat_s(isize, intptr_t, 63) zig_shl_sat_s(short, short, 15) zig_shl_sat_s(int, int, 31) -zig_shl_sat_s(long, long, 63) \ No newline at end of file +zig_shl_sat_s(long, long, 63) From 0f246257be5029e7bb73ac9a5ff356171007bc7a Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Wed, 8 Sep 2021 20:59:55 -0700 Subject: [PATCH 134/160] sat-arithmetic: update langref --- doc/langref.html.in | 97 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 6 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index a5dfa5c927..b6f49dab62 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1244,8 +1244,9 @@ fn divide(a: i32, b: i32) i32 {

    Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause undefined behavior on - integer overflow. Also available are operations such as {#syntax#}+%{#endsyntax#} and - {#syntax#}-%{#endsyntax#} which are defined to have wrapping arithmetic on all targets. + integer overflow. Alternative operators are provided for wrapping and saturating arithmetic on all targets. + {#syntax#}+%{#endsyntax#} and {#syntax#}-%{#endsyntax#} perform wrapping arithmetic + while {#syntax#}+|{#endsyntax#} and {#syntax#}-|{#endsyntax#} perform saturating arithmetic.

    Zig supports arbitrary bit-width integers, referenced by using @@ -1395,6 +1396,24 @@ a +%= b{#endsyntax#}

    {#syntax#}@as(u32, std.math.maxInt(u32)) +% 1 == 0{#endsyntax#}
    + +
    {#syntax#}a +| b
    +a +|= b{#endsyntax#}
    + +
      +
    • {#link|Integers#}
    • +
    + + Saturating Addition. +
      +
    • Invokes {#link|Peer Type Resolution#} for the operands.
    • +
    • See also {#link|@addWithSaturation#}.
    • +
    + + +
    {#syntax#}@as(u32, std.math.maxInt(u32)) +| 1 == @as(u32, std.math.maxInt(u32)){#endsyntax#}
    + +
    {#syntax#}a - b
     a -= b{#endsyntax#}
    @@ -1434,6 +1453,24 @@ a -%= b{#endsyntax#}
    {#syntax#}@as(u32, 0) -% 1 == std.math.maxInt(u32){#endsyntax#}
    + +
    {#syntax#}a -| b
    +a -|= b{#endsyntax#}
    + +
      +
    • {#link|Integers#}
    • +
    + + Saturating Subtraction. +
      +
    • Invokes {#link|Peer Type Resolution#} for the operands.
    • +
    • See also {#link|@subWithSaturation#}.
    • +
    + + +
    {#syntax#}@as(u32, 0) -| 1 == 0{#endsyntax#}
    + +
    {#syntax#}-a{#endsyntax#}
    @@ -1508,6 +1545,24 @@ a *%= b{#endsyntax#}
    {#syntax#}@as(u8, 200) *% 2 == 144{#endsyntax#}
    + +
    {#syntax#}a *| b
    +a *|= b{#endsyntax#}
    + +
      +
    • {#link|Integers#}
    • +
    + + Saturating Multiplication. +
      +
    • Invokes {#link|Peer Type Resolution#} for the operands.
    • +
    • See also {#link|@mulWithSaturation#}.
    • +
    + + +
    {#syntax#}@as(u8, 200) *| 2 == 255{#endsyntax#}
    + +
    {#syntax#}a / b
     a /= b{#endsyntax#}
    @@ -1577,6 +1632,24 @@ a <<= b{#endsyntax#}
    {#syntax#}1 << 8 == 256{#endsyntax#}
    + +
    {#syntax#}a <<| b
    +a <<|= b{#endsyntax#}
    + +
      +
    • {#link|Integers#}
    • +
    + + Saturating Bit Shift Left. +
      +
    • See also {#link|@shlExact#}.
    • +
    • See also {#link|@shlWithOverflow#}.
    • +
    + + +
    {#syntax#}@as(u8, 1) <<| 8 == 255{#endsyntax#}
    + +
    {#syntax#}a >> b
     a >>= b{#endsyntax#}
    @@ -1968,14 +2041,14 @@ const B = error{Two}; a!b x{} !x -x -%x ~x &x ?x -* / % ** *% || -+ - ++ +% -% -<< >> +* / % ** *% *| || ++ - ++ +% -% +| -| +<< >> <<| & ^ | orelse catch == != < > <= >= and or -= *= /= %= += -= <<= >>= &= ^= |={#endsyntax#} += *= *%= *|= /= %= += +%= +|= -= -%= -|= <<= <<|= >>= &= ^= |={#endsyntax#} {#header_close#} {#header_close#} {#header_open|Arrays#} @@ -11839,6 +11912,7 @@ AssignOp / PLUSEQUAL / MINUSEQUAL / LARROW2EQUAL + / LARROW2PIPEEQUAL / RARROW2EQUAL / AMPERSANDEQUAL / CARETEQUAL @@ -11873,6 +11947,8 @@ AdditionOp / PLUS2 / PLUSPERCENT / MINUSPERCENT + / PLUSPIPE + / MINUSPIPE MultiplyOp <- PIPE2 @@ -11881,6 +11957,7 @@ MultiplyOp / PERCENT / ASTERISK2 / ASTERISKPERCENT + / ASTERISKPIPE PrefixOp <- EXCLAMATIONMARK @@ -12044,6 +12121,8 @@ ASTERISK2 <- '**' skip ASTERISKEQUAL <- '*=' skip ASTERISKPERCENT <- '*%' ![=] skip ASTERISKPERCENTEQUAL <- '*%=' skip +ASTERISKPIPE <- '*|' ![=] skip +ASTERISKPIPEEQUAL <- '*|=' skip CARET <- '^' ![=] skip CARETEQUAL <- '^=' skip COLON <- ':' skip @@ -12060,6 +12139,8 @@ EXCLAMATIONMARK <- '!' ![=] skip EXCLAMATIONMARKEQUAL <- '!=' skip LARROW <- '<' ![<=] skip LARROW2 <- '<<' ![=] skip +LARROW2PIPE <- '<<|' ![=] skip +LARROW2PIPEEQUAL <- '<<|=' ![=] skip LARROW2EQUAL <- '<<=' skip LARROWEQUAL <- '<=' skip LBRACE <- '{' skip @@ -12069,6 +12150,8 @@ MINUS <- '-' ![%=>] skip MINUSEQUAL <- '-=' skip MINUSPERCENT <- '-%' ![=] skip MINUSPERCENTEQUAL <- '-%=' skip +MINUSPIPE <- '-|' ![=] skip +MINUSPIPEEQUAL <- '-|=' skip MINUSRARROW <- '->' skip PERCENT <- '%' ![=] skip PERCENTEQUAL <- '%=' skip @@ -12080,6 +12163,8 @@ PLUS2 <- '++' skip PLUSEQUAL <- '+=' skip PLUSPERCENT <- '+%' ![=] skip PLUSPERCENTEQUAL <- '+%=' skip +PLUSPIPE <- '+|' ![=] skip +PLUSPIPEEQUAL <- '+|=' skip LETTERC <- 'c' skip QUESTIONMARK <- '?' skip RARROW <- '>' ![>=] skip From 1d86eae5269edcac5f32d166f13ed27483f07688 Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Thu, 9 Sep 2021 13:07:59 -0700 Subject: [PATCH 135/160] sat-arithmetic: langref - remove syntax disclaimer --- doc/langref.html.in | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index b6f49dab62..5b3c26b937 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7241,8 +7241,7 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 { Returns {#syntax#}a + b{#endsyntax#}. The result will be clamped between the type maximum and minimum.

    - Once Saturating arithmetic. - is completed, the syntax {#syntax#}a +| b{#endsyntax#} will be equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}. + The syntax {#syntax#}a +| b{#endsyntax#} is equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}.

    {#header_close#} {#header_open|@alignCast#} @@ -8372,8 +8371,7 @@ test "@wasmMemoryGrow" { Returns {#syntax#}a * b{#endsyntax#}. The result will be clamped between the type maximum and minimum.

    - Once Saturating arithmetic. - is completed, the syntax {#syntax#}a *| b{#endsyntax#} will be equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}. + The syntax {#syntax#}a *| b{#endsyntax#} is equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}.

    NOTE: Currently there is a bug in the llvm.smul.fix.sat intrinsic which affects {#syntax#}@mulWithSaturation{#endsyntax#} of signed integers. @@ -8629,8 +8627,7 @@ test "@setRuntimeSafety" { Returns {#syntax#}a << b{#endsyntax#}. The result will be clamped between type minimum and maximum.

    - Once Saturating arithmetic. - is completed, the syntax {#syntax#}a <<| b{#endsyntax#} will be equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}. + The syntax {#syntax#}a <<| b{#endsyntax#} is equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}.

    Unlike other @shl builtins, shift_amt doesn't need to be a Log2T as saturated overshifting is well defined. @@ -8954,8 +8951,7 @@ fn doTheTest() !void { Returns {#syntax#}a - b{#endsyntax#}. The result will be clamped between the type maximum and minimum.

    - Once Saturating arithmetic. - is completed, the syntax {#syntax#}a -| b{#endsyntax#} will be equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}. + The syntax {#syntax#}a -| b{#endsyntax#} is equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}.

    {#header_close#} From 487059535242e2b94303502806feaa99d560c63b Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Thu, 9 Sep 2021 14:17:59 -0700 Subject: [PATCH 136/160] sat-arithmetic: add additional tokenizer tests --- lib/std/zig/tokenizer.zig | 12 ++++++++++++ src/codegen/c.zig | 1 + 2 files changed, 13 insertions(+) diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index a7442b8b25..02fa3dd381 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -2019,6 +2019,18 @@ test "tokenizer - saturating" { try testTokenize("<<", &.{.angle_bracket_angle_bracket_left}); try testTokenize("<<|", &.{.angle_bracket_angle_bracket_left_pipe}); try testTokenize("<<|=", &.{.angle_bracket_angle_bracket_left_pipe_equal}); + + try testTokenize("*", &.{.asterisk}); + try testTokenize("*|", &.{.asterisk_pipe}); + try testTokenize("*|=", &.{.asterisk_pipe_equal}); + + try testTokenize("+", &.{.plus}); + try testTokenize("+|", &.{.plus_pipe}); + try testTokenize("+|=", &.{.plus_pipe_equal}); + + try testTokenize("-", &.{.minus}); + try testTokenize("-|", &.{.minus_pipe}); + try testTokenize("-|=", &.{.minus_pipe_equal}); } fn testTokenize(source: [:0]const u8, expected_tokens: []const Token.Tag) !void { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 37e19d9e1a..6101740eea 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1347,6 +1347,7 @@ fn airSatOp( .c_longlong => "LLONG_MIN", .isize => "INTPTR_MIN", else => blk: { + // compute the type minimum based on the bitcount (bits) const val = -1 * std.math.pow(i65, 2, @intCast(i65, bits - 1)); break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, From fd8383545adc5f202e8098bd13b3bda3481ad235 Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Fri, 10 Sep 2021 15:38:49 -0700 Subject: [PATCH 137/160] sat-arithmetic: langref - use tags --- doc/langref.html.in | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 5b3c26b937..e750797997 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1397,8 +1397,8 @@ a +%= b{#endsyntax#} -
    {#syntax#}a +| b
    -a +|= b{#endsyntax#}
    +
    {#syntax#}a +| b
    +a +|= b{#endsyntax#}
    • {#link|Integers#}
    • @@ -1454,8 +1454,8 @@ a -%= b{#endsyntax#} -
      {#syntax#}a -| b
      -a -|= b{#endsyntax#}
      +
      {#syntax#}a -| b
      +a -|= b{#endsyntax#}
      • {#link|Integers#}
      • @@ -1546,8 +1546,8 @@ a *%= b{#endsyntax#} -
        {#syntax#}a *| b
        -a *|= b{#endsyntax#}
        +
        {#syntax#}a *| b
        +a *|= b{#endsyntax#}
        • {#link|Integers#}
        • @@ -1633,8 +1633,8 @@ a <<= b{#endsyntax#} -
          {#syntax#}a <<| b
          -a <<|= b{#endsyntax#}
          +
          {#syntax#}a <<| b
          +a <<|= b{#endsyntax#}
          • {#link|Integers#}
          • From 68050852fac6940d04e15900f135e6fc88845f9b Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Fri, 10 Sep 2021 15:41:43 -0700 Subject: [PATCH 138/160] sat-arithmetic: minor formatting changes --- lib/std/zig/Ast.zig | 2 +- src/codegen/llvm.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 3632551d17..b69da459d3 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -2577,7 +2577,7 @@ pub const Node = struct { array_mult, /// `lhs *% rhs`. main_token is the `*%`. mul_wrap, - /// `lhs *| rhs`. main_token is the `*%`. + /// `lhs *| rhs`. main_token is the `*|`. mul_sat, /// `lhs + rhs`. main_token is the `+`. add, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 733984a9d6..cdd19146b5 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1256,7 +1256,7 @@ pub const FuncGen = struct { .xor => try self.airXor(inst), .shl => try self.airShl(inst, false), - .shl_sat => try self.airShl(inst, true), + .shl_sat => try self.airShl(inst, true), .shr => try self.airShr(inst), .cmp_eq => try self.airCmp(inst, .eq), From cd8d8add9153b17b4579c2e8951ac3f3f42e1bcd Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Tue, 14 Sep 2021 18:26:28 -0700 Subject: [PATCH 139/160] sat-arithmetic: fix shl methods in cbe --- src/link/C/zig.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/link/C/zig.h b/src/link/C/zig.h index cb23492490..5c9d750729 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -428,17 +428,21 @@ zig_mul_sat_s(int, int, long) zig_mul_sat_s(long, long, long long) #define zig_shl_sat_u(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T max) { \ - T leading_zeros = __builtin_clz(x); \ - return (leading_zeros + y > bits) ? max : x << y; \ + if(x == 0) return 0; \ + T bits_set = 64 - __builtin_clzll(x); \ + return (bits_set + y > bits) ? max : x << y; \ } #define zig_shl_sat_s(ZT, T, bits) static inline T zig_shls_##ZT(T x, T y, T min, T max) { \ - T leading_zeros = __builtin_clz(x & ~max); \ - return (leading_zeros + y > bits) ? max : x << y; \ + if(x == 0) return 0; \ + T x_twos_comp = x < 0 ? -x : x; \ + T bits_set = 64 - __builtin_clzll(x_twos_comp); \ + T min_or_max = (x < 0) ? min : max; \ + return (y + bits_set > bits ) ? min_or_max : x << y; \ } -zig_shl_sat_u(u8, uint8_t, 8) -zig_shl_sat_s(i8, int8_t, 7) +zig_shl_sat_u(u8, uint8_t, 8) +zig_shl_sat_s(i8, int8_t, 7) zig_shl_sat_u(u16, uint16_t, 16) zig_shl_sat_s(i16, int16_t, 15) zig_shl_sat_u(u32, uint32_t, 32) From baaec94fe427efad4fe46ee3ffde53184cbd0ae9 Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Tue, 14 Sep 2021 18:34:52 -0700 Subject: [PATCH 140/160] sat-arithmetic: create Sema.analyzeSatArithmetic - similar to Sema.analyzeArithmetic but uses accepts Zir.Inst.Extended.InstData - missing support for Pointer types and comptime arithmetic --- src/AstGen.zig | 26 +++++++-------- src/Sema.zig | 90 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index d3235ace53..9dc09ecd27 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -535,7 +535,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_bit_shift_left_sat => { - try assignBinOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic); + try assignOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic); return rvalue(gz, rl, .void_value, node); }, .assign_bit_shift_right => { @@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_sub_sat => { - try assignBinOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic); + try assignOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic); return rvalue(gz, rl, .void_value, node); }, .assign_mod => { @@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_add_sat => { - try assignBinOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic); + try assignOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic); return rvalue(gz, rl, .void_value, node); }, .assign_mul => { @@ -596,26 +596,28 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_mul_sat => { - try assignBinOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic); + try assignOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic); return rvalue(gz, rl, .void_value, node); }, // zig fmt: off .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl), - .bit_shift_left_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic), .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr), .add => return simpleBinOp(gz, scope, rl, node, .add), .add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap), - .add_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic), .sub => return simpleBinOp(gz, scope, rl, node, .sub), .sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap), - .sub_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic), .mul => return simpleBinOp(gz, scope, rl, node, .mul), .mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap), - .mul_sat => return binOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic), .div => return simpleBinOp(gz, scope, rl, node, .div), .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem), + + .add_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic), + .sub_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic), + .mul_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic), + .bit_shift_left_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic), + .bit_and => { const current_ampersand_token = main_tokens[node]; if (token_tags[current_ampersand_token + 1] == .ampersand) { @@ -2713,9 +2715,7 @@ fn assignOp( _ = try gz.addBin(.store, lhs_ptr, result); } -// TODO: is there an existing way to do this? -// TODO: likely rename this to reflect result_loc == .none or add more params to make it more general -fn binOpExt( +fn simpleBinOpExt( gz: *GenZir, scope: *Scope, rl: ResultLoc, @@ -2735,9 +2735,7 @@ fn binOpExt( return rvalue(gz, rl, result, infix_node); } -// TODO: is there an existing method to accomplish this? -// TODO: likely rename this to indicate rhs type coercion or add more params to make it more general -fn assignBinOpExt( +fn assignOpExt( gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index, diff --git a/src/Sema.zig b/src/Sema.zig index a41d330285..be10b6d663 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6164,7 +6164,7 @@ fn zirNegate( const lhs = sema.resolveInst(.zero); const rhs = sema.resolveInst(inst_data.operand); - return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src, null); + return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src); } fn zirArithmetic( @@ -6184,7 +6184,7 @@ fn zirArithmetic( const lhs = sema.resolveInst(extra.lhs); const rhs = sema.resolveInst(extra.rhs); - return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src, null); + return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); } fn zirOverflowArithmetic( @@ -6216,11 +6216,90 @@ fn zirSatArithmetic( const lhs = sema.resolveInst(extra.lhs); const rhs = sema.resolveInst(extra.rhs); - return sema.analyzeArithmetic(block, .extended, lhs, rhs, sema.src, lhs_src, rhs_src, extended); + return sema.analyzeSatArithmetic(block, lhs, rhs, sema.src, lhs_src, rhs_src, extended); +} + +fn analyzeSatArithmetic( + sema: *Sema, + block: *Scope.Block, + lhs: Air.Inst.Ref, + rhs: Air.Inst.Ref, + src: LazySrcLoc, + lhs_src: LazySrcLoc, + rhs_src: LazySrcLoc, + extended: Zir.Inst.Extended.InstData, +) CompileError!Air.Inst.Ref { + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) { + if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) { + return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{ + lhs_ty.arrayLen(), rhs_ty.arrayLen(), + }); + } + return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{}); + } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) { + return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{ + lhs_ty, rhs_ty, + }); + } + + if (lhs_zig_ty_tag == .Pointer or rhs_zig_ty_tag == .Pointer) + return sema.mod.fail(&block.base, src, "TODO implement support for pointers in zirSatArithmetic", .{}); + + const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } }); + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); + const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); + + const scalar_type = if (resolved_type.zigTypeTag() == .Vector) + resolved_type.elemType() + else + resolved_type; + + const scalar_tag = scalar_type.zigTypeTag(); + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + + if (!is_int) + return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ + @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag), + }); + + if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { + if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { + if (lhs_val.isUndef() or rhs_val.isUndef()) { + return sema.addConstUndef(resolved_type); + } + // incase rhs is 0, simply return lhs without doing any calculations + if (rhs_val.compareWithZero(.eq)) { + switch (extended.opcode) { + .add_with_saturation, .sub_with_saturation => return sema.addConstant(scalar_type, lhs_val), + else => {}, + } + } + + return sema.mod.fail(&block.base, src, "TODO implement comptime saturating arithmetic for operand '{s}'", .{@tagName(extended.opcode)}); + } else { + try sema.requireRuntimeBlock(block, rhs_src); + } + } else { + try sema.requireRuntimeBlock(block, lhs_src); + } + + const air_tag: Air.Inst.Tag = switch (extended.opcode) { + .add_with_saturation => .addsat, + .sub_with_saturation => .subsat, + .mul_with_saturation => .mulsat, + .shl_with_saturation => .shl_sat, + else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for extended opcode '{s}'", .{@tagName(extended.opcode)}), + }; + + return block.addBinOp(air_tag, casted_lhs, casted_rhs); } -// TODO: audit - not sure if its a good idea to reuse this, adding `opt_extended` param -// FIXME: somehow, rhs of <<| is required to be Log2T. this should accept T fn analyzeArithmetic( sema: *Sema, block: *Scope.Block, @@ -6231,7 +6310,6 @@ fn analyzeArithmetic( src: LazySrcLoc, lhs_src: LazySrcLoc, rhs_src: LazySrcLoc, - opt_extended: ?Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const lhs_ty = sema.typeOf(lhs); const rhs_ty = sema.typeOf(rhs); From 38703dc9c2dccc43c77ec8dcfe0df936cced9d7a Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Tue, 14 Sep 2021 18:40:28 -0700 Subject: [PATCH 141/160] sat-arithmetic: don't test builtins in behavior tests - not necessary as we are testing the operators --- test/behavior/saturating_arithmetic.zig | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig index 7a28ed182d..5d7a229c3c 100644 --- a/test/behavior/saturating_arithmetic.zig +++ b/test/behavior/saturating_arithmetic.zig @@ -11,15 +11,6 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void { const a = test_data[0]; const b = test_data[1]; const expected = test_data[2]; - { - const actual = switch (op) { - .add => @addWithSaturation(a, b), - .sub => @subWithSaturation(a, b), - .mul => @mulWithSaturation(a, b), - .shl => @shlWithSaturation(a, b), - }; - try expectEqual(expected, actual); - } { const actual = switch (op) { .add => a +| b, From 51673bcb315d837ee0fe80dc50571c2c07d80a2c Mon Sep 17 00:00:00 2001 From: Travis Staloch Date: Wed, 22 Sep 2021 00:14:08 -0700 Subject: [PATCH 142/160] get build passing again after rebase --- src/codegen/c.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 6101740eea..1afa81b70f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1334,7 +1334,7 @@ fn airSatOp( // if it's an unsigned int with non-arbitrary bit size then we can just add if (bits > 64) { - return f.fail("TODO: C backend: airSatOp for large integers", .{}); + return o.dg.fail("TODO: C backend: airSatOp for large integers", .{}); } var min_buf: [80]u8 = undefined; From 6cb7906394281370173cd1c1fc79ad93562005a0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Sep 2021 16:55:42 -0700 Subject: [PATCH 143/160] add missing zig fmt test for saturating arithmetic --- lib/std/zig/parser_test.zig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 2f79cc175c..57f081decb 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -4739,6 +4739,26 @@ test "zig fmt: assignment with inline for and inline while" { ); } +test "zig fmt: saturating arithmetic" { + try testCanonical( + \\test { + \\ const actual = switch (op) { + \\ .add => a +| b, + \\ .sub => a -| b, + \\ .mul => a *| b, + \\ .shl => a <<| b, + \\ }; + \\ switch (op) { + \\ .add => actual +|= b, + \\ .sub => actual -|= b, + \\ .mul => actual *|= b, + \\ .shl => actual <<|= b, + \\ } + \\} + \\ + ); +} + test "zig fmt: insert trailing comma if there are comments between switch values" { try testTransform( \\const a = switch (b) { From 71da169c67ad544bd1d4dfc4bfff9fe302e8284d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Sep 2021 16:55:57 -0700 Subject: [PATCH 144/160] AstGen: delete dead code --- src/AstGen.zig | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 9dc09ecd27..92087a7719 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2781,29 +2781,6 @@ fn assignShift( _ = try gz.addBin(.store, lhs_ptr, result); } -fn assignShiftSat( - gz: *GenZir, - scope: *Scope, - infix_node: Ast.Node.Index, - op_inst_tag: Zir.Inst.Tag, -) InnerError!void { - try emitDbgNode(gz, infix_node); - const astgen = gz.astgen; - const tree = astgen.tree; - const node_datas = tree.nodes.items(.data); - - const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs); - const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); - const rhs_type = try gz.addUnNode(.typeof, lhs, infix_node); - const rhs = try expr(gz, scope, .{ .ty = rhs_type }, node_datas[infix_node].rhs); - - const result = try gz.addPlNode(op_inst_tag, infix_node, Zir.Inst.Bin{ - .lhs = lhs, - .rhs = rhs, - }); - _ = try gz.addBin(.store, lhs_ptr, result); -} - fn boolNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; const tree = astgen.tree; From 54675824449d16029fdf6a1873e78cb8f2147f60 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Sep 2021 18:55:43 -0700 Subject: [PATCH 145/160] saturating arithmetic modifications * Remove the builtins `@addWithSaturation`, `@subWithSaturation`, `@mulWithSaturation`, and `@shlWithSaturation` now that we have first-class syntax for saturating arithmetic. * langref: Clarify the behavior of `@shlExact`. * Ast: rename `bit_shift_left` to `shl` and `bit_shift_right` to `shr` for consistency. * Air: rename to include underscore separator with consistency with the rest of the ops. * Air: add shl_exact instruction * Use non-extended tags for saturating arithmetic, to keep it simple so that all the arithmetic operations can be done the same way. - Sema: unify analyzeArithmetic with analyzeSatArithmetic - implement comptime `+|`, `-|`, and `*|` - allow float operands to saturating arithmetic * `<<|` allows any integer type for the RHS. * C backend: fix rebase conflicts * LLVM backend: reduce the amount of branching for arithmetic ops * zig.h: fix magic number not matching actual size of C integer types --- doc/langref.html.in | 63 +------ lib/std/zig/Ast.zig | 36 ++-- lib/std/zig/parse.zig | 12 +- lib/std/zig/render.zig | 20 +- src/Air.zig | 18 +- src/AstGen.zig | 167 ++++++----------- src/BuiltinFn.zig | 32 ---- src/Liveness.zig | 7 +- src/Sema.zig | 234 ++++++++++++------------ src/Zir.zig | 143 +++++++-------- src/codegen.zig | 56 ++++-- src/codegen/c.zig | 76 ++++---- src/codegen/llvm.zig | 188 +++++++++++++------ src/codegen/llvm/bindings.zig | 6 + src/link/C/zig.h | 11 +- src/print_air.zig | 7 +- src/print_zir.zig | 22 +-- src/stage1/all_types.hpp | 4 - src/stage1/astgen.cpp | 60 ------ src/stage1/codegen.cpp | 4 - src/translate_c/ast.zig | 8 +- src/value.zig | 87 +++++++++ test/behavior/saturating_arithmetic.zig | 33 +--- 23 files changed, 623 insertions(+), 671 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index e750797997..2e69e37097 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -1407,7 +1407,6 @@ a +|= b{#endsyntax#} Saturating Addition.
            • Invokes {#link|Peer Type Resolution#} for the operands.
            • -
            • See also {#link|@addWithSaturation#}.
            @@ -1464,7 +1463,6 @@ a -|= b{#endsyntax#} Saturating Subtraction.
            • Invokes {#link|Peer Type Resolution#} for the operands.
            • -
            • See also {#link|@subWithSaturation#}.
            @@ -1556,7 +1554,6 @@ a *|= b{#endsyntax#} Saturating Multiplication.
            • Invokes {#link|Peer Type Resolution#} for the operands.
            • -
            • See also {#link|@mulWithSaturation#}.
            @@ -7235,15 +7232,6 @@ fn readFile(allocator: *Allocator, filename: []const u8) ![]u8 { If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.

            {#header_close#} - {#header_open|@addWithSaturation#} -
            {#syntax#}@addWithSaturation(a: T, b: T) T{#endsyntax#}
            -

            - Returns {#syntax#}a + b{#endsyntax#}. The result will be clamped between the type maximum and minimum. -

            -

            - The syntax {#syntax#}a +| b{#endsyntax#} is equivalent to calling {#syntax#}@addWithSaturation(a, b){#endsyntax#}. -

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

            @@ -8365,21 +8353,6 @@ test "@wasmMemoryGrow" {

            {#header_close#} - {#header_open|@mulWithSaturation#} -
            {#syntax#}@mulWithSaturation(a: T, b: T) T{#endsyntax#}
            -

            - Returns {#syntax#}a * b{#endsyntax#}. The result will be clamped between the type maximum and minimum. -

            -

            - The syntax {#syntax#}a *| b{#endsyntax#} is equivalent to calling {#syntax#}@mulWithSaturation(a, b){#endsyntax#}. -

            -

            - NOTE: Currently there is a bug in the llvm.smul.fix.sat intrinsic which affects {#syntax#}@mulWithSaturation{#endsyntax#} of signed integers. - This may result in an incorrect sign bit when there is overflow. This will be fixed in zig's 0.9.0 release. - Check this issue for more information. -

            - {#header_close#} - {#header_open|@panic#}
            {#syntax#}@panic(message: []const u8) noreturn{#endsyntax#}

            @@ -8597,14 +8570,16 @@ test "@setRuntimeSafety" { {#header_open|@shlExact#}

            {#syntax#}@shlExact(value: T, shift_amt: Log2T) T{#endsyntax#}

            - Performs the left shift operation ({#syntax#}<<{#endsyntax#}). Caller guarantees - that the shift will not shift any 1 bits out. + Performs the left shift operation ({#syntax#}<<{#endsyntax#}). + For unsigned integers, the result is {#link|undefined#} if any 1 bits + are shifted out. For signed integers, the result is {#link|undefined#} if + any bits that disagree with the resultant sign bit are shifted out.

            The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits. This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.

            - {#see_also|@shrExact|@shlWithOverflow|@shlWithSaturation#} + {#see_also|@shrExact|@shlWithOverflow#} {#header_close#} {#header_open|@shlWithOverflow#} @@ -8618,23 +8593,9 @@ test "@setRuntimeSafety" { The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits. This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.

            - {#see_also|@shlExact|@shrExact|@shlWithSaturation#} + {#see_also|@shlExact|@shrExact#} {#header_close#} - {#header_open|@shlWithSaturation#} -
            {#syntax#}@shlWithSaturation(a: T, shift_amt: T) T{#endsyntax#}
            -

            - Returns {#syntax#}a << b{#endsyntax#}. The result will be clamped between type minimum and maximum. -

            -

            - The syntax {#syntax#}a <<| b{#endsyntax#} is equivalent to calling {#syntax#}@shlWithSaturation(a, b){#endsyntax#}. -

            -

            - Unlike other @shl builtins, shift_amt doesn't need to be a Log2T as saturated overshifting is well defined. -

            - {#see_also|@shlExact|@shrExact|@shlWithOverflow#} - {#header_close#} - {#header_open|@shrExact#}
            {#syntax#}@shrExact(value: T, shift_amt: Log2T) T{#endsyntax#}

            @@ -8645,7 +8606,7 @@ test "@setRuntimeSafety" { The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(T.bit_count){#endsyntax#} bits. This is because {#syntax#}shift_amt >= T.bit_count{#endsyntax#} is undefined behavior.

            - {#see_also|@shlExact|@shlWithOverflow|@shlWithSaturation#} + {#see_also|@shlExact|@shlWithOverflow#} {#header_close#} {#header_open|@shuffle#} @@ -8945,16 +8906,6 @@ fn doTheTest() !void {

            {#header_close#} - {#header_open|@subWithSaturation#} -
            {#syntax#}@subWithSaturation(a: T, b: T) T{#endsyntax#}
            -

            - Returns {#syntax#}a - b{#endsyntax#}. The result will be clamped between the type maximum and minimum. -

            -

            - The syntax {#syntax#}a -| b{#endsyntax#} is equivalent to calling {#syntax#}@subWithSaturation(a, b){#endsyntax#}. -

            - {#header_close#} - {#header_open|@tagName#}
            {#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}

            diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index b69da459d3..4ee3a45221 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -395,9 +395,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex { .assign_mod, .assign_add, .assign_sub, - .assign_bit_shift_left, - .assign_bit_shift_left_sat, - .assign_bit_shift_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .assign_bit_and, .assign_bit_xor, .assign_bit_or, @@ -422,9 +422,9 @@ pub fn firstToken(tree: Tree, node: Node.Index) TokenIndex { .sub_wrap, .add_sat, .sub_sat, - .bit_shift_left, - .bit_shift_left_sat, - .bit_shift_right, + .shl, + .shl_sat, + .shr, .bit_and, .bit_xor, .bit_or, @@ -659,9 +659,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { .assign_mod, .assign_add, .assign_sub, - .assign_bit_shift_left, - .assign_bit_shift_left_sat, - .assign_bit_shift_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .assign_bit_and, .assign_bit_xor, .assign_bit_or, @@ -686,9 +686,9 @@ pub fn lastToken(tree: Tree, node: Node.Index) TokenIndex { .sub_wrap, .add_sat, .sub_sat, - .bit_shift_left, - .bit_shift_left_sat, - .bit_shift_right, + .shl, + .shl_sat, + .shr, .bit_and, .bit_xor, .bit_or, @@ -2540,11 +2540,11 @@ pub const Node = struct { /// `lhs -= rhs`. main_token is op. assign_sub, /// `lhs <<= rhs`. main_token is op. - assign_bit_shift_left, + assign_shl, /// `lhs <<|= rhs`. main_token is op. - assign_bit_shift_left_sat, + assign_shl_sat, /// `lhs >>= rhs`. main_token is op. - assign_bit_shift_right, + assign_shr, /// `lhs &= rhs`. main_token is op. assign_bit_and, /// `lhs ^= rhs`. main_token is op. @@ -2594,11 +2594,11 @@ pub const Node = struct { /// `lhs -| rhs`. main_token is the `-|`. sub_sat, /// `lhs << rhs`. main_token is the `<<`. - bit_shift_left, + shl, /// `lhs <<| rhs`. main_token is the `<<|`. - bit_shift_left_sat, + shl_sat, /// `lhs >> rhs`. main_token is the `>>`. - bit_shift_right, + shr, /// `lhs & rhs`. main_token is the `&`. bit_and, /// `lhs ^ rhs`. main_token is the `^`. diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index a2780b5225..021b028455 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1268,9 +1268,9 @@ const Parser = struct { .percent_equal => .assign_mod, .plus_equal => .assign_add, .minus_equal => .assign_sub, - .angle_bracket_angle_bracket_left_equal => .assign_bit_shift_left, - .angle_bracket_angle_bracket_left_pipe_equal => .assign_bit_shift_left_sat, - .angle_bracket_angle_bracket_right_equal => .assign_bit_shift_right, + .angle_bracket_angle_bracket_left_equal => .assign_shl, + .angle_bracket_angle_bracket_left_pipe_equal => .assign_shl_sat, + .angle_bracket_angle_bracket_right_equal => .assign_shr, .ampersand_equal => .assign_bit_and, .caret_equal => .assign_bit_xor, .pipe_equal => .assign_bit_or, @@ -1346,9 +1346,9 @@ const Parser = struct { .keyword_orelse = .{ .prec = 40, .tag = .@"orelse" }, .keyword_catch = .{ .prec = 40, .tag = .@"catch" }, - .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .bit_shift_left }, - .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .bit_shift_left_sat }, - .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .bit_shift_right }, + .angle_bracket_angle_bracket_left = .{ .prec = 50, .tag = .shl }, + .angle_bracket_angle_bracket_left_pipe = .{ .prec = 50, .tag = .shl_sat }, + .angle_bracket_angle_bracket_right = .{ .prec = 50, .tag = .shr }, .plus = .{ .prec = 60, .tag = .add }, .minus = .{ .prec = 60, .tag = .sub }, diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 47f019d1cf..4357960251 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -339,9 +339,9 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, .assign, .assign_bit_and, .assign_bit_or, - .assign_bit_shift_left, - .assign_bit_shift_left_sat, - .assign_bit_shift_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .assign_bit_xor, .assign_div, .assign_sub, @@ -357,9 +357,9 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, .bang_equal, .bit_and, .bit_or, - .bit_shift_left, - .bit_shift_left_sat, - .bit_shift_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bool_and, .bool_or, @@ -2528,8 +2528,8 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool { .assign, .assign_bit_and, .assign_bit_or, - .assign_bit_shift_left, - .assign_bit_shift_right, + .assign_shl, + .assign_shr, .assign_bit_xor, .assign_div, .assign_sub, @@ -2542,8 +2542,8 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool { .bang_equal, .bit_and, .bit_or, - .bit_shift_left, - .bit_shift_right, + .shl, + .shr, .bit_xor, .bool_and, .bool_or, diff --git a/src/Air.zig b/src/Air.zig index 00f223ad21..f05c18e87a 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -48,7 +48,7 @@ pub const Inst = struct { /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. - addsat, + add_sat, /// Float or integer subtraction. For integers, wrapping is undefined behavior. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. @@ -63,7 +63,7 @@ pub const Inst = struct { /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. - subsat, + sub_sat, /// Float or integer multiplication. For integers, wrapping is undefined behavior. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. @@ -78,7 +78,7 @@ pub const Inst = struct { /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. /// Uses the `bin_op` field. - mulsat, + mul_sat, /// Integer or float division. For integers, wrapping is undefined behavior. /// Both operands are guaranteed to be the same type, and the result type /// is the same as both operands. @@ -125,6 +125,11 @@ pub const Inst = struct { /// Shift left. `<<` /// Uses the `bin_op` field. shl, + /// Shift left; For unsigned integers, the shift produces a poison value if it shifts + /// out any non-zero bits. For signed integers, the shift produces a poison value if + /// it shifts out any bits that disagree with the resultant sign bit. + /// Uses the `bin_op` field. + shl_exact, /// Shift left saturating. `<<|` /// Uses the `bin_op` field. shl_sat, @@ -586,13 +591,13 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .add, .addwrap, - .addsat, + .add_sat, .sub, .subwrap, - .subsat, + .sub_sat, .mul, .mulwrap, - .mulsat, + .mul_sat, .div, .rem, .mod, @@ -603,6 +608,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .ptr_sub, .shr, .shl, + .shl_exact, .shl_sat, => return air.typeOf(datas[inst].bin_op.lhs), diff --git a/src/AstGen.zig b/src/AstGen.zig index 92087a7719..847860630a 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -317,9 +317,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins .assign, .assign_bit_and, .assign_bit_or, - .assign_bit_shift_left, - .assign_bit_shift_left_sat, - .assign_bit_shift_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .assign_bit_xor, .assign_div, .assign_sub, @@ -345,9 +345,9 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins .mod, .bit_and, .bit_or, - .bit_shift_left, - .bit_shift_left_sat, - .bit_shift_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bang_equal, .equal_equal, @@ -530,15 +530,15 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, - .assign_bit_shift_left => { + .assign_shl => { try assignShift(gz, scope, node, .shl); return rvalue(gz, rl, .void_value, node); }, - .assign_bit_shift_left_sat => { - try assignOpExt(gz, scope, node, .shl_with_saturation, Zir.Inst.SaturatingArithmetic); + .assign_shl_sat => { + try assignShiftSat(gz, scope, node); return rvalue(gz, rl, .void_value, node); }, - .assign_bit_shift_right => { + .assign_shr => { try assignShift(gz, scope, node, .shr); return rvalue(gz, rl, .void_value, node); }, @@ -568,7 +568,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_sub_sat => { - try assignOpExt(gz, scope, node, .sub_with_saturation, Zir.Inst.SaturatingArithmetic); + try assignOp(gz, scope, node, .sub_sat); return rvalue(gz, rl, .void_value, node); }, .assign_mod => { @@ -584,7 +584,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_add_sat => { - try assignOpExt(gz, scope, node, .add_with_saturation, Zir.Inst.SaturatingArithmetic); + try assignOp(gz, scope, node, .add_sat); return rvalue(gz, rl, .void_value, node); }, .assign_mul => { @@ -596,28 +596,27 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr return rvalue(gz, rl, .void_value, node); }, .assign_mul_sat => { - try assignOpExt(gz, scope, node, .mul_with_saturation, Zir.Inst.SaturatingArithmetic); + try assignOp(gz, scope, node, .mul_sat); return rvalue(gz, rl, .void_value, node); }, // zig fmt: off - .bit_shift_left => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl), - .bit_shift_right => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr), + .shl => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl), + .shr => return shiftOp(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shr), .add => return simpleBinOp(gz, scope, rl, node, .add), .add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap), + .add_sat => return simpleBinOp(gz, scope, rl, node, .add_sat), .sub => return simpleBinOp(gz, scope, rl, node, .sub), .sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap), + .sub_sat => return simpleBinOp(gz, scope, rl, node, .sub_sat), .mul => return simpleBinOp(gz, scope, rl, node, .mul), .mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap), + .mul_sat => return simpleBinOp(gz, scope, rl, node, .mul_sat), .div => return simpleBinOp(gz, scope, rl, node, .div), .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem), + .shl_sat => return simpleBinOp(gz, scope, rl, node, .shl_sat), - .add_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .add_with_saturation, Zir.Inst.SaturatingArithmetic), - .sub_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .sub_with_saturation, Zir.Inst.SaturatingArithmetic), - .mul_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .mul_with_saturation, Zir.Inst.SaturatingArithmetic), - .bit_shift_left_sat => return simpleBinOpExt(gz, scope, rl, node, node_datas[node].lhs, node_datas[node].rhs, .shl_with_saturation, Zir.Inst.SaturatingArithmetic), - .bit_and => { const current_ampersand_token = main_tokens[node]; if (token_tags[current_ampersand_token + 1] == .ampersand) { @@ -1928,8 +1927,8 @@ fn blockExprStmts(gz: *GenZir, parent_scope: *Scope, statements: []const Ast.Nod .assign => try assign(gz, scope, statement), - .assign_bit_shift_left => try assignShift(gz, scope, statement, .shl), - .assign_bit_shift_right => try assignShift(gz, scope, statement, .shr), + .assign_shl => try assignShift(gz, scope, statement, .shl), + .assign_shr => try assignShift(gz, scope, statement, .shr), .assign_bit_and => try assignOp(gz, scope, statement, .bit_and), .assign_bit_or => try assignOp(gz, scope, statement, .bit_or), @@ -1979,6 +1978,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner // ZIR instructions that might be a type other than `noreturn` or `void`. .add, .addwrap, + .add_sat, .param, .param_comptime, .param_anytype, @@ -2045,12 +2045,15 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .mod_rem, .mul, .mulwrap, + .mul_sat, .ref, .shl, + .shl_sat, .shr, .str, .sub, .subwrap, + .sub_sat, .negate, .negate_wrap, .typeof, @@ -2715,49 +2718,6 @@ fn assignOp( _ = try gz.addBin(.store, lhs_ptr, result); } -fn simpleBinOpExt( - gz: *GenZir, - scope: *Scope, - rl: ResultLoc, - infix_node: Ast.Node.Index, - lhs_node: Ast.Node.Index, - rhs_node: Ast.Node.Index, - tag: Zir.Inst.Extended, - comptime T: type, -) InnerError!Zir.Inst.Ref { - const lhs = try expr(gz, scope, .none, lhs_node); - const rhs = try expr(gz, scope, .none, rhs_node); - const result = try gz.addExtendedPayload(tag, T{ - .node = gz.nodeIndexToRelative(infix_node), - .lhs = lhs, - .rhs = rhs, - }); - return rvalue(gz, rl, result, infix_node); -} - -fn assignOpExt( - gz: *GenZir, - scope: *Scope, - infix_node: Ast.Node.Index, - op_inst_tag: Zir.Inst.Extended, - comptime T: type, -) InnerError!void { - const astgen = gz.astgen; - const tree = astgen.tree; - const node_datas = tree.nodes.items(.data); - - const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs); - const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); - const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node); - const rhs = try expr(gz, scope, .{ .coerced_ty = lhs_type }, node_datas[infix_node].rhs); - const result = try gz.addExtendedPayload(op_inst_tag, T{ - .node = gz.nodeIndexToRelative(infix_node), - .lhs = lhs, - .rhs = rhs, - }); - _ = try gz.addBin(.store, lhs_ptr, result); -} - fn assignShift( gz: *GenZir, scope: *Scope, @@ -2781,6 +2741,24 @@ fn assignShift( _ = try gz.addBin(.store, lhs_ptr, result); } +fn assignShiftSat(gz: *GenZir, scope: *Scope, infix_node: Ast.Node.Index) InnerError!void { + try emitDbgNode(gz, infix_node); + const astgen = gz.astgen; + const tree = astgen.tree; + const node_datas = tree.nodes.items(.data); + + const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs); + const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); + // Saturating shift-left allows any integer type for both the LHS and RHS. + const rhs = try expr(gz, scope, .none, node_datas[infix_node].rhs); + + const result = try gz.addPlNode(.shl_sat, infix_node, Zir.Inst.Bin{ + .lhs = lhs, + .rhs = rhs, + }); + _ = try gz.addBin(.store, lhs_ptr, result); +} + fn boolNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerError!Zir.Inst.Ref { const astgen = gz.astgen; const tree = astgen.tree; @@ -7556,11 +7534,6 @@ fn builtinCall( return rvalue(gz, rl, result, node); }, - .add_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .add_with_saturation), - .sub_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .sub_with_saturation), - .mul_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .mul_with_saturation), - .shl_with_saturation => return saturatingArithmetic(gz, scope, rl, node, params, .shl_with_saturation), - .atomic_load => { const int_type = try typeExpr(gz, scope, params[0]); // TODO allow this pointer type to be volatile @@ -7955,24 +7928,6 @@ fn overflowArithmetic( return rvalue(gz, rl, result, node); } -fn saturatingArithmetic( - gz: *GenZir, - scope: *Scope, - rl: ResultLoc, - node: Ast.Node.Index, - params: []const Ast.Node.Index, - tag: Zir.Inst.Extended, -) InnerError!Zir.Inst.Ref { - const lhs = try expr(gz, scope, .none, params[0]); - const rhs = try expr(gz, scope, .none, params[1]); - const result = try gz.addExtendedPayload(tag, Zir.Inst.SaturatingArithmetic{ - .node = gz.nodeIndexToRelative(node), - .lhs = lhs, - .rhs = rhs, - }); - return rvalue(gz, rl, result, node); -} - fn callExpr( gz: *GenZir, scope: *Scope, @@ -8198,9 +8153,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool .assign, .assign_bit_and, .assign_bit_or, - .assign_bit_shift_left, - .assign_bit_shift_left_sat, - .assign_bit_shift_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .assign_bit_xor, .assign_div, .assign_sub, @@ -8216,9 +8171,9 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool .bang_equal, .bit_and, .bit_or, - .bit_shift_left, - .bit_shift_left_sat, - .bit_shift_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bool_and, .bool_or, @@ -8439,9 +8394,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never .assign, .assign_bit_and, .assign_bit_or, - .assign_bit_shift_left, - .assign_bit_shift_left_sat, - .assign_bit_shift_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .assign_bit_xor, .assign_div, .assign_sub, @@ -8457,9 +8412,9 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) enum { never .bang_equal, .bit_and, .bit_or, - .bit_shift_left, - .bit_shift_left_sat, - .bit_shift_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bool_and, .bool_or, @@ -8619,9 +8574,9 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool { .assign, .assign_bit_and, .assign_bit_or, - .assign_bit_shift_left, - .assign_bit_shift_left_sat, - .assign_bit_shift_right, + .assign_shl, + .assign_shl_sat, + .assign_shr, .assign_bit_xor, .assign_div, .assign_sub, @@ -8637,9 +8592,9 @@ fn nodeImpliesRuntimeBits(tree: *const Ast, start_node: Ast.Node.Index) bool { .bang_equal, .bit_and, .bit_or, - .bit_shift_left, - .bit_shift_left_sat, - .bit_shift_right, + .shl, + .shl_sat, + .shr, .bit_xor, .bool_and, .bool_or, diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig index e415d27a3a..8f23ec86d7 100644 --- a/src/BuiltinFn.zig +++ b/src/BuiltinFn.zig @@ -2,7 +2,6 @@ const std = @import("std"); pub const Tag = enum { add_with_overflow, - add_with_saturation, align_cast, align_of, as, @@ -66,7 +65,6 @@ pub const Tag = enum { wasm_memory_grow, mod, mul_with_overflow, - mul_with_saturation, panic, pop_count, ptr_cast, @@ -81,12 +79,10 @@ pub const Tag = enum { set_runtime_safety, shl_exact, shl_with_overflow, - shl_with_saturation, shr_exact, shuffle, size_of, splat, - sub_with_saturation, reduce, src, sqrt, @@ -531,34 +527,6 @@ pub const list = list: { .param_count = 2, }, }, - .{ - "@addWithSaturation", - .{ - .tag = .add_with_saturation, - .param_count = 2, - }, - }, - .{ - "@subWithSaturation", - .{ - .tag = .sub_with_saturation, - .param_count = 2, - }, - }, - .{ - "@mulWithSaturation", - .{ - .tag = .mul_with_saturation, - .param_count = 2, - }, - }, - .{ - "@shlWithSaturation", - .{ - .tag = .shl_with_saturation, - .param_count = 2, - }, - }, .{ "@memcpy", .{ diff --git a/src/Liveness.zig b/src/Liveness.zig index c34153b76f..93f28ad7b2 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -226,13 +226,13 @@ fn analyzeInst( switch (inst_tags[inst]) { .add, .addwrap, - .addsat, + .add_sat, .sub, .subwrap, - .subsat, + .sub_sat, .mul, .mulwrap, - .mulsat, + .mul_sat, .div, .rem, .mod, @@ -255,6 +255,7 @@ fn analyzeInst( .ptr_elem_val, .ptr_ptr_elem_val, .shl, + .shl_exact, .shl_sat, .shr, .atomic_store_unordered, diff --git a/src/Sema.zig b/src/Sema.zig index be10b6d663..f106d7ea9e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -246,7 +246,6 @@ pub fn analyzeBody( .ptr_type_simple => try sema.zirPtrTypeSimple(block, inst), .ref => try sema.zirRef(block, inst), .ret_err_value_code => try sema.zirRetErrValueCode(block, inst), - .shl => try sema.zirShl(block, inst), .shr => try sema.zirShr(block, inst), .slice_end => try sema.zirSliceEnd(block, inst), .slice_sentinel => try sema.zirSliceSentinel(block, inst), @@ -319,7 +318,6 @@ pub fn analyzeBody( .div_exact => try sema.zirDivExact(block, inst), .div_floor => try sema.zirDivFloor(block, inst), .div_trunc => try sema.zirDivTrunc(block, inst), - .shl_exact => try sema.zirShlExact(block, inst), .shr_exact => try sema.zirShrExact(block, inst), .bit_offset_of => try sema.zirBitOffsetOf(block, inst), .offset_of => try sema.zirOffsetOf(block, inst), @@ -363,14 +361,21 @@ pub fn analyzeBody( .add => try sema.zirArithmetic(block, inst, .add), .addwrap => try sema.zirArithmetic(block, inst, .addwrap), + .add_sat => try sema.zirArithmetic(block, inst, .add_sat), .div => try sema.zirArithmetic(block, inst, .div), .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem), .mod => try sema.zirArithmetic(block, inst, .mod), .rem => try sema.zirArithmetic(block, inst, .rem), .mul => try sema.zirArithmetic(block, inst, .mul), .mulwrap => try sema.zirArithmetic(block, inst, .mulwrap), + .mul_sat => try sema.zirArithmetic(block, inst, .mul_sat), .sub => try sema.zirArithmetic(block, inst, .sub), .subwrap => try sema.zirArithmetic(block, inst, .subwrap), + .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat), + + .shl => try sema.zirShl(block, inst, .shl), + .shl_exact => try sema.zirShl(block, inst, .shl_exact), + .shl_sat => try sema.zirShl(block, inst, .shl_sat), // Instructions that we know to *always* be noreturn based solely on their tag. // These functions match the return type of analyzeBody so that we can @@ -694,11 +699,6 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr .c_define => return sema.zirCDefine( block, extended), .wasm_memory_size => return sema.zirWasmMemorySize( block, extended), .wasm_memory_grow => return sema.zirWasmMemoryGrow( block, extended), - .add_with_saturation, - .sub_with_saturation, - .mul_with_saturation, - .shl_with_saturation, - => return sema.zirSatArithmetic( block, extended), // zig fmt: on } } @@ -5875,7 +5875,12 @@ fn zirRetErrValueCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co return sema.mod.fail(&block.base, sema.src, "TODO implement zirRetErrValueCode", .{}); } -fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirShl( + sema: *Sema, + block: *Scope.Block, + inst: Zir.Inst.Index, + air_tag: Air.Inst.Tag, +) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -5886,6 +5891,8 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A const lhs = sema.resolveInst(extra.lhs); const rhs = sema.resolveInst(extra.rhs); + // TODO coerce rhs if air_tag is not shl_sat + const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); @@ -5901,6 +5908,12 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A return sema.addConstant(lhs_ty, lhs_val); } const val = try lhs_val.shl(rhs_val, sema.arena); + switch (air_tag) { + .shl_exact => return sema.mod.fail(&block.base, lhs_src, "TODO implement Sema for comptime shl_exact", .{}), + .shl_sat => return sema.mod.fail(&block.base, lhs_src, "TODO implement Sema for comptime shl_sat", .{}), + .shl => {}, + else => unreachable, + } return sema.addConstant(lhs_ty, val); } else rs: { if (maybe_rhs_val) |rhs_val| { @@ -5909,8 +5922,10 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A break :rs lhs_src; }; + // TODO: insert runtime safety check for shl_exact + try sema.requireRuntimeBlock(block, runtime_src); - return block.addBinOp(.shl, lhs, rhs); + return block.addBinOp(air_tag, lhs, rhs); } fn zirShr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6201,105 +6216,6 @@ fn zirOverflowArithmetic( return sema.mod.fail(&block.base, src, "TODO implement Sema.zirOverflowArithmetic", .{}); } -fn zirSatArithmetic( - sema: *Sema, - block: *Scope.Block, - extended: Zir.Inst.Extended.InstData, -) CompileError!Air.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); - - const extra = sema.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data; - sema.src = .{ .node_offset_bin_op = extra.node }; - const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = extra.node }; - const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = extra.node }; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); - - return sema.analyzeSatArithmetic(block, lhs, rhs, sema.src, lhs_src, rhs_src, extended); -} - -fn analyzeSatArithmetic( - sema: *Sema, - block: *Scope.Block, - lhs: Air.Inst.Ref, - rhs: Air.Inst.Ref, - src: LazySrcLoc, - lhs_src: LazySrcLoc, - rhs_src: LazySrcLoc, - extended: Zir.Inst.Extended.InstData, -) CompileError!Air.Inst.Ref { - const lhs_ty = sema.typeOf(lhs); - const rhs_ty = sema.typeOf(rhs); - const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); - const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); - if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) { - if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) { - return sema.mod.fail(&block.base, src, "vector length mismatch: {d} and {d}", .{ - lhs_ty.arrayLen(), rhs_ty.arrayLen(), - }); - } - return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{}); - } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) { - return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{ - lhs_ty, rhs_ty, - }); - } - - if (lhs_zig_ty_tag == .Pointer or rhs_zig_ty_tag == .Pointer) - return sema.mod.fail(&block.base, src, "TODO implement support for pointers in zirSatArithmetic", .{}); - - const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; - const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } }); - const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); - const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); - - const scalar_type = if (resolved_type.zigTypeTag() == .Vector) - resolved_type.elemType() - else - resolved_type; - - const scalar_tag = scalar_type.zigTypeTag(); - - const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; - - if (!is_int) - return sema.mod.fail(&block.base, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ - @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag), - }); - - if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(resolved_type); - } - // incase rhs is 0, simply return lhs without doing any calculations - if (rhs_val.compareWithZero(.eq)) { - switch (extended.opcode) { - .add_with_saturation, .sub_with_saturation => return sema.addConstant(scalar_type, lhs_val), - else => {}, - } - } - - return sema.mod.fail(&block.base, src, "TODO implement comptime saturating arithmetic for operand '{s}'", .{@tagName(extended.opcode)}); - } else { - try sema.requireRuntimeBlock(block, rhs_src); - } - } else { - try sema.requireRuntimeBlock(block, lhs_src); - } - - const air_tag: Air.Inst.Tag = switch (extended.opcode) { - .add_with_saturation => .addsat, - .sub_with_saturation => .subsat, - .mul_with_saturation => .mulsat, - .shl_with_saturation => .shl_sat, - else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for extended opcode '{s}'", .{@tagName(extended.opcode)}), - }; - - return block.addBinOp(air_tag, casted_lhs, casted_rhs); -} - fn analyzeArithmetic( sema: *Sema, block: *Scope.Block, @@ -6441,8 +6357,7 @@ fn analyzeArithmetic( }, .addwrap => { // Integers only; floats are checked above. - // If either of the operands are zero, then the other operand is - // returned, even if it is undefined. + // If either of the operands are zero, the other operand is returned. // If either of the operands are undefined, the result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { @@ -6464,6 +6379,30 @@ fn analyzeArithmetic( } else break :rs .{ .src = lhs_src, .air_tag = .addwrap }; } else break :rs .{ .src = rhs_src, .air_tag = .addwrap }; }, + .add_sat => { + // For both integers and floats: + // If either of the operands are zero, then the other operand is returned. + // If either of the operands are undefined, the result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { + return casted_rhs; + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.numberAddSat(rhs_val, scalar_type, sema.arena, target), + ); + } else break :rs .{ .src = lhs_src, .air_tag = .add_sat }; + } else break :rs .{ .src = rhs_src, .air_tag = .add_sat }; + }, .sub => { // For integers: // If the rhs is zero, then the other operand is @@ -6531,6 +6470,30 @@ fn analyzeArithmetic( } else break :rs .{ .src = rhs_src, .air_tag = .subwrap }; } else break :rs .{ .src = lhs_src, .air_tag = .subwrap }; }, + .sub_sat => { + // For both integers and floats: + // If the RHS is zero, result is LHS. + // If either of the operands are undefined, result is undefined. + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (maybe_rhs_val) |rhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.numberSubSat(rhs_val, scalar_type, sema.arena, target), + ); + } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; + } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat }; + }, .div => { // For integers: // If the lhs is zero, then zero is returned regardless of rhs. @@ -6649,10 +6612,9 @@ fn analyzeArithmetic( }, .mulwrap => { // Integers only; floats are handled above. - // If either of the operands are zero, the result is zero. - // If either of the operands are one, the result is the other - // operand, even if it is undefined. - // If either of the operands are undefined, the result is undefined. + // If either of the operands are zero, result is zero. + // If either of the operands are one, result is the other operand. + // If either of the operands are undefined, result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { if (lhs_val.compareWithZero(.eq)) { @@ -6684,6 +6646,42 @@ fn analyzeArithmetic( } else break :rs .{ .src = lhs_src, .air_tag = .mulwrap }; } else break :rs .{ .src = rhs_src, .air_tag = .mulwrap }; }, + .mul_sat => { + // For both integers and floats: + // If either of the operands are zero, result is zero. + // If either of the operands are one, result is the other operand. + // If either of the operands are undefined, result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (lhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (lhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_rhs; + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (rhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_lhs; + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + return sema.addConstant( + scalar_type, + try lhs_val.numberMulSat(rhs_val, scalar_type, sema.arena, target), + ); + } else break :rs .{ .src = lhs_src, .air_tag = .mul_sat }; + } else break :rs .{ .src = rhs_src, .air_tag = .mul_sat }; + }, .mod_rem => { // For integers: // Either operand being undef is a compile error because there exists @@ -7933,7 +7931,7 @@ fn analyzeRet( fn floatOpAllowed(tag: Zir.Inst.Tag) bool { // extend this swich as additional operators are implemented return switch (tag) { - .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true, + .add, .add_sat, .sub, .sub_sat, .mul, .mul_sat, .div, .mod, .rem, .mod_rem => true, else => false, }; } @@ -8600,12 +8598,6 @@ fn zirDivTrunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.mod.fail(&block.base, src, "TODO: Sema.zirDivTrunc", .{}); } -fn zirShlExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirShlExact", .{}); -} - fn zirShrExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); diff --git a/src/Zir.zig b/src/Zir.zig index 7c171e736d..1da53a526e 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -126,6 +126,64 @@ pub const Inst = struct { /// Twos complement wrapping integer addition. /// Uses the `pl_node` union field. Payload is `Bin`. addwrap, + /// Saturating addition. + /// Uses the `pl_node` union field. Payload is `Bin`. + add_sat, + /// Arithmetic subtraction. Asserts no integer overflow. + /// Uses the `pl_node` union field. Payload is `Bin`. + sub, + /// Twos complement wrapping integer subtraction. + /// Uses the `pl_node` union field. Payload is `Bin`. + subwrap, + /// Saturating subtraction. + /// Uses the `pl_node` union field. Payload is `Bin`. + sub_sat, + /// Arithmetic multiplication. Asserts no integer overflow. + /// Uses the `pl_node` union field. Payload is `Bin`. + mul, + /// Twos complement wrapping integer multiplication. + /// Uses the `pl_node` union field. Payload is `Bin`. + mulwrap, + /// Saturating multiplication. + /// Uses the `pl_node` union field. Payload is `Bin`. + mul_sat, + /// Implements the `@divExact` builtin. + /// Uses the `pl_node` union field with payload `Bin`. + div_exact, + /// Implements the `@divFloor` builtin. + /// Uses the `pl_node` union field with payload `Bin`. + div_floor, + /// Implements the `@divTrunc` builtin. + /// Uses the `pl_node` union field with payload `Bin`. + div_trunc, + /// Implements the `@mod` builtin. + /// Uses the `pl_node` union field with payload `Bin`. + mod, + /// Implements the `@rem` builtin. + /// Uses the `pl_node` union field with payload `Bin`. + rem, + /// Ambiguously remainder division or modulus. If the computation would possibly have + /// a different value depending on whether the operation is remainder division or modulus, + /// a compile error is emitted. Otherwise the computation is performed. + /// Uses the `pl_node` union field. Payload is `Bin`. + mod_rem, + /// Integer shift-left. Zeroes are shifted in from the right hand side. + /// Uses the `pl_node` union field. Payload is `Bin`. + shl, + /// Implements the `@shlExact` builtin. + /// Uses the `pl_node` union field with payload `Bin`. + shl_exact, + /// Saturating shift-left. + /// Uses the `pl_node` union field. Payload is `Bin`. + shl_sat, + /// Integer shift-right. Arithmetic or logical depending on the signedness of + /// the integer type. + /// Uses the `pl_node` union field. Payload is `Bin`. + shr, + /// Implements the `@shrExact` builtin. + /// Uses the `pl_node` union field with payload `Bin`. + shr_exact, + /// Declares a parameter of the current function. Used for: /// * debug info /// * checking shadowing against declarations in the current namespace @@ -471,12 +529,6 @@ pub const Inst = struct { /// String Literal. Makes an anonymous Decl and then takes a pointer to it. /// Uses the `str` union field. str, - /// Arithmetic subtraction. Asserts no integer overflow. - /// Uses the `pl_node` union field. Payload is `Bin`. - sub, - /// Twos complement wrapping integer subtraction. - /// Uses the `pl_node` union field. Payload is `Bin`. - subwrap, /// Arithmetic negation. Asserts no integer overflow. /// Same as sub with a lhs of 0, split into a separate instruction to save memory. /// Uses `un_node`. @@ -802,46 +854,6 @@ pub const Inst = struct { /// Implements the `@bitReverse` builtin. Uses the `un_node` union field. bit_reverse, - /// Implements the `@divExact` builtin. - /// Uses the `pl_node` union field with payload `Bin`. - div_exact, - /// Implements the `@divFloor` builtin. - /// Uses the `pl_node` union field with payload `Bin`. - div_floor, - /// Implements the `@divTrunc` builtin. - /// Uses the `pl_node` union field with payload `Bin`. - div_trunc, - /// Implements the `@mod` builtin. - /// Uses the `pl_node` union field with payload `Bin`. - mod, - /// Implements the `@rem` builtin. - /// Uses the `pl_node` union field with payload `Bin`. - rem, - /// Ambiguously remainder division or modulus. If the computation would possibly have - /// a different value depending on whether the operation is remainder division or modulus, - /// a compile error is emitted. Otherwise the computation is performed. - /// Uses the `pl_node` union field. Payload is `Bin`. - mod_rem, - /// Arithmetic multiplication. Asserts no integer overflow. - /// Uses the `pl_node` union field. Payload is `Bin`. - mul, - /// Twos complement wrapping integer multiplication. - /// Uses the `pl_node` union field. Payload is `Bin`. - mulwrap, - - /// Integer shift-left. Zeroes are shifted in from the right hand side. - /// Uses the `pl_node` union field. Payload is `Bin`. - shl, - /// Implements the `@shlExact` builtin. - /// Uses the `pl_node` union field with payload `Bin`. - shl_exact, - /// Integer shift-right. Arithmetic or logical depending on the signedness of the integer type. - /// Uses the `pl_node` union field. Payload is `Bin`. - shr, - /// Implements the `@shrExact` builtin. - /// Uses the `pl_node` union field with payload `Bin`. - shr_exact, - /// Implements the `@bitOffsetOf` builtin. /// Uses the `pl_node` union field with payload `Bin`. bit_offset_of, @@ -961,6 +973,7 @@ pub const Inst = struct { .param_anytype_comptime, .add, .addwrap, + .add_sat, .alloc, .alloc_mut, .alloc_comptime, @@ -1035,8 +1048,10 @@ pub const Inst = struct { .mod_rem, .mul, .mulwrap, + .mul_sat, .ref, .shl, + .shl_sat, .shr, .store, .store_node, @@ -1045,6 +1060,7 @@ pub const Inst = struct { .str, .sub, .subwrap, + .sub_sat, .negate, .negate_wrap, .typeof, @@ -1218,6 +1234,14 @@ pub const Inst = struct { break :list std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{ .add = .pl_node, .addwrap = .pl_node, + .add_sat = .pl_node, + .sub = .pl_node, + .subwrap = .pl_node, + .sub_sat = .pl_node, + .mul = .pl_node, + .mulwrap = .pl_node, + .mul_sat = .pl_node, + .param = .pl_tok, .param_comptime = .pl_tok, .param_anytype = .str_tok, @@ -1297,8 +1321,6 @@ pub const Inst = struct { .repeat_inline = .node, .merge_error_sets = .pl_node, .mod_rem = .pl_node, - .mul = .pl_node, - .mulwrap = .pl_node, .ref = .un_tok, .ret_node = .un_node, .ret_load = .un_node, @@ -1315,8 +1337,6 @@ pub const Inst = struct { .store_to_block_ptr = .bin, .store_to_inferred_ptr = .bin, .str = .str, - .sub = .pl_node, - .subwrap = .pl_node, .negate = .un_node, .negate_wrap = .un_node, .typeof = .un_node, @@ -1437,6 +1457,7 @@ pub const Inst = struct { .shl = .pl_node, .shl_exact = .pl_node, + .shl_sat = .pl_node, .shr = .pl_node, .shr_exact = .pl_node, @@ -1593,22 +1614,6 @@ pub const Inst = struct { wasm_memory_size, /// `operand` is payload index to `BinNode`. wasm_memory_grow, - /// Implements the `@addWithSaturation` builtin. - /// `operand` is payload index to `SaturatingArithmetic`. - /// `small` is unused. - add_with_saturation, - /// Implements the `@subWithSaturation` builtin. - /// `operand` is payload index to `SaturatingArithmetic`. - /// `small` is unused. - sub_with_saturation, - /// Implements the `@mulWithSaturation` builtin. - /// `operand` is payload index to `SaturatingArithmetic`. - /// `small` is unused. - mul_with_saturation, - /// Implements the `@shlWithSaturation` builtin. - /// `operand` is payload index to `SaturatingArithmetic`. - /// `small` is unused. - shl_with_saturation, pub const InstData = struct { opcode: Extended, @@ -2788,12 +2793,6 @@ pub const Inst = struct { ptr: Ref, }; - pub const SaturatingArithmetic = struct { - node: i32, - lhs: Ref, - rhs: Ref, - }; - pub const Cmpxchg = struct { ptr: Ref, expected_value: Ref, diff --git a/src/codegen.zig b/src/codegen.zig index a1f812388f..79105dc4a7 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -824,18 +824,20 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (air_tags[inst]) { // zig fmt: off - .add, .ptr_add => try self.airAdd(inst), - .addwrap => try self.airAddWrap(inst), - .addsat => try self.airArithmeticOpSat(inst, "addsat"), - .sub, .ptr_sub => try self.airSub(inst), - .subwrap => try self.airSubWrap(inst), - .subsat => try self.airArithmeticOpSat(inst, "subsat"), - .mul => try self.airMul(inst), - .mulwrap => try self.airMulWrap(inst), - .mulsat => try self.airArithmeticOpSat(inst, "mulsat"), - .div => try self.airDiv(inst), - .rem => try self.airRem(inst), - .mod => try self.airMod(inst), + .add, .ptr_add => try self.airAdd(inst), + .addwrap => try self.airAddWrap(inst), + .add_sat => try self.airAddSat(inst), + .sub, .ptr_sub => try self.airSub(inst), + .subwrap => try self.airSubWrap(inst), + .sub_sat => try self.airSubSat(inst), + .mul => try self.airMul(inst), + .mulwrap => try self.airMulWrap(inst), + .mul_sat => try self.airMulSat(inst), + .div => try self.airDiv(inst), + .rem => try self.airRem(inst), + .mod => try self.airMod(inst), + .shl, .shl_exact => try self.airShl(inst), + .shl_sat => try self.airShlSat(inst), .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), @@ -850,8 +852,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .bit_or => try self.airBitOr(inst), .xor => try self.airXor(inst), .shr => try self.airShr(inst), - .shl => try self.airShl(inst), - .shl_sat => try self.airArithmeticOpSat(inst, "shl_sat"), .alloc => try self.airAlloc(inst), .arg => try self.airArg(inst), @@ -1306,6 +1306,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airAddSat(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement add_sat for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn airSub(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { @@ -1324,10 +1332,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } - fn airArithmeticOpSat(self: *Self, inst: Air.Inst.Index, comptime name: []const u8) !void { + fn airSubSat(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { - else => return self.fail("TODO implement " ++ name ++ " for {}", .{self.target.cpu.arch}), + else => return self.fail("TODO implement sub_sat for {}", .{self.target.cpu.arch}), }; return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } @@ -1350,6 +1358,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement mul_sat for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn airDiv(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { @@ -1412,6 +1428,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airShlSat(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn airShr(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1afa81b70f..95ce95f2e5 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -883,25 +883,27 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO // TODO use a different strategy for add that communicates to the optimizer // that wrapping is UB. - .add, .ptr_add => try airBinOp( f, inst, " + "), - .addwrap => try airWrapOp(f, inst, " + ", "addw_"), - .addsat => return f.fail("TODO: C backend: implement codegen for addsat", .{}), + .add, .ptr_add => try airBinOp (f, inst, " + "), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. - .sub, .ptr_sub => try airBinOp( f, inst, " - "), - .subwrap => try airWrapOp(f, inst, " - ", "subw_"), - .subsat => return f.fail("TODO: C backend: implement codegen for subsat", .{}), + .sub, .ptr_sub => try airBinOp (f, inst, " - "), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. - .mul => try airBinOp( f, inst, " * "), - .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"), - .mulsat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}), + .mul => try airBinOp (f, inst, " * "), // TODO use a different strategy for div that communicates to the optimizer // that wrapping is UB. .div => try airBinOp( f, inst, " / "), .rem => try airBinOp( f, inst, " % "), - // TODO implement modulus division - .mod => try airBinOp( f, inst, " mod "), + .mod => try airBinOp( f, inst, " mod "), // TODO implement modulus division + + .addwrap => try airWrapOp(f, inst, " + ", "addw_"), + .subwrap => try airWrapOp(f, inst, " - ", "subw_"), + .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"), + + .add_sat => try airSatOp(f, inst, "adds_"), + .sub_sat => try airSatOp(f, inst, "subs_"), + .mul_sat => try airSatOp(f, inst, "muls_"), + .shl_sat => try airSatOp(f, inst, "shls_"), .cmp_eq => try airBinOp(f, inst, " == "), .cmp_gt => try airBinOp(f, inst, " > "), @@ -911,18 +913,14 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .cmp_neq => try airBinOp(f, inst, " != "), // bool_and and bool_or are non-short-circuit operations - .bool_and => try airBinOp(f, inst, " & "), - .bool_or => try airBinOp(f, inst, " | "), - .bit_and => try airBinOp(f, inst, " & "), - .bit_or => try airBinOp(f, inst, " | "), - .xor => try airBinOp(f, inst, " ^ "), - - .shr => try airBinOp(f, inst, " >> "), - .shl => try airBinOp(f, inst, " << "), - .shl_sat => return f.fail("TODO: C backend: implement codegen for mulsat", .{}), - - - .not => try airNot( f, inst), + .bool_and => try airBinOp(f, inst, " & "), + .bool_or => try airBinOp(f, inst, " | "), + .bit_and => try airBinOp(f, inst, " & "), + .bit_or => try airBinOp(f, inst, " | "), + .xor => try airBinOp(f, inst, " ^ "), + .shr => try airBinOp(f, inst, " >> "), + .shl, .shl_exact => try airBinOp(f, inst, " << "), + .not => try airNot (f, inst), .optional_payload => try airOptionalPayload(f, inst), .optional_payload_ptr => try airOptionalPayload(f, inst), @@ -1314,27 +1312,23 @@ fn airWrapOp( return ret; } -fn airSatOp( - o: *Object, - inst: Air.Inst.Index, - fn_op: [*:0]const u8, -) !CValue { - if (o.liveness.isUnused(inst)) +fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; - const bin_op = o.air.instructions.items(.data)[inst].bin_op; - const inst_ty = o.air.typeOfIndex(inst); - const int_info = inst_ty.intInfo(o.dg.module.getTarget()); + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const inst_ty = f.air.typeOfIndex(inst); + const int_info = inst_ty.intInfo(f.object.dg.module.getTarget()); const bits = int_info.bits; switch (bits) { 8, 16, 32, 64, 128 => {}, - else => return o.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}), + else => return f.object.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}), } // if it's an unsigned int with non-arbitrary bit size then we can just add if (bits > 64) { - return o.dg.fail("TODO: C backend: airSatOp for large integers", .{}); + return f.object.dg.fail("TODO: C backend: airSatOp for large integers", .{}); } var min_buf: [80]u8 = undefined; @@ -1382,11 +1376,11 @@ fn airSatOp( }, }; - const lhs = try o.resolveInst(bin_op.lhs); - const rhs = try o.resolveInst(bin_op.rhs); - const w = o.writer(); + const lhs = try f.resolveInst(bin_op.lhs); + const rhs = try f.resolveInst(bin_op.rhs); + const w = f.object.writer(); - const ret = try o.allocLocal(inst_ty, .Mut); + const ret = try f.allocLocal(inst_ty, .Mut); try w.print(" = zig_{s}", .{fn_op}); switch (inst_ty.tag()) { @@ -1412,16 +1406,16 @@ fn airSatOp( } try w.writeByte('('); - try o.writeCValue(w, lhs); + try f.writeCValue(w, lhs); try w.writeAll(", "); - try o.writeCValue(w, rhs); + try f.writeCValue(w, rhs); if (int_info.signedness == .signed) { try w.print(", {s}", .{min}); } try w.print(", {s});", .{max}); - try o.indent_writer.insertNewline(); + try f.object.indent_writer.insertNewline(); return ret; } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index cdd19146b5..b27afa9b54 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1236,27 +1236,27 @@ pub const FuncGen = struct { for (body) |inst| { const opt_value: ?*const llvm.Value = switch (air_tags[inst]) { // zig fmt: off - .add => try self.airAdd(inst, .standard), - .addwrap => try self.airAdd(inst, .wrapping), - .addsat => try self.airAdd(inst, .saturated), - .sub => try self.airSub(inst, .standard), - .subwrap => try self.airSub(inst, .wrapping), - .subsat => try self.airSub(inst, .saturated), - .mul => try self.airMul(inst, .standard), - .mulwrap => try self.airMul(inst, .wrapping), - .mulsat => try self.airMul(inst, .saturated), - .div => try self.airDiv(inst), - .rem => try self.airRem(inst), - .mod => try self.airMod(inst), - .ptr_add => try self.airPtrAdd(inst), - .ptr_sub => try self.airPtrSub(inst), + .add => try self.airAdd(inst), + .addwrap => try self.airAddWrap(inst), + .add_sat => try self.airAddSat(inst), + .sub => try self.airSub(inst), + .subwrap => try self.airSubWrap(inst), + .sub_sat => try self.airSubSat(inst), + .mul => try self.airMul(inst), + .mulwrap => try self.airMulWrap(inst), + .mul_sat => try self.airMulSat(inst), + .div => try self.airDiv(inst), + .rem => try self.airRem(inst), + .mod => try self.airMod(inst), + .ptr_add => try self.airPtrAdd(inst), + .ptr_sub => try self.airPtrSub(inst), + .shl => try self.airShl(inst), + .shl_sat => try self.airShlSat(inst), + .shl_exact => try self.airShlExact(inst), .bit_and, .bool_and => try self.airAnd(inst), .bit_or, .bool_or => try self.airOr(inst), .xor => try self.airXor(inst), - - .shl => try self.airShl(inst, false), - .shl_sat => try self.airShl(inst, true), .shr => try self.airShr(inst), .cmp_eq => try self.airCmp(inst, .eq), @@ -2028,10 +2028,8 @@ pub const FuncGen = struct { return self.todo("implement llvm codegen for 'airWrapErrUnionErr'", .{}); } - const ArithmeticType = enum { standard, wrapping, saturated }; - fn airAdd(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -2039,21 +2037,36 @@ pub const FuncGen = struct { const inst_ty = self.air.typeOfIndex(inst); if (inst_ty.isAnyFloat()) return self.builder.buildFAdd(lhs, rhs, ""); - if (ty == .wrapping) - return self.builder.buildAdd(lhs, rhs, "") - else if (ty == .saturated) { - if (inst_ty.isSignedInt()) - return self.builder.buildSAddSat(lhs, rhs, "") - else - return self.builder.buildUAddSat(lhs, rhs, ""); - } if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, ""); return self.builder.buildNUWAdd(lhs, rhs, ""); } - fn airSub(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + + return self.builder.buildAdd(lhs, rhs, ""); + } + + fn airAddSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const inst_ty = self.air.typeOfIndex(inst); + + if (inst_ty.isAnyFloat()) return self.todo("saturating float add", .{}); + if (inst_ty.isSignedInt()) return self.builder.buildSAddSat(lhs, rhs, ""); + + return self.builder.buildUAddSat(lhs, rhs, ""); + } + + fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -2061,21 +2074,35 @@ pub const FuncGen = struct { const inst_ty = self.air.typeOfIndex(inst); if (inst_ty.isAnyFloat()) return self.builder.buildFSub(lhs, rhs, ""); - if (ty == .wrapping) - return self.builder.buildSub(lhs, rhs, "") - else if (ty == .saturated) { - if (inst_ty.isSignedInt()) - return self.builder.buildSSubSat(lhs, rhs, "") - else - return self.builder.buildUSubSat(lhs, rhs, ""); - } if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, ""); return self.builder.buildNUWSub(lhs, rhs, ""); } - fn airMul(self: *FuncGen, inst: Air.Inst.Index, ty: ArithmeticType) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + + return self.builder.buildSub(lhs, rhs, ""); + } + + fn airSubSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const inst_ty = self.air.typeOfIndex(inst); + + if (inst_ty.isAnyFloat()) return self.todo("saturating float sub", .{}); + if (inst_ty.isSignedInt()) return self.builder.buildSSubSat(lhs, rhs, ""); + return self.builder.buildUSubSat(lhs, rhs, ""); + } + + fn airMul(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); @@ -2083,18 +2110,33 @@ pub const FuncGen = struct { const inst_ty = self.air.typeOfIndex(inst); if (inst_ty.isAnyFloat()) return self.builder.buildFMul(lhs, rhs, ""); - if (ty == .wrapping) - return self.builder.buildMul(lhs, rhs, "") - else if (ty == .saturated) { - if (inst_ty.isSignedInt()) - return self.builder.buildSMulFixSat(lhs, rhs, "") - else - return self.builder.buildUMulFixSat(lhs, rhs, ""); - } if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, ""); return self.builder.buildNUWMul(lhs, rhs, ""); } + fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + + return self.builder.buildMul(lhs, rhs, ""); + } + + fn airMulSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const inst_ty = self.air.typeOfIndex(inst); + + if (inst_ty.isAnyFloat()) return self.todo("saturating float mul", .{}); + if (inst_ty.isSignedInt()) return self.builder.buildSMulFixSat(lhs, rhs, ""); + return self.builder.buildUMulFixSat(lhs, rhs, ""); + } + fn airDiv(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -2200,9 +2242,25 @@ pub const FuncGen = struct { return self.builder.buildXor(lhs, rhs, ""); } - fn airShl(self: *FuncGen, inst: Air.Inst.Index, sat: bool) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + fn airShlExact(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const lhs_type = self.air.typeOf(bin_op.lhs); + const tg = self.dg.module.getTarget(); + const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg)) + self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "") + else + rhs; + if (lhs_type.isSignedInt()) return self.builder.buildNSWShl(lhs, casted_rhs, ""); + return self.builder.buildNUWShl(lhs, casted_rhs, ""); + } + + fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + const bin_op = self.air.instructions.items(.data)[inst].bin_op; const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); @@ -2212,15 +2270,25 @@ pub const FuncGen = struct { self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "") else rhs; - if (sat) { - return if (lhs_type.isSignedInt()) - self.builder.buildSShlSat(lhs, casted_rhs, "") - else - self.builder.buildUShlSat(lhs, casted_rhs, ""); - } return self.builder.buildShl(lhs, casted_rhs, ""); } + fn airShlSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const lhs_type = self.air.typeOf(bin_op.lhs); + const tg = self.dg.module.getTarget(); + const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg)) + self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "") + else + rhs; + if (lhs_type.isSignedInt()) return self.builder.buildSShlSat(lhs, casted_rhs, ""); + return self.builder.buildUShlSat(lhs, casted_rhs, ""); + } + fn airShr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 178c381235..4fac6656c8 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -469,6 +469,12 @@ pub const Builder = opaque { pub const buildShl = LLVMBuildShl; extern fn LLVMBuildShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildNUWShl = ZigLLVMBuildNUWShl; + extern fn ZigLLVMBuildNUWShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + + pub const buildNSWShl = ZigLLVMBuildNSWShl; + extern fn ZigLLVMBuildNSWShl(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; + pub const buildSShlSat = ZigLLVMBuildSShlSat; extern fn ZigLLVMBuildSShlSat(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value; diff --git a/src/link/C/zig.h b/src/link/C/zig.h index 5c9d750729..72868e4400 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -356,9 +356,6 @@ static inline long long zig_subw_longlong(long long lhs, long long rhs, long lon return (long long)(((unsigned long long)lhs) - ((unsigned long long)rhs)); } -/* - * Saturating aritmetic operations: add, sub, mul, shl - */ #define zig_add_sat_u(ZT, T) static inline T zig_adds_##ZT(T x, T y, T max) { \ return (x > max - y) ? max : x + y; \ } @@ -449,7 +446,7 @@ zig_shl_sat_u(u32, uint32_t, 32) zig_shl_sat_s(i32, int32_t, 31) zig_shl_sat_u(u64, uint64_t, 64) zig_shl_sat_s(i64, int64_t, 63) -zig_shl_sat_s(isize, intptr_t, 63) -zig_shl_sat_s(short, short, 15) -zig_shl_sat_s(int, int, 31) -zig_shl_sat_s(long, long, 63) +zig_shl_sat_s(isize, intptr_t, ((sizeof(intptr_t)) * CHAR_BIT - 1)) +zig_shl_sat_s(short, short, ((sizeof(short )) * CHAR_BIT - 1)) +zig_shl_sat_s(int, int, ((sizeof(int )) * CHAR_BIT - 1)) +zig_shl_sat_s(long, long, ((sizeof(long )) * CHAR_BIT - 1)) diff --git a/src/print_air.zig b/src/print_air.zig index 7d178b52f3..885c1b62bd 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -104,13 +104,13 @@ const Writer = struct { .add, .addwrap, - .addsat, + .add_sat, .sub, .subwrap, - .subsat, + .sub_sat, .mul, .mulwrap, - .mulsat, + .mul_sat, .div, .rem, .mod, @@ -133,6 +133,7 @@ const Writer = struct { .ptr_elem_val, .ptr_ptr_elem_val, .shl, + .shl_exact, .shl_sat, .shr, .set_union_tag, diff --git a/src/print_zir.zig b/src/print_zir.zig index 3834a694e9..5ffd6619af 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -229,12 +229,15 @@ const Writer = struct { .add, .addwrap, + .add_sat, .array_cat, .array_mul, .mul, .mulwrap, + .mul_sat, .sub, .subwrap, + .sub_sat, .cmp_lt, .cmp_lte, .cmp_eq, @@ -247,6 +250,7 @@ const Writer = struct { .mod_rem, .shl, .shl_exact, + .shl_sat, .shr, .shr_exact, .xor, @@ -400,12 +404,6 @@ const Writer = struct { .shl_with_overflow, => try self.writeOverflowArithmetic(stream, extended), - .add_with_saturation, - .sub_with_saturation, - .mul_with_saturation, - .shl_with_saturation, - => try self.writeSaturatingArithmetic(stream, extended), - .struct_decl => try self.writeStructDecl(stream, extended), .union_decl => try self.writeUnionDecl(stream, extended), .enum_decl => try self.writeEnumDecl(stream, extended), @@ -854,18 +852,6 @@ const Writer = struct { try self.writeSrc(stream, src); } - fn writeSaturatingArithmetic(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { - const extra = self.code.extraData(Zir.Inst.SaturatingArithmetic, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; - - try self.writeInstRef(stream, extra.lhs); - try stream.writeAll(", "); - try self.writeInstRef(stream, extra.rhs); - try stream.writeAll(", "); - try stream.writeAll(") "); - try self.writeSrc(stream, src); - } - fn writePlNodeCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.Call, inst_data.payload_index); diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index e31a7015b0..5b58766df9 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -1818,10 +1818,6 @@ enum BuiltinFnId { BuiltinFnIdReduce, BuiltinFnIdMaximum, BuiltinFnIdMinimum, - BuiltinFnIdSatAdd, - BuiltinFnIdSatSub, - BuiltinFnIdSatMul, - BuiltinFnIdSatShl, }; struct BuiltinFnEntry { diff --git a/src/stage1/astgen.cpp b/src/stage1/astgen.cpp index 14808dd0a2..8fbd02c688 100644 --- a/src/stage1/astgen.cpp +++ b/src/stage1/astgen.cpp @@ -4720,66 +4720,6 @@ static Stage1ZirInst *astgen_builtin_fn_call(Stage1AstGen *ag, Scope *scope, Ast Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMaximum, arg0_value, arg1_value, true); return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); } - case BuiltinFnIdSatAdd: - { - AstNode *arg0_node = node->data.fn_call_expr.params.at(0); - Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope); - if (arg0_value == ag->codegen->invalid_inst_src) - return arg0_value; - - AstNode *arg1_node = node->data.fn_call_expr.params.at(1); - Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope); - if (arg1_value == ag->codegen->invalid_inst_src) - return arg1_value; - - Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpAddSat, arg0_value, arg1_value, true); - return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); - } - case BuiltinFnIdSatSub: - { - AstNode *arg0_node = node->data.fn_call_expr.params.at(0); - Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope); - if (arg0_value == ag->codegen->invalid_inst_src) - return arg0_value; - - AstNode *arg1_node = node->data.fn_call_expr.params.at(1); - Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope); - if (arg1_value == ag->codegen->invalid_inst_src) - return arg1_value; - - Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpSubSat, arg0_value, arg1_value, true); - return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); - } - case BuiltinFnIdSatMul: - { - AstNode *arg0_node = node->data.fn_call_expr.params.at(0); - Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope); - if (arg0_value == ag->codegen->invalid_inst_src) - return arg0_value; - - AstNode *arg1_node = node->data.fn_call_expr.params.at(1); - Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope); - if (arg1_value == ag->codegen->invalid_inst_src) - return arg1_value; - - Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpMultSat, arg0_value, arg1_value, true); - return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); - } - case BuiltinFnIdSatShl: - { - AstNode *arg0_node = node->data.fn_call_expr.params.at(0); - Stage1ZirInst *arg0_value = astgen_node(ag, arg0_node, scope); - if (arg0_value == ag->codegen->invalid_inst_src) - return arg0_value; - - AstNode *arg1_node = node->data.fn_call_expr.params.at(1); - Stage1ZirInst *arg1_value = astgen_node(ag, arg1_node, scope); - if (arg1_value == ag->codegen->invalid_inst_src) - return arg1_value; - - Stage1ZirInst *bin_op = ir_build_bin_op(ag, scope, node, IrBinOpShlSat, arg0_value, arg1_value, true); - return ir_lval_wrap(ag, scope, bin_op, lval, result_loc); - } case BuiltinFnIdMemcpy: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index eade843354..a0f130b79e 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -9134,10 +9134,6 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdReduce, "reduce", 2); create_builtin_fn(g, BuiltinFnIdMaximum, "maximum", 2); create_builtin_fn(g, BuiltinFnIdMinimum, "minimum", 2); - create_builtin_fn(g, BuiltinFnIdSatAdd, "addWithSaturation", 2); - create_builtin_fn(g, BuiltinFnIdSatSub, "subWithSaturation", 2); - create_builtin_fn(g, BuiltinFnIdSatMul, "mulWithSaturation", 2); - create_builtin_fn(g, BuiltinFnIdSatShl, "shlWithSaturation", 2); } static const char *bool_to_str(bool b) { diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index d0fe6d1b31..dbd9367d1a 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -1462,10 +1462,10 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { .mul_wrap_assign => return renderBinOp(c, node, .assign_mul_wrap, .asterisk_percent_equal, "*%="), .div => return renderBinOpGrouped(c, node, .div, .slash, "/"), .div_assign => return renderBinOp(c, node, .assign_div, .slash_equal, "/="), - .shl => return renderBinOpGrouped(c, node, .bit_shift_left, .angle_bracket_angle_bracket_left, "<<"), - .shl_assign => return renderBinOp(c, node, .assign_bit_shift_left, .angle_bracket_angle_bracket_left_equal, "<<="), - .shr => return renderBinOpGrouped(c, node, .bit_shift_right, .angle_bracket_angle_bracket_right, ">>"), - .shr_assign => return renderBinOp(c, node, .assign_bit_shift_right, .angle_bracket_angle_bracket_right_equal, ">>="), + .shl => return renderBinOpGrouped(c, node, .shl, .angle_bracket_angle_bracket_left, "<<"), + .shl_assign => return renderBinOp(c, node, .assign_shl, .angle_bracket_angle_bracket_left_equal, "<<="), + .shr => return renderBinOpGrouped(c, node, .shr, .angle_bracket_angle_bracket_right, ">>"), + .shr_assign => return renderBinOp(c, node, .assign_shr, .angle_bracket_angle_bracket_right_equal, ">>="), .mod => return renderBinOpGrouped(c, node, .mod, .percent, "%"), .mod_assign => return renderBinOp(c, node, .assign_mod, .percent_equal, "%="), .@"and" => return renderBinOpGrouped(c, node, .bool_and, .keyword_and, "and"), diff --git a/src/value.zig b/src/value.zig index 29d8fa8db9..73a2b3a49f 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1588,6 +1588,35 @@ pub const Value = extern union { return result; } + /// Supports both floats and ints; handles undefined. + pub fn numberAddSat( + lhs: Value, + rhs: Value, + ty: Type, + arena: *Allocator, + target: Target, + ) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + if (ty.isAnyFloat()) { + // TODO: handle outside float range + return floatAdd(lhs, rhs, ty, arena); + } + const result = try intAdd(lhs, rhs, arena); + + const max = try ty.maxInt(arena, target); + if (compare(result, .gt, max, ty)) { + return max; + } + + const min = try ty.minInt(arena, target); + if (compare(result, .lt, min, ty)) { + return min; + } + + return result; + } + /// Supports both floats and ints; handles undefined. pub fn numberSubWrap( lhs: Value, @@ -1616,6 +1645,35 @@ pub const Value = extern union { return result; } + /// Supports both floats and ints; handles undefined. + pub fn numberSubSat( + lhs: Value, + rhs: Value, + ty: Type, + arena: *Allocator, + target: Target, + ) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + if (ty.isAnyFloat()) { + // TODO: handle outside float range + return floatSub(lhs, rhs, ty, arena); + } + const result = try intSub(lhs, rhs, arena); + + const max = try ty.maxInt(arena, target); + if (compare(result, .gt, max, ty)) { + return max; + } + + const min = try ty.minInt(arena, target); + if (compare(result, .lt, min, ty)) { + return min; + } + + return result; + } + /// Supports both floats and ints; handles undefined. pub fn numberMulWrap( lhs: Value, @@ -1644,6 +1702,35 @@ pub const Value = extern union { return result; } + /// Supports both floats and ints; handles undefined. + pub fn numberMulSat( + lhs: Value, + rhs: Value, + ty: Type, + arena: *Allocator, + target: Target, + ) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + if (ty.isAnyFloat()) { + // TODO: handle outside float range + return floatMul(lhs, rhs, ty, arena); + } + const result = try intMul(lhs, rhs, arena); + + const max = try ty.maxInt(arena, target); + if (compare(result, .gt, max, ty)) { + return max; + } + + const min = try ty.minInt(arena, target); + if (compare(result, .lt, min, ty)) { + return min; + } + + return result; + } + /// Supports both floats and ints; handles undefined. pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value { if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); diff --git a/test/behavior/saturating_arithmetic.zig b/test/behavior/saturating_arithmetic.zig index 5d7a229c3c..91f9c17fb9 100644 --- a/test/behavior/saturating_arithmetic.zig +++ b/test/behavior/saturating_arithmetic.zig @@ -32,7 +32,7 @@ fn testSaturatingOp(comptime op: Op, comptime T: type, test_data: [3]T) !void { } } -test "@addWithSaturation" { +test "saturating add" { const S = struct { fn doTheTest() !void { // .{a, b, expected a+b} @@ -50,22 +50,16 @@ test "@addWithSaturation" { try testSaturatingOp(.add, u128, .{ maxInt(u128), 1, maxInt(u128) }); const u8x3 = std.meta.Vector(3, u8); - try expectEqual(u8x3{ 255, 255, 255 }, @addWithSaturation( - u8x3{ 255, 254, 1 }, - u8x3{ 1, 2, 255 }, - )); + try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 255, 254, 1 } +| u8x3{ 1, 2, 255 })); const i8x3 = std.meta.Vector(3, i8); - try expectEqual(i8x3{ 127, 127, 127 }, @addWithSaturation( - i8x3{ 127, 126, 1 }, - i8x3{ 1, 2, 127 }, - )); + try expectEqual(i8x3{ 127, 127, 127 }, (i8x3{ 127, 126, 1 } +| i8x3{ 1, 2, 127 })); } }; try S.doTheTest(); comptime try S.doTheTest(); } -test "@subWithSaturation" { +test "saturating subtraction" { const S = struct { fn doTheTest() !void { // .{a, b, expected a-b} @@ -81,17 +75,14 @@ test "@subWithSaturation" { try testSaturatingOp(.sub, u128, .{ 0, maxInt(u128), 0 }); const u8x3 = std.meta.Vector(3, u8); - try expectEqual(u8x3{ 0, 0, 0 }, @subWithSaturation( - u8x3{ 0, 0, 0 }, - u8x3{ 255, 255, 255 }, - )); + try expectEqual(u8x3{ 0, 0, 0 }, (u8x3{ 0, 0, 0 } -| u8x3{ 255, 255, 255 })); } }; try S.doTheTest(); comptime try S.doTheTest(); } -test "@mulWithSaturation" { +test "saturating multiplication" { // TODO: once #9660 has been solved, remove this line if (std.builtin.target.cpu.arch == .wasm32) return error.SkipZigTest; @@ -112,10 +103,7 @@ test "@mulWithSaturation" { try testSaturatingOp(.mul, u128, .{ maxInt(u128), maxInt(u128), maxInt(u128) }); const u8x3 = std.meta.Vector(3, u8); - try expectEqual(u8x3{ 255, 255, 255 }, @mulWithSaturation( - u8x3{ 2, 2, 2 }, - u8x3{ 255, 255, 255 }, - )); + try expectEqual(u8x3{ 255, 255, 255 }, (u8x3{ 2, 2, 2 } *| u8x3{ 255, 255, 255 })); } }; @@ -123,7 +111,7 @@ test "@mulWithSaturation" { comptime try S.doTheTest(); } -test "@shlWithSaturation" { +test "saturating shift-left" { const S = struct { fn doTheTest() !void { // .{a, b, expected a< Date: Tue, 28 Sep 2021 20:27:28 -0700 Subject: [PATCH 146/160] saturating arithmetic supports integers only --- src/Air.zig | 2 +- src/Sema.zig | 14 +++++++------- src/value.zig | 33 ++++++++++++--------------------- test/compile_errors.zig | 4 ++-- 4 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index f05c18e87a..cdc5ff2287 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -130,7 +130,7 @@ pub const Inst = struct { /// it shifts out any bits that disagree with the resultant sign bit. /// Uses the `bin_op` field. shl_exact, - /// Shift left saturating. `<<|` + /// Saturating integer shift left. `<<|` /// Uses the `bin_op` field. shl_sat, /// Bitwise XOR. `^` diff --git a/src/Sema.zig b/src/Sema.zig index f106d7ea9e..0fb93f3fbe 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6380,7 +6380,7 @@ fn analyzeArithmetic( } else break :rs .{ .src = rhs_src, .air_tag = .addwrap }; }, .add_sat => { - // For both integers and floats: + // Integers only; floats are checked above. // If either of the operands are zero, then the other operand is returned. // If either of the operands are undefined, the result is undefined. if (maybe_lhs_val) |lhs_val| { @@ -6398,7 +6398,7 @@ fn analyzeArithmetic( if (maybe_lhs_val) |lhs_val| { return sema.addConstant( scalar_type, - try lhs_val.numberAddSat(rhs_val, scalar_type, sema.arena, target), + try lhs_val.intAddSat(rhs_val, scalar_type, sema.arena, target), ); } else break :rs .{ .src = lhs_src, .air_tag = .add_sat }; } else break :rs .{ .src = rhs_src, .air_tag = .add_sat }; @@ -6471,7 +6471,7 @@ fn analyzeArithmetic( } else break :rs .{ .src = lhs_src, .air_tag = .subwrap }; }, .sub_sat => { - // For both integers and floats: + // Integers only; floats are checked above. // If the RHS is zero, result is LHS. // If either of the operands are undefined, result is undefined. if (maybe_rhs_val) |rhs_val| { @@ -6489,7 +6489,7 @@ fn analyzeArithmetic( if (maybe_rhs_val) |rhs_val| { return sema.addConstant( scalar_type, - try lhs_val.numberSubSat(rhs_val, scalar_type, sema.arena, target), + try lhs_val.intSubSat(rhs_val, scalar_type, sema.arena, target), ); } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat }; @@ -6647,7 +6647,7 @@ fn analyzeArithmetic( } else break :rs .{ .src = rhs_src, .air_tag = .mulwrap }; }, .mul_sat => { - // For both integers and floats: + // Integers only; floats are checked above. // If either of the operands are zero, result is zero. // If either of the operands are one, result is the other operand. // If either of the operands are undefined, result is undefined. @@ -6677,7 +6677,7 @@ fn analyzeArithmetic( } return sema.addConstant( scalar_type, - try lhs_val.numberMulSat(rhs_val, scalar_type, sema.arena, target), + try lhs_val.intMulSat(rhs_val, scalar_type, sema.arena, target), ); } else break :rs .{ .src = lhs_src, .air_tag = .mul_sat }; } else break :rs .{ .src = rhs_src, .air_tag = .mul_sat }; @@ -7931,7 +7931,7 @@ fn analyzeRet( fn floatOpAllowed(tag: Zir.Inst.Tag) bool { // extend this swich as additional operators are implemented return switch (tag) { - .add, .add_sat, .sub, .sub_sat, .mul, .mul_sat, .div, .mod, .rem, .mod_rem => true, + .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true, else => false, }; } diff --git a/src/value.zig b/src/value.zig index 73a2b3a49f..0ead2ff1d9 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1588,20 +1588,17 @@ pub const Value = extern union { return result; } - /// Supports both floats and ints; handles undefined. - pub fn numberAddSat( + /// Supports integers only; asserts neither operand is undefined. + pub fn intAddSat( lhs: Value, rhs: Value, ty: Type, arena: *Allocator, target: Target, ) !Value { - if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + assert(!lhs.isUndef()); + assert(!rhs.isUndef()); - if (ty.isAnyFloat()) { - // TODO: handle outside float range - return floatAdd(lhs, rhs, ty, arena); - } const result = try intAdd(lhs, rhs, arena); const max = try ty.maxInt(arena, target); @@ -1645,20 +1642,17 @@ pub const Value = extern union { return result; } - /// Supports both floats and ints; handles undefined. - pub fn numberSubSat( + /// Supports integers only; asserts neither operand is undefined. + pub fn intSubSat( lhs: Value, rhs: Value, ty: Type, arena: *Allocator, target: Target, ) !Value { - if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + assert(!lhs.isUndef()); + assert(!rhs.isUndef()); - if (ty.isAnyFloat()) { - // TODO: handle outside float range - return floatSub(lhs, rhs, ty, arena); - } const result = try intSub(lhs, rhs, arena); const max = try ty.maxInt(arena, target); @@ -1702,20 +1696,17 @@ pub const Value = extern union { return result; } - /// Supports both floats and ints; handles undefined. - pub fn numberMulSat( + /// Supports integers only; asserts neither operand is undefined. + pub fn intMulSat( lhs: Value, rhs: Value, ty: Type, arena: *Allocator, target: Target, ) !Value { - if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + assert(!lhs.isUndef()); + assert(!rhs.isUndef()); - if (ty.isAnyFloat()) { - // TODO: handle outside float range - return floatMul(lhs, rhs, ty, arena); - } const result = try intMul(lhs, rhs, arena); const max = try ty.maxInt(arena, target); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 0e59b0523f..4acd563da9 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -8859,9 +8859,9 @@ pub fn addCases(ctx: *TestContext) !void { "tmp.zig:3:12: note: crosses namespace boundary here", }); - ctx.objErrStage1("Issue #9619: saturating arithmetic builtins should fail to compile when given floats", + ctx.objErrStage1("saturating arithmetic does not allow floats", \\pub fn main() !void { - \\ _ = @addWithSaturation(@as(f32, 1.0), @as(f32, 1.0)); + \\ _ = @as(f32, 1.0) +| @as(f32, 1.0); \\} , &[_][]const u8{ "error: invalid operands to binary expression: 'f32' and 'f32'", From 7efc2a06264170632e56256a5fad97e945768056 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Sep 2021 20:33:50 -0700 Subject: [PATCH 147/160] AstGen: improved logic for nodeMayNeedMemoryLocation * `@as` and `@bitCast` no longer unconditionally return `true` from this function; they forward the question to their sub-expression. * fix `@splat` incorrectly being marked as needing a memory location (this function returns a SIMD vector; it definitely does not want a memory location). Makes AstGen generate slightly nicer ZIR, which in turn generates slightly nicer AIR, generating slightly nicer machine code in debug builds. It also means I can procrastinate implementing the bitcast_result_ptr ZIR instruction semantic analysis :^) --- src/AstGen.zig | 26 ++++++++++++++++++++------ src/BuiltinFn.zig | 26 +++++++++++++++++--------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 847860630a..24a73539b3 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -8271,17 +8271,31 @@ fn nodeMayNeedMemoryLocation(tree: *const Ast, start_node: Ast.Node.Index) bool } }, - .builtin_call, - .builtin_call_comma, - .builtin_call_two, - .builtin_call_two_comma, - => { + .builtin_call_two, .builtin_call_two_comma => { const builtin_token = main_tokens[node]; const builtin_name = tree.tokenSlice(builtin_token); // If the builtin is an invalid name, we don't cause an error here; instead // let it pass, and the error will be "invalid builtin function" later. const builtin_info = BuiltinFn.list.get(builtin_name) orelse return false; - return builtin_info.needs_mem_loc; + switch (builtin_info.needs_mem_loc) { + .never => return false, + .always => return true, + .forward1 => node = node_datas[node].rhs, + } + }, + + .builtin_call, .builtin_call_comma => { + const params = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs]; + const builtin_token = main_tokens[node]; + const builtin_name = tree.tokenSlice(builtin_token); + // If the builtin is an invalid name, we don't cause an error here; instead + // let it pass, and the error will be "invalid builtin function" later. + const builtin_info = BuiltinFn.list.get(builtin_name) orelse return false; + switch (builtin_info.needs_mem_loc) { + .never => return false, + .always => return true, + .forward1 => node = params[1], + } }, } } diff --git a/src/BuiltinFn.zig b/src/BuiltinFn.zig index 8f23ec86d7..e1f4f5bd16 100644 --- a/src/BuiltinFn.zig +++ b/src/BuiltinFn.zig @@ -110,10 +110,19 @@ pub const Tag = enum { Vector, }; +pub const MemLocRequirement = enum { + /// The builtin never needs a memory location. + never, + /// The builtin always needs a memory location. + always, + /// The builtin forwards the question to argument at index 1. + forward1, +}; + tag: Tag, -/// `true` if the builtin call can take advantage of a result location pointer. -needs_mem_loc: bool = false, +/// Info about the builtin call's ability to take advantage of a result location pointer. +needs_mem_loc: MemLocRequirement = .never, /// `true` if the builtin call can be the left-hand side of an expression (assigned to). allows_lvalue: bool = false, /// The number of parameters to this builtin function. `null` means variable number @@ -148,7 +157,7 @@ pub const list = list: { "@as", .{ .tag = .as, - .needs_mem_loc = true, + .needs_mem_loc = .forward1, .param_count = 2, }, }, @@ -184,7 +193,7 @@ pub const list = list: { "@bitCast", .{ .tag = .bit_cast, - .needs_mem_loc = true, + .needs_mem_loc = .forward1, .param_count = 2, }, }, @@ -248,7 +257,7 @@ pub const list = list: { "@call", .{ .tag = .call, - .needs_mem_loc = true, + .needs_mem_loc = .always, .param_count = 3, }, }, @@ -410,7 +419,7 @@ pub const list = list: { "@field", .{ .tag = .field, - .needs_mem_loc = true, + .needs_mem_loc = .always, .param_count = 2, .allows_lvalue = true, }, @@ -699,7 +708,6 @@ pub const list = list: { "@splat", .{ .tag = .splat, - .needs_mem_loc = true, .param_count = 2, }, }, @@ -714,7 +722,7 @@ pub const list = list: { "@src", .{ .tag = .src, - .needs_mem_loc = true, + .needs_mem_loc = .always, .param_count = 0, }, }, @@ -869,7 +877,7 @@ pub const list = list: { "@unionInit", .{ .tag = .union_init, - .needs_mem_loc = true, + .needs_mem_loc = .always, .param_count = 3, }, }, From 33e77f127d8237088b561fae2ca0f4412bc1d6c9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Sep 2021 22:38:51 -0700 Subject: [PATCH 148/160] stage2: implement `@clz` and `@ctz` Also improve the LLVM backend to support lowering bigints to LLVM values. Moves over a bunch of math.zig test cases to the "passing for stage2" section. --- src/Air.zig | 10 ++ src/Liveness.zig | 2 + src/Sema.zig | 83 +++++++++++----- src/codegen.zig | 18 ++++ src/codegen/c.zig | 19 ++++ src/codegen/llvm.zig | 49 +++++++-- src/codegen/llvm/bindings.zig | 7 +- src/print_air.zig | 2 + src/type.zig | 6 +- src/value.zig | 39 ++++++++ test/behavior/math.zig | 182 ++++++++++++++++++++++++++++++++++ test/behavior/math_stage1.zig | 178 --------------------------------- 12 files changed, 379 insertions(+), 216 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index cdc5ff2287..f7eccfd5a5 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -160,6 +160,14 @@ pub const Inst = struct { /// Result type is the return type of the function being called. /// Uses the `pl_op` field with the `Call` payload. operand is the callee. call, + /// Count leading zeroes of an integer according to its representation in twos complement. + /// Result type will always be an unsigned integer big enough to fit the answer. + /// Uses the `ty_op` field. + clz, + /// Count trailing zeroes of an integer according to its representation in twos complement. + /// Result type will always be an unsigned integer big enough to fit the answer. + /// Uses the `ty_op` field. + ctz, /// `<`. Result type is always bool. /// Uses the `bin_op` field. @@ -669,6 +677,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .float_to_int, .int_to_float, .get_union_tag, + .clz, + .ctz, => return air.getRefType(datas[inst].ty_op.ty), .loop, diff --git a/src/Liveness.zig b/src/Liveness.zig index 93f28ad7b2..71a0414383 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -304,6 +304,8 @@ fn analyzeInst( .float_to_int, .int_to_float, .get_union_tag, + .clz, + .ctz, => { const o = inst_datas[inst].ty_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index 0fb93f3fbe..51ebb496f3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4611,8 +4611,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs); const operand = sema.resolveInst(extra.rhs); - const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_type); - _ = try sema.requireIntegerType(block, operand_src, sema.typeOf(operand)); + const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_type); + _ = try sema.checkIntType(block, operand_src, sema.typeOf(operand)); if (try sema.isComptimeKnown(block, operand_src, operand)) { return sema.coerce(block, dest_type, operand, operand_src); @@ -8384,7 +8384,7 @@ fn zirIntToFloat(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile const operand = sema.resolveInst(extra.rhs); const operand_ty = sema.typeOf(operand); - try sema.checkIntType(block, ty_src, dest_ty); + _ = try sema.checkIntType(block, ty_src, dest_ty); try sema.checkFloatType(block, operand_src, operand_ty); if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { @@ -8493,8 +8493,8 @@ fn zirTruncate(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr const operand = sema.resolveInst(extra.rhs); const operand_ty = sema.typeOf(operand); const mod = sema.mod; - const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_ty); - const src_is_comptime_int = try sema.requireIntegerType(block, operand_src, operand_ty); + const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_ty); + const src_is_comptime_int = try sema.checkIntType(block, operand_src, operand_ty); if (dest_is_comptime_int) { return sema.coerce(block, dest_ty, operand, operand_src); @@ -8552,14 +8552,56 @@ fn zirAlignCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileE fn zirClz(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirClz", .{}); + const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const operand = sema.resolveInst(inst_data.operand); + const operand_ty = sema.typeOf(operand); + // TODO implement support for vectors + if (operand_ty.zigTypeTag() != .Int) { + return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{ + operand_ty, + }); + } + const target = sema.mod.getTarget(); + const bits = operand_ty.intInfo(target).bits; + if (bits == 0) return Air.Inst.Ref.zero; + + const result_ty = try Type.smallestUnsignedInt(sema.arena, bits); + + const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(result_ty); + return sema.addIntUnsigned(result_ty, val.clz(operand_ty, target)); + } else operand_src; + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addTyOp(.clz, result_ty, operand); } fn zirCtz(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirCtz", .{}); + const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const operand = sema.resolveInst(inst_data.operand); + const operand_ty = sema.typeOf(operand); + // TODO implement support for vectors + if (operand_ty.zigTypeTag() != .Int) { + return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{ + operand_ty, + }); + } + const target = sema.mod.getTarget(); + const bits = operand_ty.intInfo(target).bits; + if (bits == 0) return Air.Inst.Ref.zero; + + const result_ty = try Type.smallestUnsignedInt(sema.arena, bits); + + const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(result_ty); + return sema.mod.fail(&block.base, operand_src, "TODO: implement comptime @ctz", .{}); + } else operand_src; + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addTyOp(.ctz, result_ty, operand); } fn zirPopCount(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8616,17 +8658,12 @@ fn zirOffsetOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.mod.fail(&block.base, src, "TODO: Sema.zirOffsetOf", .{}); } -fn checkIntType( - sema: *Sema, - block: *Scope.Block, - ty_src: LazySrcLoc, - ty: Type, -) CompileError!void { +/// Returns `true` if the type was a comptime_int. +fn checkIntType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!bool { switch (ty.zigTypeTag()) { - .ComptimeInt, .Int => {}, - else => return sema.mod.fail(&block.base, ty_src, "expected integer type, found '{}'", .{ - ty, - }), + .ComptimeInt => return true, + .Int => return false, + else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}), } } @@ -9416,14 +9453,6 @@ fn requireRuntimeBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void try sema.requireFunctionBlock(block, src); } -fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !bool { - switch (ty.zigTypeTag()) { - .ComptimeInt => return true, - .Int => return false, - else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}), - } -} - /// Emit a compile error if type cannot be used for a runtime variable. fn validateVarType( sema: *Sema, diff --git a/src/codegen.zig b/src/codegen.zig index 79105dc4a7..dfaedf041a 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -896,6 +896,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .memset => try self.airMemset(inst), .set_union_tag => try self.airSetUnionTag(inst), .get_union_tag => try self.airGetUnionTag(inst), + .clz => try self.airClz(inst), + .ctz => try self.airCtz(inst), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -1606,6 +1608,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } + fn airClz(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement airClz for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + + fn airCtz(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement airCtz for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool { if (!self.liveness.operandDies(inst, op_index)) return false; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 95ce95f2e5..d2ce9cc6de 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -962,6 +962,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .memcpy => try airMemcpy(f, inst), .set_union_tag => try airSetUnionTag(f, inst), .get_union_tag => try airGetUnionTag(f, inst), + .clz => try airBuiltinCall(f, inst, "clz"), + .ctz => try airBuiltinCall(f, inst, "ctz"), .int_to_float, .float_to_int, @@ -2075,6 +2077,23 @@ fn airSimpleCast(f: *Function, inst: Air.Inst.Index) !CValue { return local; } +fn airBuiltinCall(f: *Function, inst: Air.Inst.Index, fn_name: [*:0]const u8) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; + + const inst_ty = f.air.typeOfIndex(inst); + const local = try f.allocLocal(inst_ty, .Const); + const ty_op = f.air.instructions.items(.data)[inst].ty_op; + const writer = f.object.writer(); + const operand = try f.resolveInst(ty_op.operand); + + // TODO implement the function in zig.h and call it here + + try writer.print(" = {s}(", .{fn_name}); + try f.writeCValue(writer, operand); + try writer.writeAll(");\n"); + return local; +} + fn airCmpxchg(f: *Function, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.Cmpxchg, ty_pl.payload).data; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b27afa9b54..2c278221ae 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -889,14 +889,14 @@ pub const DeclGen = struct { const llvm_type = try self.llvmType(tv.ty); if (bigint.eqZero()) return llvm_type.constNull(); - if (bigint.limbs.len != 1) { - return self.todo("implement bigger bigint", .{}); - } - const llvm_int = llvm_type.constInt(bigint.limbs[0], .False); + const unsigned_val = if (bigint.limbs.len == 1) + llvm_type.constInt(bigint.limbs[0], .False) + else + llvm_type.constIntOfArbitraryPrecision(@intCast(c_uint, bigint.limbs.len), bigint.limbs.ptr); if (!bigint.positive) { - return llvm.constNeg(llvm_int); + return llvm.constNeg(unsigned_val); } - return llvm_int; + return unsigned_val; }, .Enum => { const llvm_type = try self.llvmType(tv.ty); @@ -1310,6 +1310,8 @@ pub const FuncGen = struct { .memcpy => try self.airMemcpy(inst), .set_union_tag => try self.airSetUnionTag(inst), .get_union_tag => try self.airGetUnionTag(inst), + .clz => try self.airClzCtz(inst, "ctlz"), + .ctz => try self.airClzCtz(inst, "cttz"), .atomic_store_unordered => try self.airAtomicStore(inst, .Unordered), .atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic), @@ -2699,6 +2701,41 @@ pub const FuncGen = struct { return self.builder.buildExtractValue(un, 1, ""); } + fn airClzCtz(self: *FuncGen, inst: Air.Inst.Index, prefix: [*:0]const u8) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand_ty = self.air.typeOf(ty_op.operand); + const operand = try self.resolveInst(ty_op.operand); + const target = self.dg.module.getTarget(); + const bits = operand_ty.intInfo(target).bits; + + var fn_name_buf: [100]u8 = undefined; + const llvm_fn_name = std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.i{d}", .{ + prefix, bits, + }) catch unreachable; + const llvm_i1 = self.context.intType(1); + const fn_val = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: { + const operand_llvm_ty = try self.dg.llvmType(operand_ty); + const param_types = [_]*const llvm.Type{ operand_llvm_ty, llvm_i1 }; + const fn_type = llvm.functionType(operand_llvm_ty, ¶m_types, param_types.len, .False); + break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type); + }; + + const params = [_]*const llvm.Value{ operand, llvm_i1.constNull() }; + const wrong_size_result = self.builder.buildCall(fn_val, ¶ms, params.len, ""); + const result_ty = self.air.typeOfIndex(inst); + const result_llvm_ty = try self.dg.llvmType(result_ty); + const result_bits = result_ty.intInfo(target).bits; + if (bits > result_bits) { + return self.builder.buildTrunc(wrong_size_result, result_llvm_ty, ""); + } else if (bits < result_bits) { + return self.builder.buildZExt(wrong_size_result, result_llvm_ty, ""); + } else { + return wrong_size_result; + } + } + fn fieldPtr( self: *FuncGen, inst: Air.Inst.Index, diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 4fac6656c8..68d91f6c68 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -172,6 +172,9 @@ pub const Type = opaque { pub const constInt = LLVMConstInt; extern fn LLVMConstInt(IntTy: *const Type, N: c_ulonglong, SignExtend: Bool) *const Value; + pub const constIntOfArbitraryPrecision = LLVMConstIntOfArbitraryPrecision; + extern fn LLVMConstIntOfArbitraryPrecision(IntTy: *const Type, NumWords: c_uint, Words: [*]const u64) *const Value; + pub const constReal = LLVMConstReal; extern fn LLVMConstReal(RealTy: *const Type, N: f64) *const Value; @@ -300,7 +303,7 @@ extern fn LLVMGetInlineAsm( pub const functionType = LLVMFunctionType; extern fn LLVMFunctionType( ReturnType: *const Type, - ParamTypes: [*]*const Type, + ParamTypes: [*]const *const Type, ParamCount: c_uint, IsVarArg: Bool, ) *const Type; @@ -346,7 +349,7 @@ pub const Builder = opaque { extern fn LLVMBuildCall( *const Builder, Fn: *const Value, - Args: [*]*const Value, + Args: [*]const *const Value, NumArgs: c_uint, Name: [*:0]const u8, ) *const Value; diff --git a/src/print_air.zig b/src/print_air.zig index 885c1b62bd..dda3b4458b 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -186,6 +186,8 @@ const Writer = struct { .int_to_float, .float_to_int, .get_union_tag, + .clz, + .ctz, => try w.writeTyOp(s, inst), .block, diff --git a/src/type.zig b/src/type.zig index e13ede852a..cb2cc6d58a 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3902,16 +3902,16 @@ pub const Type = extern union { const bits = bits: { if (max == 0) break :bits 0; const base = std.math.log2(max); - const upper = (@as(u64, 1) << base) - 1; + const upper = (@as(u64, 1) << @intCast(u6, base)) - 1; break :bits base + @boolToInt(upper < max); }; - return switch (bits) { + return switch (@intCast(u16, bits)) { 1 => initTag(.u1), 8 => initTag(.u8), 16 => initTag(.u16), 32 => initTag(.u32), 64 => initTag(.u64), - else => return Tag.int_unsigned.create(arena, bits), + else => |b| return Tag.int_unsigned.create(arena, b), }; } }; diff --git a/src/value.zig b/src/value.zig index 0ead2ff1d9..ac52654041 100644 --- a/src/value.zig +++ b/src/value.zig @@ -962,6 +962,45 @@ pub const Value = extern union { }; } + pub fn clz(val: Value, ty: Type, target: Target) u64 { + const ty_bits = ty.intInfo(target).bits; + switch (val.tag()) { + .zero, .bool_false => return ty_bits, + .one, .bool_true => return ty_bits - 1, + + .int_u64 => { + const big = @clz(u64, val.castTag(.int_u64).?.data); + return big + ty_bits - 64; + }, + .int_i64 => { + @panic("TODO implement i64 Value clz"); + }, + .int_big_positive => { + // TODO: move this code into std lib big ints + const bigint = val.castTag(.int_big_positive).?.asBigInt(); + // Limbs are stored in little-endian order but we need + // to iterate big-endian. + var total_limb_lz: u64 = 0; + var i: usize = bigint.limbs.len; + const bits_per_limb = @sizeOf(std.math.big.Limb) * 8; + while (i != 0) { + i -= 1; + const limb = bigint.limbs[i]; + const this_limb_lz = @clz(std.math.big.Limb, limb); + total_limb_lz += this_limb_lz; + if (this_limb_lz != bits_per_limb) break; + } + const total_limb_bits = bigint.limbs.len * bits_per_limb; + return total_limb_lz + ty_bits - total_limb_bits; + }, + .int_big_negative => { + @panic("TODO implement int_big_negative Value clz"); + }, + + else => unreachable, + } + } + /// Asserts the value is an integer and not undefined. /// Returns the number of bits the value requires to represent stored in twos complement form. pub fn intBitCountTwosComp(self: Value) usize { diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 510cc3d438..56fbdc124d 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -53,3 +53,185 @@ fn testThreeExprInARow(f: bool, t: bool) !void { fn assertFalse(b: bool) !void { try expect(!b); } + +test "@clz" { + try testClz(); + comptime try testClz(); +} + +fn testClz() !void { + try expect(testOneClz(u8, 0b10001010) == 0); + try expect(testOneClz(u8, 0b00001010) == 4); + try expect(testOneClz(u8, 0b00011010) == 3); + try expect(testOneClz(u8, 0b00000000) == 8); + try expect(testOneClz(u128, 0xffffffffffffffff) == 64); + try expect(testOneClz(u128, 0x10000000000000000) == 63); +} + +fn testOneClz(comptime T: type, x: T) u32 { + return @clz(T, x); +} + +test "const number literal" { + const one = 1; + const eleven = ten + one; + + try expect(eleven == 11); +} +const ten = 10; + +test "float equality" { + const x: f64 = 0.012; + const y: f64 = x + 1.0; + + try testFloatEqualityImpl(x, y); + comptime try testFloatEqualityImpl(x, y); +} + +fn testFloatEqualityImpl(x: f64, y: f64) !void { + const y2 = x + 1.0; + try expect(y == y2); +} + +test "hex float literal parsing" { + comptime try expect(0x1.0 == 1.0); +} + +test "quad hex float literal parsing in range" { + const a = 0x1.af23456789bbaaab347645365cdep+5; + const b = 0x1.dedafcff354b6ae9758763545432p-9; + const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534; + const d = 0x1.edcbff8ad76ab5bf46463233214fp-435; + _ = a; + _ = b; + _ = c; + _ = d; +} + +test "underscore separator parsing" { + try expect(0_0_0_0 == 0); + try expect(1_234_567 == 1234567); + try expect(001_234_567 == 1234567); + try expect(0_0_1_2_3_4_5_6_7 == 1234567); + + try expect(0b0_0_0_0 == 0); + try expect(0b1010_1010 == 0b10101010); + try expect(0b0000_1010_1010 == 0b10101010); + try expect(0b1_0_1_0_1_0_1_0 == 0b10101010); + + try expect(0o0_0_0_0 == 0); + try expect(0o1010_1010 == 0o10101010); + try expect(0o0000_1010_1010 == 0o10101010); + try expect(0o1_0_1_0_1_0_1_0 == 0o10101010); + + try expect(0x0_0_0_0 == 0); + try expect(0x1010_1010 == 0x10101010); + try expect(0x0000_1010_1010 == 0x10101010); + try expect(0x1_0_1_0_1_0_1_0 == 0x10101010); + + try expect(123_456.789_000e1_0 == 123456.789000e10); + try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10); + + try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10); + try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10); +} + +test "hex float literal within range" { + const a = 0x1.0p16383; + const b = 0x0.1p16387; + const c = 0x1.0p-16382; + _ = a; + _ = b; + _ = c; +} + +test "comptime_int addition" { + comptime { + try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950); + try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380); + } +} + +test "comptime_int multiplication" { + comptime { + try expect( + 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567, + ); + try expect( + 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016, + ); + } +} + +test "comptime_int shifting" { + comptime { + try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000); + } +} + +test "comptime_int multi-limb shift and mask" { + comptime { + var a = 0xefffffffa0000001eeeeeeefaaaaaaab; + + try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab); + a >>= 32; + try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef); + a >>= 32; + try expect(@as(u32, a & 0xffffffff) == 0xa0000001); + a >>= 32; + try expect(@as(u32, a & 0xffffffff) == 0xefffffff); + a >>= 32; + + try expect(a == 0); + } +} + +test "comptime_int multi-limb partial shift right" { + comptime { + var a = 0x1ffffffffeeeeeeee; + a >>= 16; + try expect(a == 0x1ffffffffeeee); + } +} + +test "xor" { + try test_xor(); + comptime try test_xor(); +} + +fn test_xor() !void { + try testOneXor(0xFF, 0x00, 0xFF); + try testOneXor(0xF0, 0x0F, 0xFF); + try testOneXor(0xFF, 0xF0, 0x0F); + try testOneXor(0xFF, 0x0F, 0xF0); + try testOneXor(0xFF, 0xFF, 0x00); +} + +fn testOneXor(a: u8, b: u8, c: u8) !void { + try expect(a ^ b == c); +} + +test "comptime_int xor" { + comptime { + try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF); + try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000); + try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000); + try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF); + try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000); + } +} + +test "comptime_int param and return" { + const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702); + try expect(a == 137114567242441932203689521744947848950); + + const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768); + try expect(b == 985095453608931032642182098849559179469148836107390954364380); +} + +fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int { + return a + b; +} diff --git a/test/behavior/math_stage1.zig b/test/behavior/math_stage1.zig index 9f412930b5..f0c160ebc4 100644 --- a/test/behavior/math_stage1.zig +++ b/test/behavior/math_stage1.zig @@ -117,20 +117,6 @@ test "@*WithOverflow with u0 values" { try expect(!@shlWithOverflow(u0, 0, 0, &result)); } -test "@clz" { - try testClz(); - comptime try testClz(); -} - -fn testClz() !void { - try expect(@clz(u8, 0b10001010) == 0); - try expect(@clz(u8, 0b00001010) == 4); - try expect(@clz(u8, 0b00011010) == 3); - try expect(@clz(u8, 0b00000000) == 8); - try expect(@clz(u128, 0xffffffffffffffff) == 64); - try expect(@clz(u128, 0x10000000000000000) == 63); -} - test "@clz vectors" { try testClzVectors(); comptime try testClzVectors(); @@ -171,14 +157,6 @@ fn testCtzVectors() !void { try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16))); } -test "const number literal" { - const one = 1; - const eleven = ten + one; - - try expect(eleven == 11); -} -const ten = 10; - test "unsigned wrapping" { try testUnsignedWrappingEval(maxInt(u32)); comptime try testUnsignedWrappingEval(maxInt(u32)); @@ -274,19 +252,6 @@ test "small int addition" { try expect(result == 0); } -test "float equality" { - const x: f64 = 0.012; - const y: f64 = x + 1.0; - - try testFloatEqualityImpl(x, y); - comptime try testFloatEqualityImpl(x, y); -} - -fn testFloatEqualityImpl(x: f64, y: f64) !void { - const y2 = x + 1.0; - try expect(y == y2); -} - test "allow signed integer division/remainder when values are comptime known and positive or exact" { try expect(5 / 3 == 1); try expect(-5 / -3 == 1); @@ -296,23 +261,6 @@ test "allow signed integer division/remainder when values are comptime known and try expect(-6 % 3 == 0); } -test "hex float literal parsing" { - comptime try expect(0x1.0 == 1.0); -} - -test "quad hex float literal parsing in range" { - const a = 0x1.af23456789bbaaab347645365cdep+5; - const b = 0x1.dedafcff354b6ae9758763545432p-9; - const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534; - const d = 0x1.edcbff8ad76ab5bf46463233214fp-435; - if (false) { - a; - b; - c; - d; - } -} - test "quad hex float literal parsing accurate" { const a: f128 = 0x1.1111222233334444555566667777p+0; @@ -403,45 +351,6 @@ test "quad hex float literal parsing accurate" { comptime try S.doTheTest(); } -test "underscore separator parsing" { - try expect(0_0_0_0 == 0); - try expect(1_234_567 == 1234567); - try expect(001_234_567 == 1234567); - try expect(0_0_1_2_3_4_5_6_7 == 1234567); - - try expect(0b0_0_0_0 == 0); - try expect(0b1010_1010 == 0b10101010); - try expect(0b0000_1010_1010 == 0b10101010); - try expect(0b1_0_1_0_1_0_1_0 == 0b10101010); - - try expect(0o0_0_0_0 == 0); - try expect(0o1010_1010 == 0o10101010); - try expect(0o0000_1010_1010 == 0o10101010); - try expect(0o1_0_1_0_1_0_1_0 == 0o10101010); - - try expect(0x0_0_0_0 == 0); - try expect(0x1010_1010 == 0x10101010); - try expect(0x0000_1010_1010 == 0x10101010); - try expect(0x1_0_1_0_1_0_1_0 == 0x10101010); - - try expect(123_456.789_000e1_0 == 123456.789000e10); - try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10); - - try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10); - try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10); -} - -test "hex float literal within range" { - const a = 0x1.0p16383; - const b = 0x0.1p16387; - const c = 0x1.0p-16382; - if (false) { - a; - b; - c; - } -} - test "truncating shift left" { try testShlTrunc(maxInt(u16)); comptime try testShlTrunc(maxInt(u16)); @@ -497,81 +406,6 @@ test "shift left/right on u0 operand" { comptime try S.doTheTest(); } -test "comptime_int addition" { - comptime { - try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950); - try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380); - } -} - -test "comptime_int multiplication" { - comptime { - try expect( - 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567, - ); - try expect( - 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016, - ); - } -} - -test "comptime_int shifting" { - comptime { - try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000); - } -} - -test "comptime_int multi-limb shift and mask" { - comptime { - var a = 0xefffffffa0000001eeeeeeefaaaaaaab; - - try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab); - a >>= 32; - try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef); - a >>= 32; - try expect(@as(u32, a & 0xffffffff) == 0xa0000001); - a >>= 32; - try expect(@as(u32, a & 0xffffffff) == 0xefffffff); - a >>= 32; - - try expect(a == 0); - } -} - -test "comptime_int multi-limb partial shift right" { - comptime { - var a = 0x1ffffffffeeeeeeee; - a >>= 16; - try expect(a == 0x1ffffffffeeee); - } -} - -test "xor" { - try test_xor(); - comptime try test_xor(); -} - -fn test_xor() !void { - try expect(0xFF ^ 0x00 == 0xFF); - try expect(0xF0 ^ 0x0F == 0xFF); - try expect(0xFF ^ 0xF0 == 0x0F); - try expect(0xFF ^ 0x0F == 0xF0); - try expect(0xFF ^ 0xFF == 0x00); -} - -test "comptime_int xor" { - comptime { - try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF); - try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000); - try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000); - try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF); - try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000); - } -} - test "f128" { try test_f128(); comptime try test_f128(); @@ -757,18 +591,6 @@ fn testRound(comptime T: type, x: T) !void { try expectEqual(x, z); } -test "comptime_int param and return" { - const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702); - try expect(a == 137114567242441932203689521744947848950); - - const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768); - try expect(b == 985095453608931032642182098849559179469148836107390954364380); -} - -fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int { - return a + b; -} - test "vector integer addition" { const S = struct { fn doTheTest() !void { From 99961f22dca7e01b85b7819dcea814ad01b65af5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 29 Sep 2021 00:13:21 -0700 Subject: [PATCH 149/160] stage2: enable building compiler_rt when using LLVM backend * AstGen: fix emitting `store_to_inferred_ptr` when it should be emitting `store` for a variable that has an explicit alignment. * Compilation: fix a couple memory leaks * Sema: implement support for locals that have specified alignment. * Sema: implement `@intCast` when it needs to emit an AIR instruction. * Sema: implement `@alignOf` * Implement debug printing for extended alloc ZIR instructions. --- src/AstGen.zig | 6 +++++- src/Compilation.zig | 9 +++++++-- src/Package.zig | 15 +++++++++------ src/Sema.zig | 47 +++++++++++++++++++++++++++++++++++++++++---- src/Zir.zig | 8 ++++++++ src/link/Elf.zig | 2 +- src/print_zir.zig | 26 ++++++++++++++++++++++++- src/type.zig | 1 + 8 files changed, 99 insertions(+), 15 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 24a73539b3..387364cb82 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2567,7 +2567,11 @@ fn varDecl( for (init_scope.instructions.items) |src_inst| { if (zir_tags[src_inst] == .store_to_block_ptr) { if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) { - zir_tags[src_inst] = .store_to_inferred_ptr; + if (var_decl.ast.type_node != 0) { + zir_tags[src_inst] = .store; + } else { + zir_tags[src_inst] = .store_to_inferred_ptr; + } } } parent_zir.appendAssumeCapacity(src_inst); diff --git a/src/Compilation.zig b/src/Compilation.zig index ef762dae1e..cb19a21c15 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1574,10 +1574,11 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { // also test the use case of `build-obj -fcompiler-rt` with the self-hosted compiler // and make sure the compiler-rt symbols are emitted. Currently this is hooked up for // stage1 but not stage2. - const capable_of_building_compiler_rt = comp.bin_file.options.use_stage1; - const capable_of_building_ssp = comp.bin_file.options.use_stage1; + const capable_of_building_compiler_rt = comp.bin_file.options.use_stage1 or + comp.bin_file.options.use_llvm; const capable_of_building_zig_libc = comp.bin_file.options.use_stage1 or comp.bin_file.options.use_llvm; + const capable_of_building_ssp = comp.bin_file.options.use_stage1; if (comp.bin_file.options.include_compiler_rt and capable_of_building_compiler_rt) { if (is_exe_or_dyn_lib) { @@ -1648,6 +1649,9 @@ pub fn destroy(self: *Compilation) void { if (self.compiler_rt_static_lib) |*crt_file| { crt_file.deinit(gpa); } + if (self.compiler_rt_obj) |*crt_file| { + crt_file.deinit(gpa); + } if (self.libssp_static_lib) |*crt_file| { crt_file.deinit(gpa); } @@ -3977,6 +3981,7 @@ fn buildOutputFromZig( }, .root_src_path = src_basename, }; + defer main_pkg.deinitTable(comp.gpa); const root_name = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len]; const target = comp.getTarget(); const bin_basename = try std.zig.binNameAlloc(comp.gpa, .{ diff --git a/src/Package.zig b/src/Package.zig index 3814f0eb95..f5380aaacb 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -99,15 +99,18 @@ pub fn destroy(pkg: *Package, gpa: *Allocator) void { } } - { - var it = pkg.table.keyIterator(); - while (it.next()) |key| { - gpa.free(key.*); - } + pkg.deinitTable(gpa); + gpa.destroy(pkg); +} + +/// Only frees memory associated with the table. +pub fn deinitTable(pkg: *Package, gpa: *Allocator) void { + var it = pkg.table.keyIterator(); + while (it.next()) |key| { + gpa.free(key.*); } pkg.table.deinit(gpa); - gpa.destroy(pkg); } pub fn add(pkg: *Package, gpa: *Allocator, name: []const u8, package: *Package) !void { diff --git a/src/Sema.zig b/src/Sema.zig index 51ebb496f3..53d7a9f4a2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1587,7 +1587,42 @@ fn zirAllocExtended( ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.AllocExtended, extended.operand); const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; - return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocExtended", .{}); + const ty_src = src; // TODO better source location + const align_src = src; // TODO better source location + const small = @bitCast(Zir.Inst.AllocExtended.Small, extended.small); + + var extra_index: usize = extra.end; + + const var_ty: Type = if (small.has_type) blk: { + const type_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + break :blk try sema.resolveType(block, ty_src, type_ref); + } else { + return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocExtended inferred", .{}); + }; + + const alignment: u16 = if (small.has_align) blk: { + const align_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const alignment = try sema.resolveAlign(block, align_src, align_ref); + break :blk alignment; + } else 0; + + if (small.is_comptime) { + return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocExtended comptime", .{}); + } + + if (!small.is_const) { + return sema.mod.fail(&block.base, src, "TODO implement Sema.zirAllocExtended var", .{}); + } + + const ptr_type = try Type.ptr(sema.arena, .{ + .pointee_type = var_ty, + .@"align" = alignment, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); + try sema.requireRuntimeBlock(block, src); + return block.addTy(.alloc, ptr_type); } fn zirAllocComptime(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -4620,7 +4655,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr return sema.mod.fail(&block.base, src, "unable to cast runtime value to 'comptime_int'", .{}); } - return sema.mod.fail(&block.base, src, "TODO implement analyze widen or shorten int", .{}); + try sema.requireRuntimeBlock(block, operand_src); + return block.addTyOp(.intcast, dest_type, operand); } fn zirBitcast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8257,8 +8293,11 @@ fn zirFrameAddress( fn zirAlignOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirAlignOf", .{}); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const ty = try sema.resolveType(block, operand_src, inst_data.operand); + const target = sema.mod.getTarget(); + const abi_align = ty.abiAlignment(target); + return sema.addIntUnsigned(Type.comptime_int, abi_align); } fn zirBoolToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { diff --git a/src/Zir.zig b/src/Zir.zig index 1da53a526e..e7359f9382 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2878,6 +2878,14 @@ pub const Inst = struct { /// 1. align_inst: Ref, // if small 0b00X0 is set pub const AllocExtended = struct { src_node: i32, + + pub const Small = packed struct { + has_type: bool, + has_align: bool, + is_const: bool, + is_comptime: bool, + _: u12 = undefined, + }; }; pub const Export = struct { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 6c3220d39e..cac475d445 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1284,7 +1284,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os; const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt) blk: { // TODO: remove when stage2 can build compiler_rt.zig - if (!build_options.is_stage1 or !self.base.options.use_stage1) break :blk null; + if (!self.base.options.use_llvm) break :blk null; // In the case of build-obj we include the compiler-rt symbols directly alongside // the symbols of the root source file, in the same compilation unit. diff --git a/src/print_zir.zig b/src/print_zir.zig index 5ffd6619af..54fd7e632f 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -393,6 +393,7 @@ const Writer = struct { .@"asm" => try self.writeAsm(stream, extended), .func => try self.writeFuncExtended(stream, extended), .variable => try self.writeVarExtended(stream, extended), + .alloc => try self.writeAllocExtended(stream, extended), .compile_log, .typeof_peer, @@ -423,7 +424,6 @@ const Writer = struct { try stream.writeByte(')'); }, - .alloc, .builtin_extern, .wasm_memory_size, .wasm_memory_grow, @@ -1767,6 +1767,30 @@ const Writer = struct { try stream.writeAll("))"); } + fn writeAllocExtended(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + const extra = self.code.extraData(Zir.Inst.AllocExtended, extended.operand); + const small = @bitCast(Zir.Inst.AllocExtended.Small, extended.small); + const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; + + var extra_index: usize = extra.end; + const type_inst: Zir.Inst.Ref = if (!small.has_type) .none else blk: { + const type_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :blk type_inst; + }; + const align_inst: Zir.Inst.Ref = if (!small.has_align) .none else blk: { + const align_inst = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + break :blk align_inst; + }; + try self.writeFlag(stream, ",is_const", small.is_const); + try self.writeFlag(stream, ",is_comptime", small.is_comptime); + try self.writeOptionalInstRef(stream, ",ty=", type_inst); + try self.writeOptionalInstRef(stream, ",align=", align_inst); + try stream.writeAll(")) "); + try self.writeSrc(stream, src); + } + fn writeBoolBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].bool_br; const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index); diff --git a/src/type.zig b/src/type.zig index cb2cc6d58a..2525aecef6 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3866,6 +3866,7 @@ pub const Type = extern union { }; pub const @"bool" = initTag(.bool); + pub const @"comptime_int" = initTag(.comptime_int); pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type { assert(d.host_size == 0 or d.bit_offset < d.host_size * 8); From 4e85bf11856a7fbf6072857f21a326854073b534 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 29 Sep 2021 10:46:32 -0700 Subject: [PATCH 150/160] stage2: put use_llvm and use_stage1 into the Module cache hash This prevents a compiler_rt built with stage2 (which is intentionally different than when built with stage1) from being used for stage1 and vice versa. Fixes the regression from the previous commit. --- src/Compilation.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Compilation.zig b/src/Compilation.zig index cb19a21c15..76a84701ce 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1177,6 +1177,8 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { } hash.add(valgrind); hash.add(single_threaded); + hash.add(use_stage1); + hash.add(use_llvm); hash.add(dll_export_fns); hash.add(options.is_test); hash.add(options.skip_linker_dependencies); From b02932f96e38732d05a6a759098cea785cb91506 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 29 Sep 2021 11:09:37 -0700 Subject: [PATCH 151/160] Sema: generic function instantiations gain addrspace of owner Decl --- src/Sema.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Sema.zig b/src/Sema.zig index 53d7a9f4a2..b71c4f1847 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3281,6 +3281,7 @@ fn analyzeCall( new_decl.is_exported = module_fn.owner_decl.is_exported; new_decl.has_align = module_fn.owner_decl.has_align; new_decl.has_linksection_or_addrspace = module_fn.owner_decl.has_linksection_or_addrspace; + new_decl.@"addrspace" = module_fn.owner_decl.@"addrspace"; new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; new_decl.alive = true; // This Decl is called at runtime. new_decl.has_tv = true; From 1d1f6a04214027da014cbc8eb780ff4c5e55f863 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 29 Sep 2021 11:33:22 -0700 Subject: [PATCH 152/160] move some behavior tests to the "passing for stage2" section --- test/behavior.zig | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/behavior.zig b/test/behavior.zig index 479e1feffc..1855ff5cf7 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -6,6 +6,15 @@ test { _ = @import("behavior/atomics.zig"); _ = @import("behavior/basic.zig"); _ = @import("behavior/bool.zig"); + _ = @import("behavior/bugs/655.zig"); + _ = @import("behavior/bugs/1277.zig"); + _ = @import("behavior/bugs/1741.zig"); + _ = @import("behavior/bugs/2346.zig"); + _ = @import("behavior/bugs/2692.zig"); + _ = @import("behavior/bugs/4769_a.zig"); + _ = @import("behavior/bugs/4769_b.zig"); + _ = @import("behavior/bugs/6850.zig"); + _ = @import("behavior/bugs/9584.zig"); _ = @import("behavior/cast.zig"); _ = @import("behavior/eval.zig"); _ = @import("behavior/generics.zig"); @@ -36,11 +45,21 @@ test { _ = @import("behavior/bit_shifting.zig"); _ = @import("behavior/bitcast.zig"); _ = @import("behavior/bitreverse.zig"); + _ = @import("behavior/bugs/394.zig"); + _ = @import("behavior/bugs/421.zig"); + _ = @import("behavior/bugs/529.zig"); + _ = @import("behavior/bugs/624.zig"); + _ = @import("behavior/bugs/656.zig"); + _ = @import("behavior/bugs/679.zig"); + _ = @import("behavior/bugs/704.zig"); + _ = @import("behavior/bugs/718.zig"); + _ = @import("behavior/bugs/726.zig"); + _ = @import("behavior/bugs/828.zig"); + _ = @import("behavior/bugs/920.zig"); _ = @import("behavior/bugs/1025.zig"); _ = @import("behavior/bugs/1076.zig"); _ = @import("behavior/bugs/1111.zig"); _ = @import("behavior/bugs/1120.zig"); - _ = @import("behavior/bugs/1277.zig"); _ = @import("behavior/bugs/1310.zig"); _ = @import("behavior/bugs/1322.zig"); _ = @import("behavior/bugs/1381.zig"); @@ -50,14 +69,11 @@ test { _ = @import("behavior/bugs/1500.zig"); _ = @import("behavior/bugs/1607.zig"); _ = @import("behavior/bugs/1735.zig"); - _ = @import("behavior/bugs/1741.zig"); _ = @import("behavior/bugs/1851.zig"); _ = @import("behavior/bugs/1914.zig"); _ = @import("behavior/bugs/2006.zig"); _ = @import("behavior/bugs/2114.zig"); - _ = @import("behavior/bugs/2346.zig"); _ = @import("behavior/bugs/2578.zig"); - _ = @import("behavior/bugs/2692.zig"); _ = @import("behavior/bugs/2889.zig"); _ = @import("behavior/bugs/3007.zig"); _ = @import("behavior/bugs/3046.zig"); @@ -69,8 +85,6 @@ test { _ = @import("behavior/bugs/3779.zig"); _ = @import("behavior/bugs/4328.zig"); _ = @import("behavior/bugs/4560.zig"); - _ = @import("behavior/bugs/4769_a.zig"); - _ = @import("behavior/bugs/4769_b.zig"); _ = @import("behavior/bugs/4954.zig"); _ = @import("behavior/bugs/5398.zig"); _ = @import("behavior/bugs/5413.zig"); @@ -78,24 +92,10 @@ test { _ = @import("behavior/bugs/5487.zig"); _ = @import("behavior/bugs/6456.zig"); _ = @import("behavior/bugs/6781.zig"); - _ = @import("behavior/bugs/6850.zig"); _ = @import("behavior/bugs/7027.zig"); _ = @import("behavior/bugs/7047.zig"); _ = @import("behavior/bugs/7003.zig"); _ = @import("behavior/bugs/7250.zig"); - _ = @import("behavior/bugs/9584.zig"); - _ = @import("behavior/bugs/394.zig"); - _ = @import("behavior/bugs/421.zig"); - _ = @import("behavior/bugs/529.zig"); - _ = @import("behavior/bugs/624.zig"); - _ = @import("behavior/bugs/655.zig"); - _ = @import("behavior/bugs/656.zig"); - _ = @import("behavior/bugs/679.zig"); - _ = @import("behavior/bugs/704.zig"); - _ = @import("behavior/bugs/718.zig"); - _ = @import("behavior/bugs/726.zig"); - _ = @import("behavior/bugs/828.zig"); - _ = @import("behavior/bugs/920.zig"); _ = @import("behavior/byteswap.zig"); _ = @import("behavior/byval_arg_var.zig"); _ = @import("behavior/call.zig"); From ea6706b6f406606a7523e35e34e390fb880b607e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 29 Sep 2021 14:04:52 -0700 Subject: [PATCH 153/160] stage2: LLVM backend: implement struct type fwd decls Makes struct types able to refer to themselves. --- src/Module.zig | 2 +- src/codegen/llvm.zig | 137 +++++++++++++++++++++++--------- src/codegen/llvm/bindings.zig | 17 +++- src/type.zig | 2 + test/behavior/struct.zig | 39 +++++++++ test/behavior/struct_stage1.zig | 39 --------- 6 files changed, 154 insertions(+), 82 deletions(-) diff --git a/src/Module.zig b/src/Module.zig index 83bbbb6366..6c790d3804 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -813,7 +813,7 @@ pub const Struct = struct { is_comptime: bool, }; - pub fn getFullyQualifiedName(s: *Struct, gpa: *Allocator) ![]u8 { + pub fn getFullyQualifiedName(s: *Struct, gpa: *Allocator) ![:0]u8 { return s.owner_decl.getFullyQualifiedName(gpa); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 2c278221ae..093ae4b8c8 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -164,9 +164,25 @@ pub const Object = struct { /// * it works for functions not all globals. /// Therefore, this table keeps track of the mapping. decl_map: std.AutoHashMapUnmanaged(*const Module.Decl, *const llvm.Value), + /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of + /// the compiler, but the Type/Value memory here is backed by `type_map_arena`. + /// TODO we need to remove entries from this map in response to incremental compilation + /// but I think the frontend won't tell us about types that get deleted because + /// hasCodeGenBits() is false for types. + type_map: TypeMap, + /// The backing memory for `type_map`. Periodically garbage collected after flush(). + /// The code for doing the periodical GC is not yet implemented. + type_map_arena: std.heap.ArenaAllocator, /// Where to put the output object file, relative to bin_file.options.emit directory. sub_path: []const u8, + pub const TypeMap = std.HashMapUnmanaged( + Type, + *const llvm.Type, + Type.HashContext64, + std.hash_map.default_max_load_percentage, + ); + pub fn create(gpa: *Allocator, sub_path: []const u8, options: link.Options) !*Object { const obj = try gpa.create(Object); errdefer gpa.destroy(obj); @@ -253,6 +269,8 @@ pub const Object = struct { .context = context, .target_machine = target_machine, .decl_map = .{}, + .type_map = .{}, + .type_map_arena = std.heap.ArenaAllocator.init(gpa), .sub_path = sub_path, }; } @@ -262,6 +280,8 @@ pub const Object = struct { self.llvm_module.dispose(); self.context.dispose(); self.decl_map.deinit(gpa); + self.type_map.deinit(gpa); + self.type_map_arena.deinit(); self.* = undefined; } @@ -725,10 +745,10 @@ pub const DeclGen = struct { } fn llvmType(self: *DeclGen, t: Type) error{ OutOfMemory, CodegenFail }!*const llvm.Type { + const gpa = self.gpa; log.debug("llvmType for {}", .{t}); switch (t.zigTypeTag()) { - .Void => return self.context.voidType(), - .NoReturn => return self.context.voidType(), + .Void, .NoReturn => return self.context.voidType(), .Int => { const info = t.intInfo(self.module.getTarget()); return self.context.intType(info.bits); @@ -799,18 +819,38 @@ pub const DeclGen = struct { return self.context.intType(16); }, .Struct => { + const gop = try self.object.type_map.getOrPut(gpa, t); + if (gop.found_existing) return gop.value_ptr.*; + + // The Type memory is ephemeral; since we want to store a longer-lived + // reference, we need to copy it here. + gop.key_ptr.* = try t.copy(&self.object.type_map_arena.allocator); + const struct_obj = t.castTag(.@"struct").?.data; assert(struct_obj.haveFieldTypes()); - const llvm_fields = try self.gpa.alloc(*const llvm.Type, struct_obj.fields.count()); - defer self.gpa.free(llvm_fields); - for (struct_obj.fields.values()) |field, i| { - llvm_fields[i] = try self.llvmType(field.ty); + + const name = try struct_obj.getFullyQualifiedName(gpa); + defer gpa.free(name); + + const llvm_struct_ty = self.context.structCreateNamed(name); + gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls + + var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{}; + try llvm_field_types.ensureTotalCapacity(gpa, struct_obj.fields.count()); + defer llvm_field_types.deinit(gpa); + + for (struct_obj.fields.values()) |field| { + if (!field.ty.hasCodeGenBits()) continue; + llvm_field_types.appendAssumeCapacity(try self.llvmType(field.ty)); } - return self.context.structType( - llvm_fields.ptr, - @intCast(c_uint, llvm_fields.len), - .False, + + llvm_struct_ty.structSetBody( + llvm_field_types.items.ptr, + @intCast(c_uint, llvm_field_types.items.len), + llvm.Bool.fromBool(struct_obj.layout == .Packed), ); + + return llvm_struct_ty; }, .Union => { const union_obj = t.castTag(.@"union").?.data; @@ -838,8 +878,8 @@ pub const DeclGen = struct { .Fn => { const ret_ty = try self.llvmType(t.fnReturnType()); const params_len = t.fnParamLen(); - const llvm_params = try self.gpa.alloc(*const llvm.Type, params_len); - defer self.gpa.free(llvm_params); + const llvm_params = try gpa.alloc(*const llvm.Type, params_len); + defer gpa.free(llvm_params); for (llvm_params) |*llvm_param, i| { llvm_param.* = try self.llvmType(t.fnParamType(i)); } @@ -1073,21 +1113,26 @@ pub const DeclGen = struct { return self.context.constStruct(&fields, fields.len, .False); }, .Struct => { - const fields_len = tv.ty.structFieldCount(); + const llvm_struct_ty = try self.llvmType(tv.ty); const field_vals = tv.val.castTag(.@"struct").?.data; const gpa = self.gpa; - const llvm_fields = try gpa.alloc(*const llvm.Value, fields_len); - defer gpa.free(llvm_fields); - for (llvm_fields) |*llvm_field, i| { - llvm_field.* = try self.genTypedValue(.{ - .ty = tv.ty.structFieldType(i), - .val = field_vals[i], - }); + + var llvm_fields: std.ArrayListUnmanaged(*const llvm.Value) = .{}; + try llvm_fields.ensureTotalCapacity(gpa, field_vals.len); + defer llvm_fields.deinit(gpa); + + for (field_vals) |field_val, i| { + const field_ty = tv.ty.structFieldType(i); + if (!field_ty.hasCodeGenBits()) continue; + + llvm_fields.appendAssumeCapacity(try self.genTypedValue(.{ + .ty = field_ty, + .val = field_val, + })); } - return self.context.constStruct( - llvm_fields.ptr, - @intCast(c_uint, llvm_fields.len), - .False, + return llvm_struct_ty.constNamedStruct( + llvm_fields.items.ptr, + @intCast(c_uint, llvm_fields.items.len), ); }, .ComptimeInt => unreachable, @@ -1692,13 +1737,15 @@ pub const FuncGen = struct { const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; const struct_ptr = try self.resolveInst(struct_field.struct_operand); const struct_ptr_ty = self.air.typeOf(struct_field.struct_operand); - const field_index = @intCast(c_uint, struct_field.field_index); - return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index); + return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, struct_field.field_index); } - fn airStructFieldPtrIndex(self: *FuncGen, inst: Air.Inst.Index, field_index: c_uint) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + fn airStructFieldPtrIndex( + self: *FuncGen, + inst: Air.Inst.Index, + field_index: u32, + ) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; const struct_ptr = try self.resolveInst(ty_op.operand); @@ -1707,13 +1754,13 @@ pub const FuncGen = struct { } fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + if (self.liveness.isUnused(inst)) return null; const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data; + const struct_ty = self.air.typeOf(struct_field.struct_operand); const struct_byval = try self.resolveInst(struct_field.struct_operand); - const field_index = @intCast(c_uint, struct_field.field_index); + const field_index = llvmFieldIndex(struct_ty, struct_field.field_index); return self.builder.buildExtractValue(struct_byval, field_index, ""); } @@ -2643,8 +2690,7 @@ pub const FuncGen = struct { const fill_char = if (val_is_undef) u8_llvm_ty.constInt(0xaa, .False) else value; const target = self.dg.module.getTarget(); const dest_ptr_align = ptr_ty.ptrAlignment(target); - const memset = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align); - memset.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr())); + _ = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align, ptr_ty.isVolatilePtr()); if (val_is_undef and self.dg.module.comp.bin_file.options.valgrind) { // TODO generate valgrind client request to mark byte range as undefined @@ -2667,14 +2713,14 @@ pub const FuncGen = struct { const src_ptr_u8 = self.builder.buildBitCast(src_ptr, ptr_u8_llvm_ty, ""); const is_volatile = src_ptr_ty.isVolatilePtr() or dest_ptr_ty.isVolatilePtr(); const target = self.dg.module.getTarget(); - const memcpy = self.builder.buildMemCpy( + _ = self.builder.buildMemCpy( dest_ptr_u8, dest_ptr_ty.ptrAlignment(target), src_ptr_u8, src_ptr_ty.ptrAlignment(target), len, + is_volatile, ); - memcpy.setVolatile(llvm.Bool.fromBool(is_volatile)); return null; } @@ -2741,11 +2787,14 @@ pub const FuncGen = struct { inst: Air.Inst.Index, struct_ptr: *const llvm.Value, struct_ptr_ty: Type, - field_index: c_uint, + field_index: u32, ) !?*const llvm.Value { const struct_ty = struct_ptr_ty.childType(); switch (struct_ty.zigTypeTag()) { - .Struct => return self.builder.buildStructGEP(struct_ptr, field_index, ""), + .Struct => { + const llvm_field_index = llvmFieldIndex(struct_ty, field_index); + return self.builder.buildStructGEP(struct_ptr, llvm_field_index, ""); + }, .Union => return self.unionFieldPtr(inst, struct_ptr, struct_ty, field_index), else => unreachable, } @@ -2968,3 +3017,15 @@ fn toLlvmAtomicRmwBinOp( .Min => if (is_signed) llvm.AtomicRMWBinOp.Min else return .UMin, }; } + +/// Take into account 0 bit fields. +fn llvmFieldIndex(ty: Type, index: u32) c_uint { + const struct_obj = ty.castTag(.@"struct").?.data; + var result: c_uint = 0; + for (struct_obj.fields.values()[0..index]) |field| { + if (field.ty.hasCodeGenBits()) { + result += 1; + } + } + return result; +} diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 68d91f6c68..8ded801165 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -181,6 +181,13 @@ pub const Type = opaque { pub const constArray = LLVMConstArray; extern fn LLVMConstArray(ElementTy: *const Type, ConstantVals: [*]*const Value, Length: c_uint) *const Value; + pub const constNamedStruct = LLVMConstNamedStruct; + extern fn LLVMConstNamedStruct( + StructTy: *const Type, + ConstantVals: [*]const *const Value, + Count: c_uint, + ) *const Value; + pub const getUndef = LLVMGetUndef; extern fn LLVMGetUndef(Ty: *const Type) *const Value; @@ -666,23 +673,25 @@ pub const Builder = opaque { Name: [*:0]const u8, ) *const Value; - pub const buildMemSet = LLVMBuildMemSet; - extern fn LLVMBuildMemSet( + pub const buildMemSet = ZigLLVMBuildMemSet; + extern fn ZigLLVMBuildMemSet( B: *const Builder, Ptr: *const Value, Val: *const Value, Len: *const Value, Align: c_uint, + is_volatile: bool, ) *const Value; - pub const buildMemCpy = LLVMBuildMemCpy; - extern fn LLVMBuildMemCpy( + pub const buildMemCpy = ZigLLVMBuildMemCpy; + extern fn ZigLLVMBuildMemCpy( B: *const Builder, Dst: *const Value, DstAlign: c_uint, Src: *const Value, SrcAlign: c_uint, Size: *const Value, + is_volatile: bool, ) *const Value; }; diff --git a/src/type.zig b/src/type.zig index 2525aecef6..cd7eb7a7d0 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1785,6 +1785,8 @@ pub const Type = extern union { if (is_packed) @panic("TODO packed structs"); var size: u64 = 0; for (s.fields.values()) |field| { + if (!field.ty.hasCodeGenBits()) continue; + const field_align = a: { if (field.abi_align.tag() == .abi_align_default) { break :a field.ty.abiAlignment(target); diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 2dde3c930d..d755c92c72 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -90,3 +90,42 @@ test "call member function directly" { const result = MemberFnTestFoo.member(instance); try expect(result == 1234); } + +test "struct point to self" { + var root: Node = undefined; + root.val.x = 1; + + var node: Node = undefined; + node.next = &root; + node.val.x = 2; + + root.next = &node; + + try expect(node.next.next.next.val.x == 1); +} + +test "void struct fields" { + const foo = VoidStructFieldsFoo{ + .a = void{}, + .b = 1, + .c = void{}, + }; + try expect(foo.b == 1); + try expect(@sizeOf(VoidStructFieldsFoo) == 4); +} +const VoidStructFieldsFoo = struct { + a: void, + b: i32, + c: void, +}; + +test "member functions" { + const r = MemberFnRand{ .seed = 1234 }; + try expect(r.getSeed() == 1234); +} +const MemberFnRand = struct { + seed: u32, + pub fn getSeed(r: *const MemberFnRand) u32 { + return r.seed; + } +}; diff --git a/test/behavior/struct_stage1.zig b/test/behavior/struct_stage1.zig index b5394afd50..3c4aaf58ec 100644 --- a/test/behavior/struct_stage1.zig +++ b/test/behavior/struct_stage1.zig @@ -16,21 +16,6 @@ test "top level fields" { try expectEqual(@as(i32, 1235), instance.top_level_field); } -test "void struct fields" { - const foo = VoidStructFieldsFoo{ - .a = void{}, - .b = 1, - .c = void{}, - }; - try expect(foo.b == 1); - try expect(@sizeOf(VoidStructFieldsFoo) == 4); -} -const VoidStructFieldsFoo = struct { - a: void, - b: i32, - c: void, -}; - const StructFoo = struct { a: i32, b: bool, @@ -46,19 +31,6 @@ const Val = struct { x: i32, }; -test "struct point to self" { - var root: Node = undefined; - root.val.x = 1; - - var node: Node = undefined; - node.next = &root; - node.val.x = 2; - - root.next = &node; - - try expect(node.next.next.next.val.x == 1); -} - test "fn call of struct field" { const Foo = struct { ptr: fn () i32, @@ -89,17 +61,6 @@ test "store member function in variable" { try expect(result == 1234); } -test "member functions" { - const r = MemberFnRand{ .seed = 1234 }; - try expect(r.getSeed() == 1234); -} -const MemberFnRand = struct { - seed: u32, - pub fn getSeed(r: *const MemberFnRand) u32 { - return r.seed; - } -}; - test "return struct byval from function" { const bar = makeBar2(1234, 5678); try expect(bar.y == 5678); From bdbedff910e18e18dc31db84a80607435e9b6ee0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 29 Sep 2021 15:33:45 -0700 Subject: [PATCH 154/160] stage2: LLVM backend: properly set module target data Also fix tripping LLVM assert having to do with 0 bit integers. stage2 behavior tests now run clean in a debug build of llvm 12. --- src/codegen/llvm.zig | 23 ++++++++++++++++------- src/codegen/llvm/bindings.zig | 11 +++++++++++ src/type.zig | 1 + 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 093ae4b8c8..a1e9f47df4 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -264,6 +264,11 @@ pub const Object = struct { ); errdefer target_machine.dispose(); + const target_data = target_machine.createTargetDataLayout(); + defer target_data.dispose(); + + llvm_module.setModuleDataLayout(target_data); + return Object{ .llvm_module = llvm_module, .context = context, @@ -1589,13 +1594,17 @@ pub const FuncGen = struct { return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const operand = try self.resolveInst(ty_op.operand); - const array_len = self.air.typeOf(ty_op.operand).elemType().arrayLen(); - const usize_llvm_ty = try self.dg.llvmType(Type.initTag(.usize)); - const len = usize_llvm_ty.constInt(array_len, .False); + const operand_ty = self.air.typeOf(ty_op.operand); + const array_ty = operand_ty.childType(); + const llvm_usize = try self.dg.llvmType(Type.usize); + const len = llvm_usize.constInt(array_ty.arrayLen(), .False); const slice_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst)); + if (!array_ty.hasCodeGenBits()) { + return self.builder.buildInsertValue(slice_llvm_ty.getUndef(), len, 1, ""); + } + const operand = try self.resolveInst(ty_op.operand); const indices: [2]*const llvm.Value = .{ - usize_llvm_ty.constNull(), usize_llvm_ty.constNull(), + llvm_usize.constNull(), llvm_usize.constNull(), }; const ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""); const partial = self.builder.buildInsertValue(slice_llvm_ty.getUndef(), ptr, 0, ""); @@ -2454,12 +2463,12 @@ pub const FuncGen = struct { } fn airAlloc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; + if (self.liveness.isUnused(inst)) return null; // buildAlloca expects the pointee type, not the pointer type, so assert that // a Payload.PointerSimple is passed to the alloc instruction. const ptr_ty = self.air.typeOfIndex(inst); const pointee_type = ptr_ty.elemType(); + if (!pointee_type.hasCodeGenBits()) return null; const pointee_llvm_ty = try self.dg.llvmType(pointee_type); const target = self.dg.module.getTarget(); const alloca_inst = self.buildAlloca(pointee_llvm_ty); diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 8ded801165..69845c0899 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -219,6 +219,9 @@ pub const Module = opaque { pub const verify = LLVMVerifyModule; extern fn LLVMVerifyModule(*const Module, Action: VerifierFailureAction, OutMessage: *[*:0]const u8) Bool; + pub const setModuleDataLayout = LLVMSetModuleDataLayout; + extern fn LLVMSetModuleDataLayout(*const Module, *const TargetData) void; + pub const addFunction = LLVMAddFunction; extern fn LLVMAddFunction(*const Module, Name: [*:0]const u8, FunctionTy: *const Type) *const Value; @@ -766,6 +769,14 @@ pub const TargetMachine = opaque { llvm_ir_filename: ?[*:0]const u8, bitcode_filename: ?[*:0]const u8, ) bool; + + pub const createTargetDataLayout = LLVMCreateTargetDataLayout; + extern fn LLVMCreateTargetDataLayout(*const TargetMachine) *const TargetData; +}; + +pub const TargetData = opaque { + pub const dispose = LLVMDisposeTargetData; + extern fn LLVMDisposeTargetData(*const TargetData) void; }; pub const CodeModel = enum(c_int) { diff --git a/src/type.zig b/src/type.zig index cd7eb7a7d0..dacde84167 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3868,6 +3868,7 @@ pub const Type = extern union { }; pub const @"bool" = initTag(.bool); + pub const @"usize" = initTag(.usize); pub const @"comptime_int" = initTag(.comptime_int); pub fn ptr(arena: *Allocator, d: Payload.Pointer.Data) !Type { From ba7f40c4302fe88fddc92b5b44365f74e00800ff Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 29 Sep 2021 15:37:34 -0700 Subject: [PATCH 155/160] stage2: fix ELF linking to include compiler_rt There was duplicated logic for whether to include compiler_rt in the linker line both in the frontend and in the linker backends. Now the logic is only in the frontend; the linker puts it on the linker line if the frontend provides it. Fixes the CI failures. --- src/link/Elf.zig | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/link/Elf.zig b/src/link/Elf.zig index cac475d445..a8efa8dab9 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1282,20 +1282,11 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { const gc_sections = self.base.options.gc_sections orelse !is_obj; const stack_size = self.base.options.stack_size_override orelse 16777216; const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os; - const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt) blk: { - // TODO: remove when stage2 can build compiler_rt.zig - if (!self.base.options.use_llvm) break :blk null; - - // In the case of build-obj we include the compiler-rt symbols directly alongside - // the symbols of the root source file, in the same compilation unit. - if (is_obj) break :blk null; - - if (is_exe_or_dyn_lib) { - break :blk comp.compiler_rt_static_lib.?.full_object_path; - } else { - break :blk comp.compiler_rt_obj.?.full_object_path; - } - } else null; + const compiler_rt_path: ?[]const u8 = blk: { + if (comp.compiler_rt_static_lib) |x| break :blk x.full_object_path; + if (comp.compiler_rt_obj) |x| break :blk x.full_object_path; + break :blk null; + }; // Here we want to determine whether we can save time by not invoking LLD when the // output is unchanged. None of the linker options or the object files that are being From 2ed9288246821c39ae75fa21998a53b34e713cd4 Mon Sep 17 00:00:00 2001 From: Matthew Borkowski Date: Thu, 30 Sep 2021 01:55:21 -0400 Subject: [PATCH 156/160] parse.zig: better c pointer prefix parsing, don't index out of bounds on eof --- lib/std/zig/parse.zig | 17 ++++++----------- lib/std/zig/parser_test.zig | 8 ++++++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 021b028455..a449f6ae0f 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1584,18 +1584,13 @@ const Parser = struct { _ = p.nextToken(); const asterisk = p.nextToken(); var sentinel: Node.Index = 0; - prefix: { - if (p.eatToken(.identifier)) |ident| { - const token_slice = p.source[p.token_starts[ident]..][0..2]; - if (!std.mem.eql(u8, token_slice, "c]")) { - p.tok_i -= 1; - } else { - break :prefix; - } - } - if (p.eatToken(.colon)) |_| { - sentinel = try p.expectExpr(); + if (p.eatToken(.identifier)) |ident| { + const ident_slice = p.source[p.token_starts[ident]..p.token_starts[ident + 1]]; + if (!std.mem.eql(u8, std.mem.trimRight(u8, ident_slice, &std.ascii.spaces), "c")) { + p.tok_i -= 1; } + } else if (p.eatToken(.colon)) |_| { + sentinel = try p.expectExpr(); } _ = try p.expectToken(.r_bracket); const mods = try p.parsePtrModifiers(); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 57f081decb..f69f0598dd 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -5259,6 +5259,14 @@ test "recovery: nonfinal varargs" { }); } +test "recovery: eof in c pointer" { + try testError( + \\const Ptr = [*c + , &[_]Error{ + .expected_token, + }); +} + const std = @import("std"); const mem = std.mem; const print = std.debug.print; From f87156e33c688effcf00b8fa9c2542391423ee78 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Wed, 29 Sep 2021 18:37:12 -0500 Subject: [PATCH 157/160] Add a panic handler to give better errors for crashes in sema --- lib/std/Thread/Mutex.zig | 177 ++++++------ src/Sema.zig | 6 + src/crash_report.zig | 581 +++++++++++++++++++++++++++++++++++++++ src/main.zig | 6 + src/print_zir.zig | 342 +++++++++++++---------- 5 files changed, 893 insertions(+), 219 deletions(-) create mode 100644 src/crash_report.zig diff --git a/lib/std/Thread/Mutex.zig b/lib/std/Thread/Mutex.zig index a337809a18..7473d9ec7f 100644 --- a/lib/std/Thread/Mutex.zig +++ b/lib/std/Thread/Mutex.zig @@ -33,17 +33,29 @@ const testing = std.testing; const StaticResetEvent = std.thread.StaticResetEvent; /// Try to acquire the mutex without blocking. Returns `null` if the mutex is -/// unavailable. Otherwise returns `Held`. Call `release` on `Held`. -pub fn tryAcquire(m: *Mutex) ?Impl.Held { +/// unavailable. Otherwise returns `Held`. Call `release` on `Held`, or use +/// releaseDirect(). +pub fn tryAcquire(m: *Mutex) ?Held { return m.impl.tryAcquire(); } /// Acquire the mutex. Deadlocks if the mutex is already /// held by the calling thread. -pub fn acquire(m: *Mutex) Impl.Held { +pub fn acquire(m: *Mutex) Held { return m.impl.acquire(); } +/// Release the mutex. Prefer Held.release() if available. +pub fn releaseDirect(m: *Mutex) void { + return m.impl.releaseDirect(); +} + +/// A held mutex handle. Call release to allow other threads to +/// take the mutex. Do not call release() more than once. +/// For more complex scenarios, this handle can be discarded +/// and Mutex.releaseDirect can be called instead. +pub const Held = Impl.Held; + const Impl = if (builtin.single_threaded) Dummy else if (builtin.os.tag == .windows) @@ -53,6 +65,32 @@ else if (std.Thread.use_pthreads) else AtomicMutex; +fn HeldInterface(comptime MutexType: type) type { + return struct { + const Mixin = @This(); + pub const Held = struct { + mutex: *MutexType, + + pub fn release(held: Mixin.Held) void { + held.mutex.releaseDirect(); + } + }; + + pub fn tryAcquire(m: *MutexType) ?Mixin.Held { + if (m.tryAcquireDirect()) { + return Mixin.Held{ .mutex = m }; + } else { + return null; + } + } + + pub fn acquire(m: *MutexType) Mixin.Held { + m.acquireDirect(); + return Mixin.Held{ .mutex = m }; + } + }; +} + pub const AtomicMutex = struct { state: State = .unlocked, @@ -62,39 +100,32 @@ pub const AtomicMutex = struct { waiting, }; - pub const Held = struct { - mutex: *AtomicMutex, + pub usingnamespace HeldInterface(@This()); - pub fn release(held: Held) void { - switch (@atomicRmw(State, &held.mutex.state, .Xchg, .unlocked, .Release)) { - .unlocked => unreachable, - .locked => {}, - .waiting => held.mutex.unlockSlow(), - } - } - }; - - pub fn tryAcquire(m: *AtomicMutex) ?Held { - if (@cmpxchgStrong( + fn tryAcquireDirect(m: *AtomicMutex) bool { + return @cmpxchgStrong( State, &m.state, .unlocked, .locked, .Acquire, .Monotonic, - ) == null) { - return Held{ .mutex = m }; - } else { - return null; - } + ) == null; } - pub fn acquire(m: *AtomicMutex) Held { + fn acquireDirect(m: *AtomicMutex) void { switch (@atomicRmw(State, &m.state, .Xchg, .locked, .Acquire)) { .unlocked => {}, else => |s| m.lockSlow(s), } - return Held{ .mutex = m }; + } + + fn releaseDirect(m: *AtomicMutex) void { + switch (@atomicRmw(State, &m.state, .Xchg, .unlocked, .Release)) { + .unlocked => unreachable, + .locked => {}, + .waiting => m.unlockSlow(), + } } fn lockSlow(m: *AtomicMutex, current_state: State) void { @@ -171,36 +202,20 @@ pub const AtomicMutex = struct { pub const PthreadMutex = struct { pthread_mutex: std.c.pthread_mutex_t = .{}, - pub const Held = struct { - mutex: *PthreadMutex, + pub usingnamespace HeldInterface(@This()); - pub fn release(held: Held) void { - switch (std.c.pthread_mutex_unlock(&held.mutex.pthread_mutex)) { - .SUCCESS => return, - .INVAL => unreachable, - .AGAIN => unreachable, - .PERM => unreachable, - else => unreachable, - } - } - }; - - /// Try to acquire the mutex without blocking. Returns null if - /// the mutex is unavailable. Otherwise returns Held. Call - /// release on Held. - pub fn tryAcquire(m: *PthreadMutex) ?Held { - if (std.c.pthread_mutex_trylock(&m.pthread_mutex) == .SUCCESS) { - return Held{ .mutex = m }; - } else { - return null; - } + /// Try to acquire the mutex without blocking. Returns true if + /// the mutex is unavailable. Otherwise returns false. Call + /// release when done. + fn tryAcquireDirect(m: *PthreadMutex) bool { + return std.c.pthread_mutex_trylock(&m.pthread_mutex) == .SUCCESS; } /// Acquire the mutex. Will deadlock if the mutex is already /// held by the calling thread. - pub fn acquire(m: *PthreadMutex) Held { + fn acquireDirect(m: *PthreadMutex) void { switch (std.c.pthread_mutex_lock(&m.pthread_mutex)) { - .SUCCESS => return Held{ .mutex = m }, + .SUCCESS => {}, .INVAL => unreachable, .BUSY => unreachable, .AGAIN => unreachable, @@ -209,6 +224,16 @@ pub const PthreadMutex = struct { else => unreachable, } } + + fn releaseDirect(m: *PthreadMutex) void { + switch (std.c.pthread_mutex_unlock(&m.pthread_mutex)) { + .SUCCESS => return, + .INVAL => unreachable, + .AGAIN => unreachable, + .PERM => unreachable, + else => unreachable, + } + } }; /// This has the sematics as `Mutex`, however it does not actually do any @@ -216,58 +241,50 @@ pub const PthreadMutex = struct { pub const Dummy = struct { lock: @TypeOf(lock_init) = lock_init, + pub usingnamespace HeldInterface(@This()); + const lock_init = if (std.debug.runtime_safety) false else {}; - pub const Held = struct { - mutex: *Dummy, - - pub fn release(held: Held) void { - if (std.debug.runtime_safety) { - held.mutex.lock = false; - } - } - }; - - /// Try to acquire the mutex without blocking. Returns null if - /// the mutex is unavailable. Otherwise returns Held. Call - /// release on Held. - pub fn tryAcquire(m: *Dummy) ?Held { + /// Try to acquire the mutex without blocking. Returns false if + /// the mutex is unavailable. Otherwise returns true. + fn tryAcquireDirect(m: *Dummy) bool { if (std.debug.runtime_safety) { - if (m.lock) return null; + if (m.lock) return false; m.lock = true; } - return Held{ .mutex = m }; + return true; } /// Acquire the mutex. Will deadlock if the mutex is already /// held by the calling thread. - pub fn acquire(m: *Dummy) Held { - return m.tryAcquire() orelse @panic("deadlock detected"); + fn acquireDirect(m: *Dummy) void { + if (!m.tryAcquireDirect()) { + @panic("deadlock detected"); + } + } + + fn releaseDirect(m: *Dummy) void { + if (std.debug.runtime_safety) { + m.lock = false; + } } }; const WindowsMutex = struct { srwlock: windows.SRWLOCK = windows.SRWLOCK_INIT, - pub const Held = struct { - mutex: *WindowsMutex, + pub usingnamespace HeldInterface(@This()); - pub fn release(held: Held) void { - windows.kernel32.ReleaseSRWLockExclusive(&held.mutex.srwlock); - } - }; - - pub fn tryAcquire(m: *WindowsMutex) ?Held { - if (windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE) { - return Held{ .mutex = m }; - } else { - return null; - } + fn tryAcquireDirect(m: *WindowsMutex) bool { + return windows.kernel32.TryAcquireSRWLockExclusive(&m.srwlock) != windows.FALSE; } - pub fn acquire(m: *WindowsMutex) Held { + fn acquireDirect(m: *WindowsMutex) void { windows.kernel32.AcquireSRWLockExclusive(&m.srwlock); - return Held{ .mutex = m }; + } + + fn releaseDirect(m: *WindowsMutex) void { + windows.kernel32.ReleaseSRWLockExclusive(&m.srwlock); } }; diff --git a/src/Sema.zig b/src/Sema.zig index b71c4f1847..f9f0160064 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -90,6 +90,7 @@ const LazySrcLoc = Module.LazySrcLoc; const RangeSet = @import("RangeSet.zig"); const target_util = @import("target.zig"); const Package = @import("Package.zig"); +const crash_report = @import("crash_report.zig"); pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref); @@ -153,11 +154,16 @@ pub fn analyzeBody( var orig_captures: usize = parent_capture_scope.captures.count(); + var crash_info = crash_report.prepAnalyzeBody(sema, block, body); + crash_info.push(); + defer crash_info.pop(); + // We use a while(true) loop here to avoid a redundant way of breaking out of // the loop. The only way to break out of the loop is with a `noreturn` // instruction. var i: usize = 0; const result = while (true) { + crash_info.setBodyIndex(i); const inst = body[i]; const air_inst: Air.Inst.Ref = switch (tags[inst]) { // zig fmt: off diff --git a/src/crash_report.zig b/src/crash_report.zig new file mode 100644 index 0000000000..84f4b8db84 --- /dev/null +++ b/src/crash_report.zig @@ -0,0 +1,581 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const debug = std.debug; +const os = std.os; +const io = std.io; +const print_zir = @import("print_zir.zig"); + +const Module = @import("Module.zig"); +const Sema = @import("Sema.zig"); +const Zir = @import("Zir.zig"); + +pub const is_enabled = builtin.mode == .Debug; + +/// To use these crash report diagnostics, publish these symbols in your main file. +/// You will also need to call initialize() on startup, preferably as the very first operation in your program. +pub const root_decls = struct { + pub const panic = if (is_enabled) compilerPanic else std.builtin.default_panic; + pub const enable_segfault_handler = if (is_enabled) false else debug.default_enable_segfault_handler; +}; + +/// Install signal handlers to identify crashes and report diagnostics. +pub fn initialize() void { + if (is_enabled and debug.have_segfault_handling_support) { + attachSegfaultHandler(); + } +} + +fn En(comptime T: type) type { + return if (is_enabled) T else void; +} + +fn en(val: anytype) En(@TypeOf(val)) { + return if (is_enabled) val else {}; +} + +pub const AnalyzeBody = struct { + parent: if (is_enabled) ?*AnalyzeBody else void, + sema: En(*Sema), + block: En(*Module.Scope.Block), + body: En([]const Zir.Inst.Index), + body_index: En(usize), + + pub fn push(self: *@This()) void { + if (!is_enabled) return; + const head = &zir_state; + debug.assert(self.parent == null); + self.parent = head.*; + head.* = self; + } + + pub fn pop(self: *@This()) void { + if (!is_enabled) return; + const head = &zir_state; + const old = head.*.?; + debug.assert(old == self); + head.* = old.parent; + } + + pub fn setBodyIndex(self: *@This(), index: usize) void { + if (!is_enabled) return; + self.body_index = index; + } +}; + +threadlocal var zir_state: ?*AnalyzeBody = if (is_enabled) null else @compileError("Cannot use zir_state if crash_report is disabled."); + +pub fn prepAnalyzeBody(sema: *Sema, block: *Module.Scope.Block, body: []const Zir.Inst.Index) AnalyzeBody { + if (is_enabled) { + return .{ + .parent = null, + .sema = sema, + .block = block, + .body = body, + .body_index = 0, + }; + } else { + if (@sizeOf(AnalyzeBody) != 0) + @compileError("AnalyzeBody must have zero size when crash reports are disabled"); + return undefined; + } +} + +fn dumpStatusReport() !void { + const anal = zir_state orelse return; + // Note: We have the panic mutex here, so we can safely use the global crash heap. + var fba = std.heap.FixedBufferAllocator.init(&crash_heap); + const allocator = &fba.allocator; + + const stderr = io.getStdErr().writer(); + const block: *Scope.Block = anal.block; + + try stderr.writeAll("Analyzing "); + try writeFullyQualifiedDeclWithFile(block.src_decl, stderr); + try stderr.writeAll("\n"); + + print_zir.renderInstructionContext( + allocator, + anal.body, + anal.body_index, + block.src_decl.getFileScope(), + block.src_decl.src_node, + 6, // indent + stderr, + ) catch |err| switch (err) { + error.OutOfMemory => try stderr.writeAll(" \n"), + else => |e| return e, + }; + try stderr.writeAll(" For full context, use the command\n zig ast-check -t "); + try writeFilePath(block.src_decl.getFileScope(), stderr); + try stderr.writeAll("\n\n"); + + var parent = anal.parent; + while (parent) |curr| { + fba.reset(); + try stderr.writeAll(" in "); + try writeFullyQualifiedDeclWithFile(curr.block.src_decl, stderr); + try stderr.writeAll("\n > "); + print_zir.renderSingleInstruction( + allocator, + curr.body[curr.body_index], + curr.block.src_decl.getFileScope(), + curr.block.src_decl.src_node, + 6, // indent + stderr, + ) catch |err| switch (err) { + error.OutOfMemory => try stderr.writeAll(" \n"), + else => |e| return e, + }; + try stderr.writeAll("\n"); + + parent = curr.parent; + } + + try stderr.writeAll("\n"); +} + +const Scope = Module.Scope; +const Decl = Module.Decl; + +var crash_heap: [16 * 4096]u8 = undefined; + +fn writeFilePath(file: *Scope.File, stream: anytype) !void { + if (file.pkg.root_src_directory.path) |path| { + try stream.writeAll(path); + try stream.writeAll(std.fs.path.sep_str); + } + try stream.writeAll(file.sub_file_path); +} + +fn writeFullyQualifiedDeclWithFile(decl: *Decl, stream: anytype) !void { + try writeFilePath(decl.getFileScope(), stream); + try stream.writeAll(": "); + try decl.namespace.renderFullyQualifiedName(std.mem.sliceTo(decl.name, 0), stream); +} + +fn compilerPanic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn { + PanicSwitch.preDispatch(); + @setCold(true); + const ret_addr = @returnAddress(); + const stack_ctx: StackContext = .{ .current = .{ .ret_addr = ret_addr } }; + PanicSwitch.dispatch(error_return_trace, stack_ctx, msg); +} + +/// Attaches a global SIGSEGV handler +pub fn attachSegfaultHandler() void { + if (!debug.have_segfault_handling_support) { + @compileError("segfault handler not supported for this target"); + } + if (builtin.os.tag == .windows) { + _ = os.windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows); + return; + } + var act = os.Sigaction{ + .handler = .{ .sigaction = handleSegfaultLinux }, + .mask = os.empty_sigset, + .flags = (os.SA.SIGINFO | os.SA.RESTART | os.SA.RESETHAND), + }; + + os.sigaction(os.SIG.SEGV, &act, null); + os.sigaction(os.SIG.ILL, &act, null); + os.sigaction(os.SIG.BUS, &act, null); +} + +fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: ?*const c_void) callconv(.C) noreturn { + // TODO: use alarm() here to prevent infinite loops + PanicSwitch.preDispatch(); + + const addr = switch (builtin.os.tag) { + .linux => @ptrToInt(info.fields.sigfault.addr), + .freebsd => @ptrToInt(info.addr), + .netbsd => @ptrToInt(info.info.reason.fault.addr), + .openbsd => @ptrToInt(info.data.fault.addr), + .solaris => @ptrToInt(info.reason.fault.addr), + else => @compileError("TODO implement handleSegfaultLinux for new linux OS"), + }; + + var err_buffer: [128]u8 = undefined; + const error_msg = switch (sig) { + os.SIG.SEGV => std.fmt.bufPrint(&err_buffer, "Segmentation fault at address 0x{x}", .{addr}) catch "Segmentation fault", + os.SIG.ILL => std.fmt.bufPrint(&err_buffer, "Illegal instruction at address 0x{x}", .{addr}) catch "Illegal instruction", + os.SIG.BUS => std.fmt.bufPrint(&err_buffer, "Bus error at address 0x{x}", .{addr}) catch "Bus error", + else => std.fmt.bufPrint(&err_buffer, "Unknown error (signal {}) at address 0x{x}", .{ sig, addr }) catch "Unknown error", + }; + + const stack_ctx: StackContext = switch (builtin.cpu.arch) { + .i386 => ctx: { + const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); + const ip = @intCast(usize, ctx.mcontext.gregs[os.REG.EIP]); + const bp = @intCast(usize, ctx.mcontext.gregs[os.REG.EBP]); + break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } }; + }, + .x86_64 => ctx: { + const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); + const ip = switch (builtin.os.tag) { + .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RIP]), + .freebsd => @intCast(usize, ctx.mcontext.rip), + .openbsd => @intCast(usize, ctx.sc_rip), + else => unreachable, + }; + const bp = switch (builtin.os.tag) { + .linux, .netbsd, .solaris => @intCast(usize, ctx.mcontext.gregs[os.REG.RBP]), + .openbsd => @intCast(usize, ctx.sc_rbp), + .freebsd => @intCast(usize, ctx.mcontext.rbp), + else => unreachable, + }; + break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } }; + }, + .arm => ctx: { + const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); + const ip = @intCast(usize, ctx.mcontext.arm_pc); + const bp = @intCast(usize, ctx.mcontext.arm_fp); + break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } }; + }, + .aarch64 => ctx: { + const ctx = @ptrCast(*const os.ucontext_t, @alignCast(@alignOf(os.ucontext_t), ctx_ptr)); + const ip = @intCast(usize, ctx.mcontext.pc); + // x29 is the ABI-designated frame pointer + const bp = @intCast(usize, ctx.mcontext.regs[29]); + break :ctx StackContext{ .exception = .{ .bp = bp, .ip = ip } }; + }, + else => .not_supported, + }; + + PanicSwitch.dispatch(null, stack_ctx, error_msg); +} + +const WindowsSegfaultMessage = union(enum) { + literal: []const u8, + segfault: void, + illegal_instruction: void, +}; + +fn handleSegfaultWindows(info: *os.windows.EXCEPTION_POINTERS) callconv(os.windows.WINAPI) c_long { + switch (info.ExceptionRecord.ExceptionCode) { + os.windows.EXCEPTION_DATATYPE_MISALIGNMENT => handleSegfaultWindowsExtra(info, .{ .literal = "Unaligned Memory Access" }), + os.windows.EXCEPTION_ACCESS_VIOLATION => handleSegfaultWindowsExtra(info, .segfault), + os.windows.EXCEPTION_ILLEGAL_INSTRUCTION => handleSegfaultWindowsExtra(info, .illegal_instruction), + os.windows.EXCEPTION_STACK_OVERFLOW => handleSegfaultWindowsExtra(info, .{ .literal = "Stack Overflow" }), + else => return os.windows.EXCEPTION_CONTINUE_SEARCH, + } +} + +fn handleSegfaultWindowsExtra(info: *os.windows.EXCEPTION_POINTERS, comptime msg: WindowsSegfaultMessage) noreturn { + PanicSwitch.preDispatch(); + + const stack_ctx = if (@hasDecl(os.windows, "CONTEXT")) ctx: { + const regs = info.ContextRecord.getRegs(); + break :ctx StackContext{ .exception = .{ .bp = regs.bp, .ip = regs.ip } }; + } else ctx: { + const addr = @ptrToInt(info.ExceptionRecord.ExceptionAddress); + break :ctx StackContext{ .current = .{ .ret_addr = addr } }; + }; + + switch (msg) { + .literal => |err| PanicSwitch.dispatch(null, stack_ctx, err), + .segfault => { + const format_item = "Segmentation fault at address 0x{x}"; + var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large + const to_print = std.fmt.bufPrint(&buf, format_item, .{info.ExceptionRecord.ExceptionInformation[1]}) catch unreachable; + PanicSwitch.dispatch(null, stack_ctx, to_print); + }, + .illegal_instruction => { + const ip: ?usize = switch (stack_ctx) { + .exception => |ex| ex.ip, + .current => |cur| cur.ret_addr, + .not_supported => null, + }; + + if (ip) |addr| { + const format_item = "Illegal instruction at address 0x{x}"; + var buf: [format_item.len + 32]u8 = undefined; // 32 is arbitrary, but sufficiently large + const to_print = std.fmt.bufPrint(&buf, format_item, .{addr}) catch unreachable; + PanicSwitch.dispatch(null, stack_ctx, to_print); + } else { + PanicSwitch.dispatch(null, stack_ctx, "Illegal Instruction"); + } + }, + } +} + +const StackContext = union(enum) { + current: struct { + ret_addr: ?usize, + }, + exception: struct { + bp: usize, + ip: usize, + }, + not_supported: void, + + pub fn dumpStackTrace(ctx: @This()) void { + switch (ctx) { + .current => |ct| { + debug.dumpCurrentStackTrace(ct.ret_addr); + }, + .exception => |ex| { + debug.dumpStackTraceFromBase(ex.bp, ex.ip); + }, + .not_supported => { + const stderr = io.getStdErr().writer(); + stderr.writeAll("Stack trace not supported on this platform.\n") catch {}; + }, + } + } +}; + +const PanicSwitch = struct { + const RecoverStage = enum { + initialize, + report_stack, + release_mutex, + release_ref_count, + abort, + silent_abort, + }; + + const RecoverVerbosity = enum { + message_and_stack, + message_only, + silent, + }; + + const PanicState = struct { + recover_stage: RecoverStage = .initialize, + recover_verbosity: RecoverVerbosity = .message_and_stack, + panic_ctx: StackContext = undefined, + panic_trace: ?*const std.builtin.StackTrace = null, + awaiting_dispatch: bool = false, + }; + + /// Counter for the number of threads currently panicking. + /// Updated atomically before taking the panic_mutex. + /// In recoverable cases, the program will not abort + /// until all panicking threads have dumped their traces. + var panicking: u8 = 0; + + // Locked to avoid interleaving panic messages from multiple threads. + var panic_mutex = std.Thread.Mutex{}; + + /// Tracks the state of the current panic. If the code within the + /// panic triggers a secondary panic, this allows us to recover. + threadlocal var panic_state_raw: PanicState = .{}; + + /// The segfault handlers above need to do some work before they can dispatch + /// this switch. Calling preDispatch() first makes that work fault tolerant. + pub fn preDispatch() void { + // TODO: We want segfaults to trigger the panic recursively here, + // but if there is a segfault accessing this TLS slot it will cause an + // infinite loop. We should use `alarm()` to prevent the infinite + // loop and maybe also use a non-thread-local global to detect if + // it's happening and print a message. + var panic_state: *volatile PanicState = &panic_state_raw; + if (panic_state.awaiting_dispatch) { + dispatch(null, .{ .current = .{ .ret_addr = null } }, "Panic while preparing callstack"); + } + panic_state.awaiting_dispatch = true; + } + + /// This is the entry point to a panic-tolerant panic handler. + /// preDispatch() *MUST* be called exactly once before calling this. + /// A threadlocal "recover_stage" is updated throughout the process. + /// If a panic happens during the panic, the recover_stage will be + /// used to select a recover* function to call to resume the panic. + /// The recover_verbosity field is used to handle panics while reporting + /// panics within panics. If the panic handler triggers a panic, it will + /// attempt to log an additional stack trace for the secondary panic. If + /// that panics, it will fall back to just logging the panic message. If + /// it can't even do that witout panicing, it will recover without logging + /// anything about the internal panic. Depending on the state, "recover" + /// here may just mean "call abort". + pub fn dispatch( + trace: ?*const std.builtin.StackTrace, + stack_ctx: StackContext, + msg: []const u8, + ) noreturn { + var panic_state: *volatile PanicState = &panic_state_raw; + debug.assert(panic_state.awaiting_dispatch); + panic_state.awaiting_dispatch = false; + nosuspend switch (panic_state.recover_stage) { + .initialize => goTo(initPanic, .{ panic_state, trace, stack_ctx, msg }), + .report_stack => goTo(recoverReportStack, .{ panic_state, trace, stack_ctx, msg }), + .release_mutex => goTo(recoverReleaseMutex, .{ panic_state, trace, stack_ctx, msg }), + .release_ref_count => goTo(recoverReleaseRefCount, .{ panic_state, trace, stack_ctx, msg }), + .abort => goTo(recoverAbort, .{ panic_state, trace, stack_ctx, msg }), + .silent_abort => goTo(abort, .{}), + }; + } + + noinline fn initPanic( + state: *volatile PanicState, + trace: ?*const std.builtin.StackTrace, + stack: StackContext, + msg: []const u8, + ) noreturn { + // use a temporary so there's only one volatile store + const new_state = PanicState{ + .recover_stage = .abort, + .panic_ctx = stack, + .panic_trace = trace, + }; + state.* = new_state; + + _ = @atomicRmw(u8, &panicking, .Add, 1, .SeqCst); + + state.recover_stage = .release_ref_count; + + _ = panic_mutex.acquire(); + + state.recover_stage = .release_mutex; + + const stderr = io.getStdErr().writer(); + if (builtin.single_threaded) { + stderr.print("panic: ", .{}) catch goTo(releaseMutex, .{state}); + } else { + const current_thread_id = std.Thread.getCurrentId(); + stderr.print("thread {} panic: ", .{current_thread_id}) catch goTo(releaseMutex, .{state}); + } + stderr.print("{s}\n", .{msg}) catch goTo(releaseMutex, .{state}); + + state.recover_stage = .report_stack; + + dumpStatusReport() catch |err| { + stderr.print("\nIntercepted error.{} while dumping current state. Continuing...\n", .{err}) catch {}; + }; + + goTo(reportStack, .{state}); + } + + noinline fn recoverReportStack( + state: *volatile PanicState, + trace: ?*const std.builtin.StackTrace, + stack: StackContext, + msg: []const u8, + ) noreturn { + recover(state, trace, stack, msg); + + state.recover_stage = .release_mutex; + const stderr = io.getStdErr().writer(); + stderr.writeAll("\nOriginal Error:\n") catch {}; + goTo(reportStack, .{state}); + } + + noinline fn reportStack(state: *volatile PanicState) noreturn { + state.recover_stage = .release_mutex; + + if (state.panic_trace) |t| { + debug.dumpStackTrace(t.*); + } + state.panic_ctx.dumpStackTrace(); + + goTo(releaseMutex, .{state}); + } + + noinline fn recoverReleaseMutex( + state: *volatile PanicState, + trace: ?*const std.builtin.StackTrace, + stack: StackContext, + msg: []const u8, + ) noreturn { + recover(state, trace, stack, msg); + goTo(releaseMutex, .{state}); + } + + noinline fn releaseMutex(state: *volatile PanicState) noreturn { + state.recover_stage = .abort; + + panic_mutex.releaseDirect(); + + goTo(releaseRefCount, .{state}); + } + + noinline fn recoverReleaseRefCount( + state: *volatile PanicState, + trace: ?*const std.builtin.StackTrace, + stack: StackContext, + msg: []const u8, + ) noreturn { + recover(state, trace, stack, msg); + goTo(releaseRefCount, .{state}); + } + + noinline fn releaseRefCount(state: *volatile PanicState) noreturn { + state.recover_stage = .abort; + + if (@atomicRmw(u8, &panicking, .Sub, 1, .SeqCst) != 1) { + // Another thread is panicking, wait for the last one to finish + // and call abort() + + // Sleep forever without hammering the CPU + var event: std.Thread.StaticResetEvent = .{}; + event.wait(); + // This should be unreachable, recurse into recoverAbort. + @panic("event.wait() returned"); + } + + goTo(abort, .{}); + } + + noinline fn recoverAbort( + state: *volatile PanicState, + trace: ?*const std.builtin.StackTrace, + stack: StackContext, + msg: []const u8, + ) noreturn { + recover(state, trace, stack, msg); + + state.recover_stage = .silent_abort; + const stderr = io.getStdErr().writer(); + stderr.writeAll("Aborting...\n") catch {}; + goTo(abort, .{}); + } + + noinline fn abort() noreturn { + os.abort(); + } + + inline fn goTo(comptime func: anytype, args: anytype) noreturn { + // TODO: Tailcall is broken right now, but eventually this should be used + // to avoid blowing up the stack. It's ok for now though, there are no + // cycles in the state machine so the max stack usage is bounded. + //@call(.{.modifier = .always_tail}, func, args); + @call(.{}, func, args); + } + + fn recover( + state: *volatile PanicState, + trace: ?*const std.builtin.StackTrace, + stack: StackContext, + msg: []const u8, + ) void { + switch (state.recover_verbosity) { + .message_and_stack => { + // lower the verbosity, and restore it at the end if we don't panic. + state.recover_verbosity = .message_only; + + const stderr = io.getStdErr().writer(); + stderr.writeAll("\nPanicked during a panic: ") catch {}; + stderr.writeAll(msg) catch {}; + stderr.writeAll("\nInner panic stack:\n") catch {}; + if (trace) |t| { + debug.dumpStackTrace(t.*); + } + stack.dumpStackTrace(); + + state.recover_verbosity = .message_and_stack; + }, + .message_only => { + state.recover_verbosity = .silent; + + const stderr = io.getStdErr().writer(); + stderr.writeAll("\nPanicked while dumping inner panic stack: ") catch {}; + stderr.writeAll(msg) catch {}; + stderr.writeAll("\n") catch {}; + + // If we succeed, restore all the way to dumping the stack. + state.recover_verbosity = .message_and_stack; + }, + .silent => {}, + } + } +}; diff --git a/src/main.zig b/src/main.zig index 213b9506e7..e0be4b6021 100644 --- a/src/main.zig +++ b/src/main.zig @@ -20,6 +20,10 @@ const translate_c = @import("translate_c.zig"); const Cache = @import("Cache.zig"); const target_util = @import("target.zig"); const ThreadPool = @import("ThreadPool.zig"); +const crash_report = @import("crash_report.zig"); + +// Crash report needs to override the panic handler and other root decls +pub usingnamespace crash_report.root_decls; pub fn fatal(comptime format: []const u8, args: anytype) noreturn { std.log.emerg(format, args); @@ -134,6 +138,8 @@ var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{ }){}; pub fn main() anyerror!void { + crash_report.initialize(); + var gpa_need_deinit = false; const gpa = gpa: { if (!std.builtin.link_libc) { diff --git a/src/print_zir.zig b/src/print_zir.zig index 54fd7e632f..1d97f792e6 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -24,15 +24,20 @@ pub fn renderAsTextToFile( .code = scope_file.zir, .indent = 0, .parent_decl_node = 0, + .recurse_decls = true, + .recurse_blocks = true, }; + var raw_stream = std.io.bufferedWriter(fs_file.writer()); + const stream = raw_stream.writer(); + const main_struct_inst = Zir.main_struct_inst; - try fs_file.writer().print("%{d} ", .{main_struct_inst}); - try writer.writeInstToStream(fs_file.writer(), main_struct_inst); - try fs_file.writeAll("\n"); + try stream.print("%{d} ", .{main_struct_inst}); + try writer.writeInstToStream(stream, main_struct_inst); + try stream.writeAll("\n"); const imports_index = scope_file.zir.extra[@enumToInt(Zir.ExtraIndex.imports)]; if (imports_index != 0) { - try fs_file.writeAll("Imports:\n"); + try stream.writeAll("Imports:\n"); const extra = scope_file.zir.extraData(Zir.Inst.Imports, imports_index); var import_i: u32 = 0; @@ -44,13 +49,74 @@ pub fn renderAsTextToFile( const src: LazySrcLoc = .{ .token_abs = item.data.token }; const import_path = scope_file.zir.nullTerminatedString(item.data.name); - try fs_file.writer().print(" @import(\"{}\") ", .{ + try stream.print(" @import(\"{}\") ", .{ std.zig.fmtEscapes(import_path), }); - try writer.writeSrc(fs_file.writer(), src); - try fs_file.writer().writeAll("\n"); + try writer.writeSrc(stream, src); + try stream.writeAll("\n"); } } + + try raw_stream.flush(); +} + +pub fn renderInstructionContext( + gpa: *Allocator, + block: []const Zir.Inst.Index, + block_index: usize, + scope_file: *Module.Scope.File, + parent_decl_node: Ast.Node.Index, + indent: u32, + stream: anytype, +) !void { + var arena = std.heap.ArenaAllocator.init(gpa); + defer arena.deinit(); + + var writer: Writer = .{ + .gpa = gpa, + .arena = &arena.allocator, + .file = scope_file, + .code = scope_file.zir, + .indent = if (indent < 2) 2 else indent, + .parent_decl_node = parent_decl_node, + .recurse_decls = false, + .recurse_blocks = true, + }; + + try writer.writeBody(stream, block[0..block_index]); + try stream.writeByteNTimes(' ', writer.indent - 2); + try stream.print("> %{d} ", .{block[block_index]}); + try writer.writeInstToStream(stream, block[block_index]); + try stream.writeByte('\n'); + if (block_index + 1 < block.len) { + try writer.writeBody(stream, block[block_index + 1 ..]); + } +} + +pub fn renderSingleInstruction( + gpa: *Allocator, + inst: Zir.Inst.Index, + scope_file: *Module.Scope.File, + parent_decl_node: Ast.Node.Index, + indent: u32, + stream: anytype, +) !void { + var arena = std.heap.ArenaAllocator.init(gpa); + defer arena.deinit(); + + var writer: Writer = .{ + .gpa = gpa, + .arena = &arena.allocator, + .file = scope_file, + .code = scope_file.zir, + .indent = indent, + .parent_decl_node = parent_decl_node, + .recurse_decls = false, + .recurse_blocks = false, + }; + + try stream.print("%{d} ", .{inst}); + try writer.writeInstToStream(stream, inst); } const Writer = struct { @@ -59,7 +125,9 @@ const Writer = struct { file: *Module.Scope.File, code: Zir, indent: u32, - parent_decl_node: u32, + parent_decl_node: Ast.Node.Index, + recurse_decls: bool, + recurse_blocks: bool, fn relativeToNodeIndex(self: *Writer, offset: i32) Ast.Node.Index { return @bitCast(Ast.Node.Index, offset + @bitCast(i32, self.parent_decl_node)); @@ -567,12 +635,8 @@ const Writer = struct { try stream.print("\"{}\", ", .{ std.zig.fmtEscapes(self.code.nullTerminatedString(extra.data.name)), }); - try stream.writeAll("{\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); + try self.writeBracedBody(stream, body); + try stream.writeAll(") "); try self.writeSrc(stream, inst_data.src()); } @@ -881,12 +945,8 @@ const Writer = struct { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index); const body = self.code.extra[extra.end..][0..extra.data.body_len]; - try stream.writeAll("{\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); + try self.writeBracedBody(stream, body); + try stream.writeAll(") "); } fn writePlNodeCondBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { @@ -895,17 +955,11 @@ const Writer = struct { const then_body = self.code.extra[extra.end..][0..extra.data.then_body_len]; const else_body = self.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; try self.writeInstRef(stream, extra.data.condition); - try stream.writeAll(", {\n"); - self.indent += 2; - try self.writeBody(stream, then_body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}, {\n"); - self.indent += 2; - try self.writeBody(stream, else_body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); + try stream.writeAll(", "); + try self.writeBracedBody(stream, then_body); + try stream.writeAll(", "); + try self.writeBracedBody(stream, else_body); + try stream.writeAll(") "); try self.writeSrc(stream, inst_data.src()); } @@ -963,17 +1017,10 @@ const Writer = struct { } else { const prev_parent_decl_node = self.parent_decl_node; if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); + try self.writeBracedDecl(stream, body); + try stream.writeAll(", {\n"); + self.indent += 2; - if (body.len == 0) { - try stream.writeAll("{}, {\n"); - } else { - try stream.writeAll("{\n"); - try self.writeBody(stream, body); - - try stream.writeByteNTimes(' ', self.indent - 2); - try stream.writeAll("}, {\n"); - } - const bits_per_field = 4; const fields_per_u32 = 32 / bits_per_field; const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; @@ -1096,17 +1143,10 @@ const Writer = struct { const prev_parent_decl_node = self.parent_decl_node; if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); + try self.writeBracedDecl(stream, body); + try stream.writeAll(", {\n"); + self.indent += 2; - if (body.len == 0) { - try stream.writeAll("{}, {\n"); - } else { - try stream.writeAll("{\n"); - try self.writeBody(stream, body); - - try stream.writeByteNTimes(' ', self.indent - 2); - try stream.writeAll("}, {\n"); - } - const bits_per_field = 4; const fields_per_u32 = 32 / bits_per_field; const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable; @@ -1251,18 +1291,25 @@ const Writer = struct { try stream.writeAll(")"); } } - const tag = self.code.instructions.items(.tag)[decl_index]; - try stream.print(" line({d}) hash({}): %{d} = {s}(", .{ - line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index, @tagName(tag), - }); - const decl_block_inst_data = self.code.instructions.items(.data)[decl_index].pl_node; - const sub_decl_node_off = decl_block_inst_data.src_node; - self.parent_decl_node = self.relativeToNodeIndex(sub_decl_node_off); - try self.writePlNodeBlockWithoutSrc(stream, decl_index); - self.parent_decl_node = parent_decl_node; - try self.writeSrc(stream, decl_block_inst_data.src()); - try stream.writeAll("\n"); + if (self.recurse_decls) { + const tag = self.code.instructions.items(.tag)[decl_index]; + try stream.print(" line({d}) hash({}): %{d} = {s}(", .{ + line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index, @tagName(tag), + }); + + const decl_block_inst_data = self.code.instructions.items(.data)[decl_index].pl_node; + const sub_decl_node_off = decl_block_inst_data.src_node; + self.parent_decl_node = self.relativeToNodeIndex(sub_decl_node_off); + try self.writePlNodeBlockWithoutSrc(stream, decl_index); + self.parent_decl_node = parent_decl_node; + try self.writeSrc(stream, decl_block_inst_data.src()); + try stream.writeAll("\n"); + } else { + try stream.print(" line({d}) hash({}): %{d} = ...\n", .{ + line, std.fmt.fmtSliceHexLower(&hash_bytes), decl_index, + }); + } } return extra_index; } @@ -1329,17 +1376,10 @@ const Writer = struct { } else { const prev_parent_decl_node = self.parent_decl_node; if (src_node) |off| self.parent_decl_node = self.relativeToNodeIndex(off); + try self.writeBracedDecl(stream, body); + try stream.writeAll(", {\n"); + self.indent += 2; - if (body.len == 0) { - try stream.writeAll("{}, {\n"); - } else { - try stream.writeAll("{\n"); - try self.writeBody(stream, body); - - try stream.writeByteNTimes(' ', self.indent - 2); - try stream.writeAll("}, {\n"); - } - const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable; const body_end = extra_index; extra_index += bit_bags_count; @@ -1463,18 +1503,18 @@ const Writer = struct { try self.writeInstRef(stream, extra.data.operand); + self.indent += 2; + if (special.body.len != 0) { const prong_name = switch (special_prong) { .@"else" => "else", .under => "_", else => unreachable, }; - try stream.print(", {s} => {{\n", .{prong_name}); - self.indent += 2; - try self.writeBody(stream, special.body); - self.indent -= 2; + try stream.writeAll(",\n"); try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); + try stream.print("{s} => ", .{prong_name}); + try self.writeBracedBody(stream, special.body); } var extra_index: usize = special.end; @@ -1488,16 +1528,16 @@ const Writer = struct { const body = self.code.extra[extra_index..][0..body_len]; extra_index += body_len; - try stream.writeAll(", "); - try self.writeInstRef(stream, item_ref); - try stream.writeAll(" => {\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; + try stream.writeAll(",\n"); try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); + try self.writeInstRef(stream, item_ref); + try stream.writeAll(" => "); + try self.writeBracedBody(stream, body); } } + + self.indent -= 2; + try stream.writeAll(") "); try self.writeSrc(stream, inst_data.src()); } @@ -1527,18 +1567,18 @@ const Writer = struct { try self.writeInstRef(stream, extra.data.operand); + self.indent += 2; + if (special.body.len != 0) { const prong_name = switch (special_prong) { .@"else" => "else", .under => "_", else => unreachable, }; - try stream.print(", {s} => {{\n", .{prong_name}); - self.indent += 2; - try self.writeBody(stream, special.body); - self.indent -= 2; + try stream.writeAll(",\n"); try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); + try stream.print("{s} => ", .{prong_name}); + try self.writeBracedBody(stream, special.body); } var extra_index: usize = special.end; @@ -1552,14 +1592,11 @@ const Writer = struct { const body = self.code.extra[extra_index..][0..body_len]; extra_index += body_len; - try stream.writeAll(", "); - try self.writeInstRef(stream, item_ref); - try stream.writeAll(" => {\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; + try stream.writeAll(",\n"); try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); + try self.writeInstRef(stream, item_ref); + try stream.writeAll(" => "); + try self.writeBracedBody(stream, body); } } { @@ -1574,8 +1611,11 @@ const Writer = struct { const items = self.code.refSlice(extra_index, items_len); extra_index += items_len; - for (items) |item_ref| { - try stream.writeAll(", "); + try stream.writeAll(",\n"); + try stream.writeByteNTimes(' ', self.indent); + + for (items) |item_ref, item_i| { + if (item_i != 0) try stream.writeAll(", "); try self.writeInstRef(stream, item_ref); } @@ -1586,7 +1626,9 @@ const Writer = struct { const item_last = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); extra_index += 1; - try stream.writeAll(", "); + if (range_i != 0 or items.len != 0) { + try stream.writeAll(", "); + } try self.writeInstRef(stream, item_first); try stream.writeAll("..."); try self.writeInstRef(stream, item_last); @@ -1594,14 +1636,13 @@ const Writer = struct { const body = self.code.extra[extra_index..][0..body_len]; extra_index += body_len; - try stream.writeAll(" => {\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); + try stream.writeAll(" => "); + try self.writeBracedBody(stream, body); } } + + self.indent -= 2; + try stream.writeAll(") "); try self.writeSrc(stream, inst_data.src()); } @@ -1796,12 +1837,8 @@ const Writer = struct { const extra = self.code.extraData(Zir.Inst.Block, inst_data.payload_index); const body = self.code.extra[extra.end..][0..extra.data.body_len]; try self.writeInstRef(stream, inst_data.lhs); - try stream.writeAll(", {\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("})"); + try stream.writeAll(", "); + try self.writeBracedBody(stream, body); } fn writeIntType(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { @@ -1846,12 +1883,8 @@ const Writer = struct { if (ret_ty_body.len == 0) { try stream.writeAll("ret_ty=void"); } else { - try stream.writeAll("ret_ty={\n"); - self.indent += 2; - try self.writeBody(stream, ret_ty_body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}"); + try stream.writeAll("ret_ty="); + try self.writeBracedBody(stream, ret_ty_body); } try self.writeOptionalInstRef(stream, ", cc=", cc); @@ -1860,16 +1893,9 @@ const Writer = struct { try self.writeFlag(stream, ", extern", is_extern); try self.writeFlag(stream, ", inferror", inferred_error_set); - if (body.len == 0) { - try stream.writeAll(", body={}) "); - } else { - try stream.writeAll(", body={\n"); - self.indent += 2; - try self.writeBody(stream, body); - self.indent -= 2; - try stream.writeByteNTimes(' ', self.indent); - try stream.writeAll("}) "); - } + try stream.writeAll(", body="); + try self.writeBracedBody(stream, body); + try stream.writeAll(") "); if (body.len != 0) { try stream.print("(lbrace={d}:{d},rbrace={d}:{d}) ", .{ src_locs.lbrace_line, @truncate(u16, src_locs.columns), @@ -1929,18 +1955,19 @@ const Writer = struct { } fn writeSrc(self: *Writer, stream: anytype, src: LazySrcLoc) !void { - const tree = self.file.tree; - const src_loc: Module.SrcLoc = .{ - .file_scope = self.file, - .parent_decl_node = self.parent_decl_node, - .lazy = src, - }; - // Caller must ensure AST tree is loaded. - const abs_byte_off = src_loc.byteOffset(self.gpa) catch unreachable; - const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off); - try stream.print("{s}:{d}:{d}", .{ - @tagName(src), delta_line.line + 1, delta_line.column + 1, - }); + if (self.file.tree_loaded) { + const tree = self.file.tree; + const src_loc: Module.SrcLoc = .{ + .file_scope = self.file, + .parent_decl_node = self.parent_decl_node, + .lazy = src, + }; + const abs_byte_off = src_loc.byteOffset(self.gpa) catch unreachable; + const delta_line = std.zig.findLineColumn(tree.source, abs_byte_off); + try stream.print("{s}:{d}:{d}", .{ + @tagName(src), delta_line.line + 1, delta_line.column + 1, + }); + } } fn writeSrcNode(self: *Writer, stream: anytype, src_node: ?i32) !void { @@ -1950,6 +1977,43 @@ const Writer = struct { return self.writeSrc(stream, src); } + fn writeBracedDecl(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void { + try self.writeBracedBodyConditional(stream, body, self.recurse_decls); + } + + fn writeBracedBody(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void { + try self.writeBracedBodyConditional(stream, body, self.recurse_blocks); + } + + fn writeBracedBodyConditional(self: *Writer, stream: anytype, body: []const Zir.Inst.Index, enabled: bool) !void { + if (body.len == 0) { + try stream.writeAll("{}"); + } else if (enabled) { + try stream.writeAll("{\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } else if (body.len == 1) { + try stream.writeByte('{'); + try self.writeInstIndex(stream, body[0]); + try stream.writeByte('}'); + } else if (body.len == 2) { + try stream.writeByte('{'); + try self.writeInstIndex(stream, body[0]); + try stream.writeAll(", "); + try self.writeInstIndex(stream, body[1]); + try stream.writeByte('}'); + } else { + try stream.writeByte('{'); + try self.writeInstIndex(stream, body[0]); + try stream.writeAll(".."); + try self.writeInstIndex(stream, body[body.len - 1]); + try stream.writeByte('}'); + } + } + fn writeBody(self: *Writer, stream: anytype, body: []const Zir.Inst.Index) !void { for (body) |inst| { try stream.writeByteNTimes(' ', self.indent); From c82c3585c8fdf02820747c118c78957e2eb5d072 Mon Sep 17 00:00:00 2001 From: Martin Wickham Date: Thu, 30 Sep 2021 00:19:21 -0500 Subject: [PATCH 158/160] Add error message to test runner for bad arguments --- lib/std/special/test_runner.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/std/special/test_runner.zig b/lib/std/special/test_runner.zig index b762e7784e..e72204377f 100644 --- a/lib/std/special/test_runner.zig +++ b/lib/std/special/test_runner.zig @@ -13,6 +13,12 @@ fn processArgs() void { const args = std.process.argsAlloc(&args_allocator.allocator) catch { @panic("Too many bytes passed over the CLI to the test runner"); }; + if (args.len != 2) { + const self_name = if (args.len >= 1) args[0] else if (builtin.os.tag == .windows) "test.exe" else "test"; + const zig_ext = if (builtin.os.tag == .windows) ".exe" else ""; + std.debug.print("Usage: {s} path/to/zig{s}\n", .{ self_name, zig_ext }); + @panic("Wrong number of command line arguments"); + } std.testing.zig_exe_path = args[1]; } From 5e7406bdd9f942900dceb2f917ed5f64b6f2ba00 Mon Sep 17 00:00:00 2001 From: g-w1 <58830309+g-w1@users.noreply.github.com> Date: Thu, 30 Sep 2021 18:31:27 -0400 Subject: [PATCH 159/160] stage2: implement array_init instruction (#9843) * stage2: array mul support more types of operands * stage2: array cat support more types of operands * print_zir: print array_init * stage2: implement Sema for array_init --- src/Sema.zig | 139 ++++++++++++++++++++++++++-------------- src/print_zir.zig | 19 +++++- test/behavior/array.zig | 6 ++ 3 files changed, 114 insertions(+), 50 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index f9f0160064..7c2ef32ad3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6111,35 +6111,36 @@ fn zirArrayCat(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| { if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| { const final_len = lhs_info.len + rhs_info.len; - if (lhs_ty.zigTypeTag() == .Pointer) { - var anon_decl = try block.startAnonDecl(); - defer anon_decl.deinit(); + const is_pointer = lhs_ty.zigTypeTag() == .Pointer; + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); - const lhs_sub_val = (try lhs_val.pointerDeref(anon_decl.arena())).?; - const rhs_sub_val = (try rhs_val.pointerDeref(anon_decl.arena())).?; - const buf = try anon_decl.arena().alloc(Value, final_len); - { - var i: u64 = 0; - while (i < lhs_info.len) : (i += 1) { - const val = try lhs_sub_val.elemValue(sema.arena, i); - buf[i] = try val.copy(anon_decl.arena()); - } + const lhs_sub_val = if (is_pointer) (try lhs_val.pointerDeref(anon_decl.arena())).? else lhs_val; + const rhs_sub_val = if (is_pointer) (try rhs_val.pointerDeref(anon_decl.arena())).? else rhs_val; + const buf = try anon_decl.arena().alloc(Value, final_len); + { + var i: u64 = 0; + while (i < lhs_info.len) : (i += 1) { + const val = try lhs_sub_val.elemValue(sema.arena, i); + buf[i] = try val.copy(anon_decl.arena()); } - { - var i: u64 = 0; - while (i < rhs_info.len) : (i += 1) { - const val = try rhs_sub_val.elemValue(sema.arena, i); - buf[lhs_info.len + i] = try val.copy(anon_decl.arena()); - } - } - const ty = if (res_sent) |rs| - try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type, .sentinel = rs }) - else - try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type }); - const val = try Value.Tag.array.create(anon_decl.arena(), buf); - return sema.analyzeDeclRef(try anon_decl.finish(ty, val)); } - return sema.mod.fail(&block.base, lhs_src, "TODO array_cat more types of Values", .{}); + { + var i: u64 = 0; + while (i < rhs_info.len) : (i += 1) { + const val = try rhs_sub_val.elemValue(sema.arena, i); + buf[lhs_info.len + i] = try val.copy(anon_decl.arena()); + } + } + const ty = if (res_sent) |rs| + try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type, .sentinel = rs }) + else + try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = lhs_info.elem_type }); + const val = try Value.Tag.array.create(anon_decl.arena(), buf); + return if (is_pointer) + sema.analyzeDeclRef(try anon_decl.finish(ty, val)) + else + sema.analyzeDeclVal(block, .unneeded, try anon_decl.finish(ty, val)); } else { return sema.mod.fail(&block.base, lhs_src, "TODO runtime array_cat", .{}); } @@ -6179,29 +6180,30 @@ fn zirArrayMul(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr const final_len = std.math.mul(u64, mulinfo.len, tomulby) catch return sema.mod.fail(&block.base, rhs_src, "operation results in overflow", .{}); if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| { - if (lhs_ty.zigTypeTag() == .Pointer) { - var anon_decl = try block.startAnonDecl(); - defer anon_decl.deinit(); - const lhs_sub_val = (try lhs_val.pointerDeref(anon_decl.arena())).?; + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + const lhs_sub_val = if (lhs_ty.zigTypeTag() == .Pointer) (try lhs_val.pointerDeref(anon_decl.arena())).? else lhs_val; + const final_ty = if (mulinfo.sentinel) |sent| + try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ .len = final_len, .elem_type = mulinfo.elem_type, .sentinel = sent }) + else + try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = mulinfo.elem_type }); + const buf = try anon_decl.arena().alloc(Value, final_len); - const final_ty = if (mulinfo.sentinel) |sent| - try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ .len = final_len, .elem_type = mulinfo.elem_type, .sentinel = sent }) - else - try Type.Tag.array.create(anon_decl.arena(), .{ .len = final_len, .elem_type = mulinfo.elem_type }); - - const buf = try anon_decl.arena().alloc(Value, final_len); - var i: u64 = 0; - while (i < tomulby) : (i += 1) { - var j: u64 = 0; - while (j < mulinfo.len) : (j += 1) { - const val = try lhs_sub_val.elemValue(sema.arena, j); - buf[mulinfo.len * i + j] = try val.copy(anon_decl.arena()); - } + // the actual loop + var i: u64 = 0; + while (i < tomulby) : (i += 1) { + var j: u64 = 0; + while (j < mulinfo.len) : (j += 1) { + const val = try lhs_sub_val.elemValue(sema.arena, j); + buf[mulinfo.len * i + j] = try val.copy(anon_decl.arena()); } - const val = try Value.Tag.array.create(anon_decl.arena(), buf); - return sema.analyzeDeclRef(try anon_decl.finish(final_ty, val)); } - return sema.mod.fail(&block.base, lhs_src, "TODO array_mul more types of Values", .{}); + const val = try Value.Tag.array.create(anon_decl.arena(), buf); + if (lhs_ty.zigTypeTag() == .Pointer) { + return sema.analyzeDeclRef(try anon_decl.finish(final_ty, val)); + } else { + return sema.analyzeDeclVal(block, .unneeded, try anon_decl.finish(final_ty, val)); + } } return sema.mod.fail(&block.base, lhs_src, "TODO runtime array_mul", .{}); } @@ -8227,8 +8229,49 @@ fn zirArrayInit(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref: const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); - _ = is_ref; - return sema.mod.fail(&block.base, src, "TODO: Sema.zirArrayInit", .{}); + const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index); + const args = sema.code.refSlice(extra.end, extra.data.operands_len); + + var resolved_args = try sema.mod.gpa.alloc(Air.Inst.Ref, args.len); + for (args) |arg, i| resolved_args[i] = sema.resolveInst(arg); + + var all_args_comptime = for (resolved_args) |arg| { + if ((try sema.resolveMaybeUndefVal(block, src, arg)) == null) break false; + } else true; + + if (all_args_comptime) { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + assert(!(resolved_args.len == 0)); + const final_ty = try Type.Tag.array.create(anon_decl.arena(), .{ .len = resolved_args.len, .elem_type = sema.typeOf(resolved_args[0]) }); + const buf = try anon_decl.arena().alloc(Value, resolved_args.len); + for (resolved_args) |arg, i| { + buf[i] = (try sema.resolveMaybeUndefVal(block, src, arg)).?; + } + + const val = try Value.Tag.array.create(anon_decl.arena(), buf); + if (is_ref) + return sema.analyzeDeclRef(try anon_decl.finish(final_ty, val)) + else + return sema.analyzeDeclVal(block, .unneeded, try anon_decl.finish(final_ty, val)); + } + + assert(!(resolved_args.len == 0)); + const array_ty = try Type.Tag.array.create(sema.arena, .{ .len = resolved_args.len, .elem_type = sema.typeOf(resolved_args[0]) }); + const final_ty = try Type.ptr(sema.arena, .{ + .pointee_type = array_ty, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); + const alloc = try block.addTy(.alloc, final_ty); + + for (resolved_args) |arg, i| { + const pointer_to_array_at_index = try block.addBinOp(.ptr_elem_ptr, alloc, try sema.addIntUnsigned(Type.initTag(.u64), i)); + _ = try block.addBinOp(.store, pointer_to_array_at_index, arg); + } + return if (is_ref) + alloc + else + try sema.analyzeLoad(block, .unneeded, alloc, .unneeded); } fn zirArrayInitAnon(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref { diff --git a/src/print_zir.zig b/src/print_zir.zig index 1d97f792e6..c53c92f6bf 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -259,15 +259,16 @@ const Writer = struct { .@"break", .break_inline, => try self.writeBreak(stream, inst), + .array_init, + .array_init_ref, + => try self.writeArrayInit(stream, inst), .elem_ptr_node, .elem_val_node, .slice_start, .slice_end, .slice_sentinel, - .array_init, .array_init_anon, - .array_init_ref, .array_init_anon_ref, .union_init_ptr, .shuffle, @@ -1860,6 +1861,20 @@ const Writer = struct { try stream.writeAll(")"); } + fn writeArrayInit(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].pl_node; + + const extra = self.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index); + const args = self.code.refSlice(extra.end, extra.data.operands_len); + + try stream.writeAll(".{"); + for (args) |arg, i| { + if (i != 0) try stream.writeAll(", "); + try self.writeInstRef(stream, arg); + } + try stream.writeAll("})"); + } + fn writeUnreachable(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].@"unreachable"; const safety_str = if (inst_data.safety) "safe" else "unsafe"; diff --git a/test/behavior/array.zig b/test/behavior/array.zig index 8cf0042d5f..8250cdea06 100644 --- a/test/behavior/array.zig +++ b/test/behavior/array.zig @@ -27,3 +27,9 @@ test "arrays" { fn getArrayLen(a: []const u32) usize { return a.len; } + +test "array init with mult" { + const a = 'a'; + var i: [8]u8 = [2]u8{ a, 'b' } ** 4; + try expect(std.mem.eql(u8, &i, "abababab")); +} From c4cd592f0e1eeff5a4056796610d97010ae4e38c Mon Sep 17 00:00:00 2001 From: Nathan Michaels Date: Thu, 30 Sep 2021 00:19:23 -0400 Subject: [PATCH 160/160] Fix a typo in @ceil documentation. --- 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 2e69e37097..ec9d96d069 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8866,7 +8866,7 @@ fn doTheTest() !void { {#header_open|@ceil#}

            {#syntax#}@ceil(value: anytype) @TypeOf(value){#endsyntax#}

            - Returns the largest integral value not less than the given floating point number. + Returns the smallest integral value not less than the given floating point number. Uses a dedicated hardware instruction when available.