diff options
Diffstat (limited to 'std')
153 files changed, 9635 insertions, 3898 deletions
diff --git a/std/array_list.zig b/std/array_list.zig index b173dab747..ca7d5f911e 100644 --- a/std/array_list.zig +++ b/std/array_list.zig @@ -111,6 +111,17 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type { new_item_ptr.* = item; } + pub fn orderedRemove(self: *Self, i: usize) T { + const newlen = self.len - 1; + if (newlen == i) return self.pop(); + + const old_item = self.at(i); + for (self.items[i..newlen]) |*b, j| b.* = self.items[i + 1 + j]; + self.items[newlen] = undefined; + self.len = newlen; + return old_item; + } + /// Removes the element at the specified index and returns it. /// The empty slot is filled from the end of the list. pub fn swapRemove(self: *Self, i: usize) T { @@ -279,6 +290,33 @@ test "std.ArrayList.basic" { testing.expect(list.pop() == 33); } +test "std.ArrayList.orderedRemove" { + var list = ArrayList(i32).init(debug.global_allocator); + defer list.deinit(); + + try list.append(1); + try list.append(2); + try list.append(3); + try list.append(4); + try list.append(5); + try list.append(6); + try list.append(7); + + //remove from middle + testing.expectEqual(i32(4), list.orderedRemove(3)); + testing.expectEqual(i32(5), list.at(3)); + testing.expectEqual(usize(6), list.len); + + //remove from end + testing.expectEqual(i32(7), list.orderedRemove(5)); + testing.expectEqual(usize(5), list.len); + + //remove from front + testing.expectEqual(i32(1), list.orderedRemove(0)); + testing.expectEqual(i32(2), list.at(0)); + testing.expectEqual(usize(4), list.len); +} + test "std.ArrayList.swapRemove" { var list = ArrayList(i32).init(debug.global_allocator); defer list.deinit(); diff --git a/std/build.zig b/std/build.zig index 2bd4a9b08f..b5ec97ab5f 100644 --- a/std/build.zig +++ b/std/build.zig @@ -50,6 +50,8 @@ pub const Builder = struct { build_root: []const u8, cache_root: []const u8, release_mode: ?builtin.Mode, + override_std_dir: ?[]const u8, + override_lib_dir: ?[]const u8, pub const CStd = enum { C89, @@ -133,6 +135,8 @@ pub const Builder = struct { }, .have_install_step = false, .release_mode = null, + .override_std_dir = null, + .override_lib_dir = null, }; self.detectNativeSystemPaths(); self.default_step = self.step("default", "Build the project"); @@ -937,8 +941,11 @@ pub const LibExeObjStep = struct { verbose_link: bool, verbose_cc: bool, disable_gen_h: bool, + bundle_compiler_rt: bool, + disable_stack_probing: bool, c_std: Builder.CStd, override_std_dir: ?[]const u8, + override_lib_dir: ?[]const u8, main_pkg_path: ?[]const u8, exec_cmd_args: ?[]const ?[]const u8, name_prefix: []const u8, @@ -1039,11 +1046,14 @@ pub const LibExeObjStep = struct { .c_std = Builder.CStd.C99, .system_linker_hack = false, .override_std_dir = null, + .override_lib_dir = null, .main_pkg_path = null, .exec_cmd_args = null, .name_prefix = "", .filter = null, .disable_gen_h = false, + .bundle_compiler_rt = false, + .disable_stack_probing = false, .output_dir = null, .need_system_paths = false, .single_threaded = false, @@ -1446,6 +1456,12 @@ pub const LibExeObjStep = struct { if (self.disable_gen_h) { try zig_args.append("--disable-gen-h"); } + if (self.bundle_compiler_rt) { + try zig_args.append("--bundle-compiler-rt"); + } + if (self.disable_stack_probing) { + try zig_args.append("--disable-stack-probing"); + } switch (self.target) { Target.Native => {}, @@ -1528,6 +1544,17 @@ pub const LibExeObjStep = struct { if (self.override_std_dir) |dir| { try zig_args.append("--override-std-dir"); try zig_args.append(builder.pathFromRoot(dir)); + } else if (self.builder.override_std_dir) |dir| { + try zig_args.append("--override-std-dir"); + try zig_args.append(builder.pathFromRoot(dir)); + } + + if (self.override_lib_dir) |dir| { + try zig_args.append("--override-lib-dir"); + try zig_args.append(builder.pathFromRoot(dir)); + } else if (self.builder.override_lib_dir) |dir| { + try zig_args.append("--override-lib-dir"); + try zig_args.append(builder.pathFromRoot(dir)); } if (self.main_pkg_path) |dir| { @@ -12,6 +12,12 @@ pub use switch (builtin.os) { // TODO https://github.com/ziglang/zig/issues/265 on this whole file +pub const FILE = @OpaqueType(); +pub extern "c" fn fopen(filename: [*]const u8, modes: [*]const u8) ?*FILE; +pub extern "c" fn fclose(stream: *FILE) c_int; +pub extern "c" fn fwrite(ptr: [*]const u8, size_of_type: usize, item_count: usize, stream: *FILE) usize; +pub extern "c" fn fread(ptr: [*]u8, size_of_type: usize, item_count: usize, stream: *FILE) usize; + pub extern "c" fn abort() noreturn; pub extern "c" fn exit(code: c_int) noreturn; pub extern "c" fn isatty(fd: c_int) c_int; diff --git a/std/c/freebsd.zig b/std/c/freebsd.zig index 2f2f4c0a1b..b6cf15bf31 100644 --- a/std/c/freebsd.zig +++ b/std/c/freebsd.zig @@ -42,14 +42,36 @@ pub const pthread_attr_t = extern struct { }; pub const msghdr = extern struct { - msg_name: *u8, + /// optional address + msg_name: ?*sockaddr, + /// size of address msg_namelen: socklen_t, - msg_iov: *iovec, + /// scatter/gather array + msg_iov: [*]iovec, + /// # elements in msg_iov msg_iovlen: i32, - __pad1: i32, - msg_control: *u8, + /// ancillary data + msg_control: ?*c_void, + /// ancillary data buffer len msg_controllen: socklen_t, - __pad2: 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, }; diff --git a/std/c/linux.zig b/std/c/linux.zig index b0dadf071d..48e359f361 100644 --- a/std/c/linux.zig +++ b/std/c/linux.zig @@ -1,3 +1,4 @@ +const linux = @import("../os/linux.zig"); pub use @import("../os/linux/errno.zig"); pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) c_int; @@ -11,3 +12,6 @@ pub const pthread_attr_t = extern struct { /// See std.elf for constants for this pub extern fn getauxval(__type: c_ulong) c_ulong; + +pub const dl_iterate_phdr_callback = extern fn (info: *linux.dl_phdr_info, size: usize, data: ?*c_void) c_int; +pub extern fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int; diff --git a/std/c/netbsd.zig b/std/c/netbsd.zig index 796d45dafc..2c07ee296b 100644 --- a/std/c/netbsd.zig +++ b/std/c/netbsd.zig @@ -42,14 +42,36 @@ pub const pthread_attr_t = extern struct { }; pub const msghdr = extern struct { - msg_name: *u8, + /// optional address + msg_name: ?*sockaddr, + /// size of address msg_namelen: socklen_t, - msg_iov: *iovec, + /// scatter/gather array + msg_iov: [*]iovec, + /// # elements in msg_iov msg_iovlen: i32, - __pad1: i32, - msg_control: *u8, + /// ancillary data + msg_control: ?*c_void, + /// ancillary data buffer len msg_controllen: socklen_t, - __pad2: 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, }; diff --git a/std/crypto/chacha20.zig b/std/crypto/chacha20.zig index 78ef700bdf..796b5c6e4f 100644 --- a/std/crypto/chacha20.zig +++ b/std/crypto/chacha20.zig @@ -142,7 +142,7 @@ pub fn chaCha20With64BitNonce(out: []u8, in: []const u8, counter: u64, key: [32] assert(in.len >= out.len); assert(counter +% (in.len >> 6) >= counter); - var cursor: u64 = 0; + var cursor: usize = 0; var k: [8]u32 = undefined; var c: [4]u32 = undefined; @@ -161,7 +161,8 @@ pub fn chaCha20With64BitNonce(out: []u8, in: []const u8, counter: u64, key: [32] c[3] = mem.readIntSliceLittle(u32, nonce[4..8]); const block_size = (1 << 6); - const big_block = (block_size << 32); + // The full block size is greater than the address space on a 32bit machine + const big_block = if (@sizeOf(usize) > 4) (block_size << 32) else maxInt(usize); // first partial big block if (((@intCast(u64, maxInt(u32) - @truncate(u32, counter)) + 1) << 6) < in.len) { diff --git a/std/debug.zig b/std/debug.zig index c85a982059..d017f96144 100644 --- a/std/debug.zig +++ b/std/debug.zig @@ -13,6 +13,8 @@ const ArrayList = std.ArrayList; const builtin = @import("builtin"); const maxInt = std.math.maxInt; +const leb = @import("debug/leb128.zig"); + pub const FailingAllocator = @import("debug/failing_allocator.zig").FailingAllocator; pub const failing_allocator = &FailingAllocator.init(global_allocator, 0).allocator; @@ -214,14 +216,14 @@ pub fn writeStackTrace( tty_color: bool, ) !void { var frame_index: usize = 0; - var frames_left: usize = stack_trace.index; + var frames_left: usize = std.math.min(stack_trace.index, stack_trace.instruction_addresses.len); while (frames_left != 0) : ({ frames_left -= 1; frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; }) { const return_address = stack_trace.instruction_addresses[frame_index]; - try printSourceAtAddress(debug_info, out_stream, return_address, tty_color); + try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_color); } } @@ -263,7 +265,7 @@ pub fn writeCurrentStackTrace(out_stream: var, debug_info: *DebugInfo, tty_color } var it = StackIterator.init(start_addr); while (it.next()) |return_address| { - try printSourceAtAddress(debug_info, out_stream, return_address, tty_color); + try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_color); } } @@ -376,7 +378,6 @@ fn printSourceAtAddressWindows(di: *DebugInfo, out_stream: var, relocated_addres // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in, // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. - const subsection_end_index = sect_offset + subsect_hdr.Length; while (line_index < subsection_end_index) { @@ -690,9 +691,9 @@ pub fn printSourceAtAddressDwarf( return; }; const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name); - if (getLineNumberInfoDwarf(debug_info, compile_unit.*, address - 1)) |line_info| { + if (getLineNumberInfoDwarf(debug_info, compile_unit.*, address)) |line_info| { defer line_info.deinit(); - const symbol_name = "???"; + const symbol_name = getSymbolNameDwarf(debug_info, address) orelse "???"; try printLineInfo( out_stream, line_info, @@ -969,6 +970,8 @@ fn findDwarfSectionFromElf(elf_file: *elf.Elf, name: []const u8) !?DwarfInfo.Sec pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void { di.abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator); di.compile_unit_list = ArrayList(CompileUnit).init(allocator); + di.func_list = ArrayList(Func).init(allocator); + try scanAllFunctions(di); try scanAllCompileUnits(di); } @@ -992,6 +995,7 @@ pub fn openElfDebugInfo( .debug_ranges = (try findDwarfSectionFromElf(&efile, ".debug_ranges")), .abbrev_table_list = undefined, .compile_unit_list = undefined, + .func_list = undefined, }; try openDwarfDebugInfo(&di, allocator); return di; @@ -1162,6 +1166,7 @@ pub const DwarfInfo = struct { debug_ranges: ?Section, abbrev_table_list: ArrayList(AbbrevTableHeader), compile_unit_list: ArrayList(CompileUnit), + func_list: ArrayList(Func), pub const Section = struct { offset: usize, @@ -1178,7 +1183,7 @@ pub const DwarfInfo = struct { }; pub const DebugInfo = switch (builtin.os) { - builtin.Os.macosx => struct { + builtin.Os.macosx, builtin.Os.ios => struct { symbols: []const MachoSymbol, strings: []const u8, ofiles: OFileTable, @@ -1213,7 +1218,6 @@ const CompileUnit = struct { version: u16, is_64: bool, die: *Die, - index: usize, pc_range: ?PcRange, }; @@ -1244,21 +1248,19 @@ const FormValue = union(enum) { ExprLoc: []u8, Flag: bool, SecOffset: u64, - Ref: []u8, + Ref: u64, RefAddr: u64, - RefSig8: u64, String: []u8, StrPtr: u64, }; const Constant = struct { - payload: []u8, + payload: u64, signed: bool, fn asUnsignedLe(self: *const Constant) !u64 { - if (self.payload.len > @sizeOf(u64)) return error.InvalidDebugInfo; if (self.signed) return error.InvalidDebugInfo; - return mem.readVarInt(u64, self.payload, builtin.Endian.Little); + return self.payload; } }; @@ -1304,6 +1306,14 @@ const Die = struct { }; } + fn getAttrRef(self: *const Die, id: u64) !u64 { + const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; + return switch (form_value.*) { + FormValue.Ref => |value| value, + else => error.InvalidDebugInfo, + }; + } + fn getAttrString(self: *const Die, di: *DwarfInfo, id: u64) ![]u8 { const form_value = self.getAttr(id) orelse return error.MissingDebugInfo; return switch (form_value.*) { @@ -1443,11 +1453,18 @@ fn parseFormValueBlock(allocator: *mem.Allocator, in_stream: var, size: usize) ! return parseFormValueBlockLen(allocator, in_stream, block_len); } -fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: bool, size: usize) !FormValue { +fn parseFormValueConstant(allocator: *mem.Allocator, in_stream: var, signed: bool, comptime size: i32) !FormValue { return FormValue{ .Const = Constant{ .signed = signed, - .payload = try readAllocBytes(allocator, in_stream, size), + .payload = switch (size) { + 1 => try in_stream.readIntLittle(u8), + 2 => try in_stream.readIntLittle(u16), + 4 => try in_stream.readIntLittle(u32), + 8 => try in_stream.readIntLittle(u64), + -1 => if (signed) @bitCast(u64, try leb.readILEB128(i64, in_stream)) else try leb.readULEB128(u64, in_stream), + else => @compileError("Invalid size"), + }, }, }; } @@ -1460,14 +1477,17 @@ fn parseFormValueTargetAddrSize(in_stream: var) !u64 { return if (@sizeOf(usize) == 4) u64(try in_stream.readIntLittle(u32)) else if (@sizeOf(usize) == 8) try in_stream.readIntLittle(u64) else unreachable; } -fn parseFormValueRefLen(allocator: *mem.Allocator, in_stream: var, size: usize) !FormValue { - const buf = try readAllocBytes(allocator, in_stream, size); - return FormValue{ .Ref = buf }; -} - -fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, comptime T: type) !FormValue { - const block_len = try in_stream.readIntLittle(T); - return parseFormValueRefLen(allocator, in_stream, block_len); +fn parseFormValueRef(allocator: *mem.Allocator, in_stream: var, size: i32) !FormValue { + return FormValue{ + .Ref = switch (size) { + 1 => try in_stream.readIntLittle(u8), + 2 => try in_stream.readIntLittle(u16), + 4 => try in_stream.readIntLittle(u32), + 8 => try in_stream.readIntLittle(u64), + -1 => try leb.readULEB128(u64, in_stream), + else => unreachable, + }, + }; } fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64: bool) anyerror!FormValue { @@ -1477,7 +1497,7 @@ fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64 DW.FORM_block2 => parseFormValueBlock(allocator, in_stream, 2), DW.FORM_block4 => parseFormValueBlock(allocator, in_stream, 4), DW.FORM_block => x: { - const block_len = try readULeb128(in_stream); + const block_len = try leb.readULEB128(usize, in_stream); return parseFormValueBlockLen(allocator, in_stream, block_len); }, DW.FORM_data1 => parseFormValueConstant(allocator, in_stream, false, 1), @@ -1485,12 +1505,11 @@ fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64 DW.FORM_data4 => parseFormValueConstant(allocator, in_stream, false, 4), DW.FORM_data8 => parseFormValueConstant(allocator, in_stream, false, 8), DW.FORM_udata, DW.FORM_sdata => { - const block_len = try readULeb128(in_stream); const signed = form_id == DW.FORM_sdata; - return parseFormValueConstant(allocator, in_stream, signed, block_len); + return parseFormValueConstant(allocator, in_stream, signed, -1); }, DW.FORM_exprloc => { - const size = try readULeb128(in_stream); + const size = try leb.readULEB128(usize, in_stream); const buf = try readAllocBytes(allocator, in_stream, size); return FormValue{ .ExprLoc = buf }; }, @@ -1498,22 +1517,19 @@ fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64 DW.FORM_flag_present => FormValue{ .Flag = true }, DW.FORM_sec_offset => FormValue{ .SecOffset = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, - DW.FORM_ref1 => parseFormValueRef(allocator, in_stream, u8), - DW.FORM_ref2 => parseFormValueRef(allocator, in_stream, u16), - DW.FORM_ref4 => parseFormValueRef(allocator, in_stream, u32), - DW.FORM_ref8 => parseFormValueRef(allocator, in_stream, u64), - DW.FORM_ref_udata => { - const ref_len = try readULeb128(in_stream); - return parseFormValueRefLen(allocator, in_stream, ref_len); - }, + DW.FORM_ref1 => parseFormValueRef(allocator, in_stream, 1), + DW.FORM_ref2 => parseFormValueRef(allocator, in_stream, 2), + DW.FORM_ref4 => parseFormValueRef(allocator, in_stream, 4), + DW.FORM_ref8 => parseFormValueRef(allocator, in_stream, 8), + DW.FORM_ref_udata => parseFormValueRef(allocator, in_stream, -1), DW.FORM_ref_addr => FormValue{ .RefAddr = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, - DW.FORM_ref_sig8 => FormValue{ .RefSig8 = try in_stream.readIntLittle(u64) }, + DW.FORM_ref_sig8 => FormValue{ .Ref = try in_stream.readIntLittle(u64) }, DW.FORM_string => FormValue{ .String = try readStringRaw(allocator, in_stream) }, DW.FORM_strp => FormValue{ .StrPtr = try parseFormValueDwarfOffsetSize(in_stream, is_64) }, DW.FORM_indirect => { - const child_form_id = try readULeb128(in_stream); + const child_form_id = try leb.readULEB128(u64, in_stream); return parseFormValue(allocator, in_stream, child_form_id, is_64); }, else => error.InvalidDebugInfo, @@ -1523,19 +1539,19 @@ fn parseFormValue(allocator: *mem.Allocator, in_stream: var, form_id: u64, is_64 fn parseAbbrevTable(di: *DwarfInfo) !AbbrevTable { var result = AbbrevTable.init(di.allocator()); while (true) { - const abbrev_code = try readULeb128(di.dwarf_in_stream); + const abbrev_code = try leb.readULEB128(u64, di.dwarf_in_stream); if (abbrev_code == 0) return result; try result.append(AbbrevTableEntry{ .abbrev_code = abbrev_code, - .tag_id = try readULeb128(di.dwarf_in_stream), + .tag_id = try leb.readULEB128(u64, di.dwarf_in_stream), .has_children = (try di.dwarf_in_stream.readByte()) == DW.CHILDREN_yes, .attrs = ArrayList(AbbrevAttr).init(di.allocator()), }); const attrs = &result.items[result.len - 1].attrs; while (true) { - const attr_id = try readULeb128(di.dwarf_in_stream); - const form_id = try readULeb128(di.dwarf_in_stream); + const attr_id = try leb.readULEB128(u64, di.dwarf_in_stream); + const form_id = try leb.readULEB128(u64, di.dwarf_in_stream); if (attr_id == 0 and form_id == 0) break; try attrs.append(AbbrevAttr{ .attr_id = attr_id, @@ -1568,8 +1584,28 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con return null; } +fn parseDie1(di: *DwarfInfo, abbrev_table: *const AbbrevTable, is_64: bool) !?Die { + const abbrev_code = try leb.readULEB128(u64, di.dwarf_in_stream); + if (abbrev_code == 0) return null; + const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo; + + var result = Die{ + .tag_id = table_entry.tag_id, + .has_children = table_entry.has_children, + .attrs = ArrayList(Die.Attr).init(di.allocator()), + }; + try result.attrs.resize(table_entry.attrs.len); + for (table_entry.attrs.toSliceConst()) |attr, i| { + result.attrs.items[i] = Die.Attr{ + .id = attr.attr_id, + .value = try parseFormValue(di.allocator(), di.dwarf_in_stream, attr.form_id, is_64), + }; + } + return result; +} + fn parseDie(di: *DwarfInfo, abbrev_table: *const AbbrevTable, is_64: bool) !Die { - const abbrev_code = try readULeb128(di.dwarf_in_stream); + const abbrev_code = try leb.readULEB128(u64, di.dwarf_in_stream); const table_entry = getAbbrevTableEntry(abbrev_table, abbrev_code) orelse return error.InvalidDebugInfo; var result = Die{ @@ -1682,9 +1718,9 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u while (true) { const file_name = readStringMem(&ptr); if (file_name.len == 0) break; - const dir_index = try readULeb128Mem(&ptr); - const mtime = try readULeb128Mem(&ptr); - const len_bytes = try readULeb128Mem(&ptr); + const dir_index = try leb.readULEB128Mem(usize, &ptr); + const mtime = try leb.readULEB128Mem(usize, &ptr); + const len_bytes = try leb.readULEB128Mem(usize, &ptr); try file_entries.append(FileEntry{ .file_name = file_name, .dir_index = dir_index, @@ -1698,7 +1734,7 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u const opcode = readByteMem(&ptr); if (opcode == DW.LNS_extended_op) { - const op_size = try readULeb128Mem(&ptr); + const op_size = try leb.readULEB128Mem(u64, &ptr); if (op_size < 1) return error.InvalidDebugInfo; var sub_op = readByteMem(&ptr); switch (sub_op) { @@ -1713,9 +1749,9 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u }, DW.LNE_define_file => { const file_name = readStringMem(&ptr); - const dir_index = try readULeb128Mem(&ptr); - const mtime = try readULeb128Mem(&ptr); - const len_bytes = try readULeb128Mem(&ptr); + const dir_index = try leb.readULEB128Mem(usize, &ptr); + const mtime = try leb.readULEB128Mem(usize, &ptr); + const len_bytes = try leb.readULEB128Mem(usize, &ptr); try file_entries.append(FileEntry{ .file_name = file_name, .dir_index = dir_index, @@ -1743,19 +1779,19 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u prog.basic_block = false; }, DW.LNS_advance_pc => { - const arg = try readULeb128Mem(&ptr); + const arg = try leb.readULEB128Mem(u64, &ptr); prog.address += arg * minimum_instruction_length; }, DW.LNS_advance_line => { - const arg = try readILeb128Mem(&ptr); + const arg = try leb.readILEB128Mem(i64, &ptr); prog.line += arg; }, DW.LNS_set_file => { - const arg = try readULeb128Mem(&ptr); + const arg = try leb.readULEB128Mem(u64, &ptr); prog.file = arg; }, DW.LNS_set_column => { - const arg = try readULeb128Mem(&ptr); + const arg = try leb.readULEB128Mem(u64, &ptr); prog.column = arg; }, DW.LNS_negate_stmt => { @@ -1787,182 +1823,292 @@ fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, target_address: u fn getLineNumberInfoDwarf(di: *DwarfInfo, compile_unit: CompileUnit, target_address: usize) !LineInfo { const compile_unit_cwd = try compile_unit.die.getAttrString(di, DW.AT_comp_dir); + const line_info_offset = try compile_unit.die.getAttrSecOffset(DW.AT_stmt_list); - const debug_line_end = di.debug_line.offset + di.debug_line.size; - var this_offset = di.debug_line.offset; - var this_index: usize = 0; + assert(line_info_offset < di.debug_line.size); - while (this_offset < debug_line_end) : (this_index += 1) { - try di.dwarf_seekable_stream.seekTo(this_offset); + try di.dwarf_seekable_stream.seekTo(di.debug_line.offset + line_info_offset); - var is_64: bool = undefined; - const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64); - if (unit_length == 0) return error.MissingDebugInfo; - const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); + var is_64: bool = undefined; + const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64); + if (unit_length == 0) { + return error.MissingDebugInfo; + } + const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); - if (compile_unit.index != this_index) { - this_offset += next_offset; - continue; - } + const version = try di.dwarf_in_stream.readInt(u16, di.endian); + // TODO support 3 and 5 + if (version != 2 and version != 4) return error.InvalidDebugInfo; - const version = try di.dwarf_in_stream.readInt(u16, di.endian); - // TODO support 3 and 5 - if (version != 2 and version != 4) return error.InvalidDebugInfo; + const prologue_length = if (is_64) try di.dwarf_in_stream.readInt(u64, di.endian) else try di.dwarf_in_stream.readInt(u32, di.endian); + const prog_start_offset = (try di.dwarf_seekable_stream.getPos()) + prologue_length; + + const minimum_instruction_length = try di.dwarf_in_stream.readByte(); + if (minimum_instruction_length == 0) return error.InvalidDebugInfo; + + if (version >= 4) { + // maximum_operations_per_instruction + _ = try di.dwarf_in_stream.readByte(); + } + + const default_is_stmt = (try di.dwarf_in_stream.readByte()) != 0; + const line_base = try di.dwarf_in_stream.readByteSigned(); + + const line_range = try di.dwarf_in_stream.readByte(); + if (line_range == 0) return error.InvalidDebugInfo; - const prologue_length = if (is_64) try di.dwarf_in_stream.readInt(u64, di.endian) else try di.dwarf_in_stream.readInt(u32, di.endian); - const prog_start_offset = (try di.dwarf_seekable_stream.getPos()) + prologue_length; + const opcode_base = try di.dwarf_in_stream.readByte(); - const minimum_instruction_length = try di.dwarf_in_stream.readByte(); - if (minimum_instruction_length == 0) return error.InvalidDebugInfo; + const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1); - if (version >= 4) { - // maximum_operations_per_instruction - _ = try di.dwarf_in_stream.readByte(); + { + var i: usize = 0; + while (i < opcode_base - 1) : (i += 1) { + standard_opcode_lengths[i] = try di.dwarf_in_stream.readByte(); } + } - const default_is_stmt = (try di.dwarf_in_stream.readByte()) != 0; - const line_base = try di.dwarf_in_stream.readByteSigned(); + var include_directories = ArrayList([]u8).init(di.allocator()); + try include_directories.append(compile_unit_cwd); + while (true) { + const dir = try di.readString(); + if (dir.len == 0) break; + try include_directories.append(dir); + } + + var file_entries = ArrayList(FileEntry).init(di.allocator()); + var prog = LineNumberProgram.init(default_is_stmt, include_directories.toSliceConst(), &file_entries, target_address); - const line_range = try di.dwarf_in_stream.readByte(); - if (line_range == 0) return error.InvalidDebugInfo; + while (true) { + const file_name = try di.readString(); + if (file_name.len == 0) break; + const dir_index = try leb.readULEB128(usize, di.dwarf_in_stream); + const mtime = try leb.readULEB128(usize, di.dwarf_in_stream); + const len_bytes = try leb.readULEB128(usize, di.dwarf_in_stream); + try file_entries.append(FileEntry{ + .file_name = file_name, + .dir_index = dir_index, + .mtime = mtime, + .len_bytes = len_bytes, + }); + } - const opcode_base = try di.dwarf_in_stream.readByte(); + try di.dwarf_seekable_stream.seekTo(prog_start_offset); - const standard_opcode_lengths = try di.allocator().alloc(u8, opcode_base - 1); + while (true) { + const opcode = try di.dwarf_in_stream.readByte(); - { - var i: usize = 0; - while (i < opcode_base - 1) : (i += 1) { - standard_opcode_lengths[i] = try di.dwarf_in_stream.readByte(); + if (opcode == DW.LNS_extended_op) { + const op_size = try leb.readULEB128(u64, di.dwarf_in_stream); + if (op_size < 1) return error.InvalidDebugInfo; + var sub_op = try di.dwarf_in_stream.readByte(); + switch (sub_op) { + DW.LNE_end_sequence => { + prog.end_sequence = true; + if (try prog.checkLineMatch()) |info| return info; + return error.MissingDebugInfo; + }, + DW.LNE_set_address => { + const addr = try di.dwarf_in_stream.readInt(usize, di.endian); + prog.address = addr; + }, + DW.LNE_define_file => { + const file_name = try di.readString(); + const dir_index = try leb.readULEB128(usize, di.dwarf_in_stream); + const mtime = try leb.readULEB128(usize, di.dwarf_in_stream); + const len_bytes = try leb.readULEB128(usize, di.dwarf_in_stream); + try file_entries.append(FileEntry{ + .file_name = file_name, + .dir_index = dir_index, + .mtime = mtime, + .len_bytes = len_bytes, + }); + }, + else => { + const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo; + try di.dwarf_seekable_stream.seekForward(fwd_amt); + }, + } + } else if (opcode >= opcode_base) { + // special opcodes + const adjusted_opcode = opcode - opcode_base; + const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); + const inc_line = i32(line_base) + i32(adjusted_opcode % line_range); + prog.line += inc_line; + prog.address += inc_addr; + if (try prog.checkLineMatch()) |info| return info; + prog.basic_block = false; + } else { + switch (opcode) { + DW.LNS_copy => { + if (try prog.checkLineMatch()) |info| return info; + prog.basic_block = false; + }, + DW.LNS_advance_pc => { + const arg = try leb.readULEB128(u64, di.dwarf_in_stream); + prog.address += arg * minimum_instruction_length; + }, + DW.LNS_advance_line => { + const arg = try leb.readILEB128(i64, di.dwarf_in_stream); + prog.line += arg; + }, + DW.LNS_set_file => { + const arg = try leb.readULEB128(u64, di.dwarf_in_stream); + prog.file = arg; + }, + DW.LNS_set_column => { + const arg = try leb.readULEB128(u64, di.dwarf_in_stream); + prog.column = arg; + }, + DW.LNS_negate_stmt => { + prog.is_stmt = !prog.is_stmt; + }, + DW.LNS_set_basic_block => { + prog.basic_block = true; + }, + DW.LNS_const_add_pc => { + const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); + prog.address += inc_addr; + }, + DW.LNS_fixed_advance_pc => { + const arg = try di.dwarf_in_stream.readInt(u16, di.endian); + prog.address += arg; + }, + DW.LNS_set_prologue_end => {}, + else => { + if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; + const len_bytes = standard_opcode_lengths[opcode - 1]; + try di.dwarf_seekable_stream.seekForward(len_bytes); + }, } } + } - var include_directories = ArrayList([]u8).init(di.allocator()); - try include_directories.append(compile_unit_cwd); - while (true) { - const dir = try di.readString(); - if (dir.len == 0) break; - try include_directories.append(dir); - } + return error.MissingDebugInfo; +} - var file_entries = ArrayList(FileEntry).init(di.allocator()); - var prog = LineNumberProgram.init(default_is_stmt, include_directories.toSliceConst(), &file_entries, target_address); +const Func = struct { + pc_range: ?PcRange, + name: ?[]u8, +}; - while (true) { - const file_name = try di.readString(); - if (file_name.len == 0) break; - const dir_index = try readULeb128(di.dwarf_in_stream); - const mtime = try readULeb128(di.dwarf_in_stream); - const len_bytes = try readULeb128(di.dwarf_in_stream); - try file_entries.append(FileEntry{ - .file_name = file_name, - .dir_index = dir_index, - .mtime = mtime, - .len_bytes = len_bytes, - }); +fn getSymbolNameDwarf(di: *DwarfInfo, address: u64) ?[]const u8 { + for (di.func_list.toSliceConst()) |*func| { + if (func.pc_range) |range| { + if (address >= range.start and address < range.end) { + return func.name; + } } + } + + return null; +} - try di.dwarf_seekable_stream.seekTo(prog_start_offset); +fn scanAllFunctions(di: *DwarfInfo) !void { + const debug_info_end = di.debug_info.offset + di.debug_info.size; + var this_unit_offset = di.debug_info.offset; - while (true) { - const opcode = try di.dwarf_in_stream.readByte(); - - if (opcode == DW.LNS_extended_op) { - const op_size = try readULeb128(di.dwarf_in_stream); - if (op_size < 1) return error.InvalidDebugInfo; - var sub_op = try di.dwarf_in_stream.readByte(); - switch (sub_op) { - DW.LNE_end_sequence => { - prog.end_sequence = true; - if (try prog.checkLineMatch()) |info| return info; - return error.MissingDebugInfo; - }, - DW.LNE_set_address => { - const addr = try di.dwarf_in_stream.readInt(usize, di.endian); - prog.address = addr; - }, - DW.LNE_define_file => { - const file_name = try di.readString(); - const dir_index = try readULeb128(di.dwarf_in_stream); - const mtime = try readULeb128(di.dwarf_in_stream); - const len_bytes = try readULeb128(di.dwarf_in_stream); - try file_entries.append(FileEntry{ - .file_name = file_name, - .dir_index = dir_index, - .mtime = mtime, - .len_bytes = len_bytes, - }); - }, - else => { - const fwd_amt = math.cast(isize, op_size - 1) catch return error.InvalidDebugInfo; - try di.dwarf_seekable_stream.seekForward(fwd_amt); - }, - } - } else if (opcode >= opcode_base) { - // special opcodes - const adjusted_opcode = opcode - opcode_base; - const inc_addr = minimum_instruction_length * (adjusted_opcode / line_range); - const inc_line = i32(line_base) + i32(adjusted_opcode % line_range); - prog.line += inc_line; - prog.address += inc_addr; - if (try prog.checkLineMatch()) |info| return info; - prog.basic_block = false; - } else { - switch (opcode) { - DW.LNS_copy => { - if (try prog.checkLineMatch()) |info| return info; - prog.basic_block = false; - }, - DW.LNS_advance_pc => { - const arg = try readULeb128(di.dwarf_in_stream); - prog.address += arg * minimum_instruction_length; - }, - DW.LNS_advance_line => { - const arg = try readILeb128(di.dwarf_in_stream); - prog.line += arg; - }, - DW.LNS_set_file => { - const arg = try readULeb128(di.dwarf_in_stream); - prog.file = arg; - }, - DW.LNS_set_column => { - const arg = try readULeb128(di.dwarf_in_stream); - prog.column = arg; - }, - DW.LNS_negate_stmt => { - prog.is_stmt = !prog.is_stmt; - }, - DW.LNS_set_basic_block => { - prog.basic_block = true; - }, - DW.LNS_const_add_pc => { - const inc_addr = minimum_instruction_length * ((255 - opcode_base) / line_range); - prog.address += inc_addr; - }, - DW.LNS_fixed_advance_pc => { - const arg = try di.dwarf_in_stream.readInt(u16, di.endian); - prog.address += arg; - }, - DW.LNS_set_prologue_end => {}, - else => { - if (opcode - 1 >= standard_opcode_lengths.len) return error.InvalidDebugInfo; - const len_bytes = standard_opcode_lengths[opcode - 1]; - try di.dwarf_seekable_stream.seekForward(len_bytes); - }, - } + while (this_unit_offset < debug_info_end) { + try di.dwarf_seekable_stream.seekTo(this_unit_offset); + + var is_64: bool = undefined; + const unit_length = try readInitialLength(@typeOf(di.dwarf_in_stream.readFn).ReturnType.ErrorSet, di.dwarf_in_stream, &is_64); + if (unit_length == 0) return; + const next_offset = unit_length + (if (is_64) usize(12) else usize(4)); + + const version = try di.dwarf_in_stream.readInt(u16, di.endian); + if (version < 2 or version > 5) return error.InvalidDebugInfo; + + const debug_abbrev_offset = if (is_64) try di.dwarf_in_stream.readInt(u64, di.endian) else try di.dwarf_in_stream.readInt(u32, di.endian); + + const address_size = try di.dwarf_in_stream.readByte(); + if (address_size != @sizeOf(usize)) return error.InvalidDebugInfo; + + const compile_unit_pos = try di.dwarf_seekable_stream.getPos(); + const abbrev_table = try getAbbrevTable(di, debug_abbrev_offset); + + try di.dwarf_seekable_stream.seekTo(compile_unit_pos); + + const next_unit_pos = this_unit_offset + next_offset; + + while ((try di.dwarf_seekable_stream.getPos()) < next_unit_pos) { + const die_obj = (try parseDie1(di, abbrev_table, is_64)) orelse continue; + const after_die_offset = try di.dwarf_seekable_stream.getPos(); + + switch (die_obj.tag_id) { + DW.TAG_subprogram, DW.TAG_inlined_subroutine, DW.TAG_subroutine, DW.TAG_entry_point => { + const fn_name = x: { + var depth: i32 = 3; + var this_die_obj = die_obj; + // Prenvent endless loops + while (depth > 0) : (depth -= 1) { + if (this_die_obj.getAttr(DW.AT_name)) |_| { + const name = try this_die_obj.getAttrString(di, DW.AT_name); + break :x name; + } else if (this_die_obj.getAttr(DW.AT_abstract_origin)) |ref| { + // Follow the DIE it points to and repeat + const ref_offset = try this_die_obj.getAttrRef(DW.AT_abstract_origin); + if (ref_offset > next_offset) return error.InvalidDebugInfo; + try di.dwarf_seekable_stream.seekTo(this_unit_offset + ref_offset); + this_die_obj = (try parseDie1(di, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; + } else if (this_die_obj.getAttr(DW.AT_specification)) |ref| { + // Follow the DIE it points to and repeat + const ref_offset = try this_die_obj.getAttrRef(DW.AT_specification); + if (ref_offset > next_offset) return error.InvalidDebugInfo; + try di.dwarf_seekable_stream.seekTo(this_unit_offset + ref_offset); + this_die_obj = (try parseDie1(di, abbrev_table, is_64)) orelse return error.InvalidDebugInfo; + } else { + break :x null; + } + } + + break :x null; + }; + + const pc_range = x: { + if (die_obj.getAttrAddr(DW.AT_low_pc)) |low_pc| { + if (die_obj.getAttr(DW.AT_high_pc)) |high_pc_value| { + const pc_end = switch (high_pc_value.*) { + FormValue.Address => |value| value, + FormValue.Const => |value| b: { + const offset = try value.asUnsignedLe(); + break :b (low_pc + offset); + }, + else => return error.InvalidDebugInfo, + }; + break :x PcRange{ + .start = low_pc, + .end = pc_end, + }; + } else { + break :x null; + } + } else |err| { + if (err != error.MissingDebugInfo) return err; + break :x null; + } + }; + + try di.func_list.append(Func{ + .name = fn_name, + .pc_range = pc_range, + }); + }, + else => { + continue; + }, } + + try di.dwarf_seekable_stream.seekTo(after_die_offset); } - this_offset += next_offset; + this_unit_offset += next_offset; } - - return error.MissingDebugInfo; } fn scanAllCompileUnits(di: *DwarfInfo) !void { const debug_info_end = di.debug_info.offset + di.debug_info.size; var this_unit_offset = di.debug_info.offset; - var cu_index: usize = 0; while (this_unit_offset < debug_info_end) { try di.dwarf_seekable_stream.seekTo(this_unit_offset); @@ -2019,11 +2165,9 @@ fn scanAllCompileUnits(di: *DwarfInfo) !void { .is_64 = is_64, .pc_range = pc_range, .die = compile_unit_die, - .index = cu_index, }); this_unit_offset += next_offset; - cu_index += 1; } } @@ -2098,52 +2242,6 @@ fn readStringMem(ptr: *[*]const u8) []const u8 { return result; } -fn readULeb128Mem(ptr: *[*]const u8) !u64 { - var result: u64 = 0; - var shift: usize = 0; - var i: usize = 0; - - while (true) { - const byte = ptr.*[i]; - i += 1; - - var operand: u64 = undefined; - - if (@shlWithOverflow(u64, byte & 0b01111111, @intCast(u6, shift), &operand)) return error.InvalidDebugInfo; - - result |= operand; - - if ((byte & 0b10000000) == 0) { - ptr.* += i; - return result; - } - - shift += 7; - } -} -fn readILeb128Mem(ptr: *[*]const u8) !i64 { - var result: i64 = 0; - var shift: usize = 0; - var i: usize = 0; - - while (true) { - const byte = ptr.*[i]; - i += 1; - - var operand: i64 = undefined; - if (@shlWithOverflow(i64, byte & 0b01111111, @intCast(u6, shift), &operand)) return error.InvalidDebugInfo; - - result |= operand; - shift += 7; - - if ((byte & 0b10000000) == 0) { - if (shift < @sizeOf(i64) * 8 and (byte & 0b01000000) != 0) result |= -(i64(1) << @intCast(u6, shift)); - ptr.* += i; - return result; - } - } -} - fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool) !u64 { const first_32_bits = try in_stream.readIntLittle(u32); is_64.* = (first_32_bits == 0xffffffff); @@ -2155,46 +2253,6 @@ fn readInitialLength(comptime E: type, in_stream: *io.InStream(E), is_64: *bool) } } -fn readULeb128(in_stream: var) !u64 { - var result: u64 = 0; - var shift: usize = 0; - - while (true) { - const byte = try in_stream.readByte(); - - var operand: u64 = undefined; - - if (@shlWithOverflow(u64, byte & 0b01111111, @intCast(u6, shift), &operand)) return error.InvalidDebugInfo; - - result |= operand; - - if ((byte & 0b10000000) == 0) return result; - - shift += 7; - } -} - -fn readILeb128(in_stream: var) !i64 { - var result: i64 = 0; - var shift: usize = 0; - - while (true) { - const byte = try in_stream.readByte(); - - var operand: i64 = undefined; - - if (@shlWithOverflow(i64, byte & 0b01111111, @intCast(u6, shift), &operand)) return error.InvalidDebugInfo; - - result |= operand; - shift += 7; - - if ((byte & 0b10000000) == 0) { - if (shift < @sizeOf(i64) * 8 and (byte & 0b01000000) != 0) result |= -(i64(1) << @intCast(u6, shift)); - return result; - } - } -} - /// This should only be used in temporary test programs. pub const global_allocator = &global_fixed_allocator.allocator; var global_fixed_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(global_allocator_mem[0..]); diff --git a/std/debug/failing_allocator.zig b/std/debug/failing_allocator.zig index 7a89460513..5776d23194 100644 --- a/std/debug/failing_allocator.zig +++ b/std/debug/failing_allocator.zig @@ -10,6 +10,7 @@ pub const FailingAllocator = struct { internal_allocator: *mem.Allocator, allocated_bytes: usize, freed_bytes: usize, + allocations: usize, deallocations: usize, pub fn init(allocator: *mem.Allocator, fail_index: usize) FailingAllocator { @@ -19,6 +20,7 @@ pub const FailingAllocator = struct { .index = 0, .allocated_bytes = 0, .freed_bytes = 0, + .allocations = 0, .deallocations = 0, .allocator = mem.Allocator{ .reallocFn = realloc, @@ -39,19 +41,25 @@ pub const FailingAllocator = struct { new_size, new_align, ); - if (new_size <= old_mem.len) { + if (new_size < old_mem.len) { self.freed_bytes += old_mem.len - new_size; - } else { + if (new_size == 0) + self.deallocations += 1; + } else if (new_size > old_mem.len) { self.allocated_bytes += new_size - old_mem.len; + if (old_mem.len == 0) + self.allocations += 1; } - self.deallocations += 1; self.index += 1; return result; } fn shrink(allocator: *mem.Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { const self = @fieldParentPtr(FailingAllocator, "allocator", allocator); - self.freed_bytes += old_mem.len - new_size; - return self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); + const r = self.internal_allocator.shrinkFn(self.internal_allocator, old_mem, old_align, new_size, new_align); + self.freed_bytes += old_mem.len - r.len; + if (new_size == 0) + self.deallocations += 1; + return r; } }; diff --git a/std/debug/leb128.zig b/std/debug/leb128.zig new file mode 100644 index 0000000000..2801877839 --- /dev/null +++ b/std/debug/leb128.zig @@ -0,0 +1,228 @@ +const std = @import("std"); +const testing = std.testing; + +pub fn readULEB128(comptime T: type, in_stream: var) !T { + const ShiftT = @IntType(false, std.math.log2(T.bit_count)); + + var result: T = 0; + var shift: usize = 0; + + while (true) { + const byte = try in_stream.readByte(); + + if (shift > T.bit_count) + return error.Overflow; + + var operand: T = undefined; + if (@shlWithOverflow(T, byte & 0x7f, @intCast(ShiftT, shift), &operand)) + return error.Overflow; + + result |= operand; + + if ((byte & 0x80) == 0) + return result; + + shift += 7; + } +} + +pub fn readULEB128Mem(comptime T: type, ptr: *[*]const u8) !T { + const ShiftT = @IntType(false, std.math.log2(T.bit_count)); + + var result: T = 0; + var shift: usize = 0; + var i: usize = 0; + + while (true) : (i += 1) { + const byte = ptr.*[i]; + + if (shift > T.bit_count) + return error.Overflow; + + var operand: T = undefined; + if (@shlWithOverflow(T, byte & 0x7f, @intCast(ShiftT, shift), &operand)) + return error.Overflow; + + result |= operand; + + if ((byte & 0x80) == 0) { + ptr.* += i; + return result; + } + + shift += 7; + } +} + +pub fn readILEB128(comptime T: type, in_stream: var) !T { + const UT = @IntType(false, T.bit_count); + const ShiftT = @IntType(false, std.math.log2(T.bit_count)); + + var result: UT = 0; + var shift: usize = 0; + + while (true) { + const byte = u8(try in_stream.readByte()); + + if (shift > T.bit_count) + return error.Overflow; + + var operand: UT = undefined; + if (@shlWithOverflow(UT, UT(byte & 0x7f), @intCast(ShiftT, shift), &operand)) { + if (byte != 0x7f) + return error.Overflow; + } + + result |= operand; + + shift += 7; + + if ((byte & 0x80) == 0) { + if (shift < T.bit_count and (byte & 0x40) != 0) { + result |= @bitCast(UT, @intCast(T, -1)) << @intCast(ShiftT, shift); + } + return @bitCast(T, result); + } + } +} + +pub fn readILEB128Mem(comptime T: type, ptr: *[*]const u8) !T { + const UT = @IntType(false, T.bit_count); + const ShiftT = @IntType(false, std.math.log2(T.bit_count)); + + var result: UT = 0; + var shift: usize = 0; + var i: usize = 0; + + while (true) : (i += 1) { + const byte = ptr.*[i]; + + if (shift > T.bit_count) + return error.Overflow; + + var operand: UT = undefined; + if (@shlWithOverflow(UT, UT(byte & 0x7f), @intCast(ShiftT, shift), &operand)) { + if (byte != 0x7f) + return error.Overflow; + } + + result |= operand; + + shift += 7; + + if ((byte & 0x80) == 0) { + if (shift < T.bit_count and (byte & 0x40) != 0) { + result |= @bitCast(UT, @intCast(T, -1)) << @intCast(ShiftT, shift); + } + ptr.* += i; + return @bitCast(T, result); + } + } +} + +fn test_read_stream_ileb128(comptime T: type, encoded: []const u8) !T { + var in_stream = std.io.SliceInStream.init(encoded); + return try readILEB128(T, &in_stream.stream); +} + +fn test_read_stream_uleb128(comptime T: type, encoded: []const u8) !T { + var in_stream = std.io.SliceInStream.init(encoded); + return try readULEB128(T, &in_stream.stream); +} + +fn test_read_ileb128(comptime T: type, encoded: []const u8) !T { + var in_stream = std.io.SliceInStream.init(encoded); + const v1 = readILEB128(T, &in_stream.stream); + var in_ptr = encoded.ptr; + const v2 = readILEB128Mem(T, &in_ptr); + testing.expectEqual(v1, v2); + return v1; +} + +fn test_read_uleb128(comptime T: type, encoded: []const u8) !T { + var in_stream = std.io.SliceInStream.init(encoded); + const v1 = readULEB128(T, &in_stream.stream); + var in_ptr = encoded.ptr; + const v2 = readULEB128Mem(T, &in_ptr); + testing.expectEqual(v1, v2); + return v1; +} + +test "deserialize signed LEB128" { + // Truncated + testing.expectError(error.EndOfStream, test_read_stream_ileb128(i64, "\x80")); + + // Overflow + testing.expectError(error.Overflow, test_read_ileb128(i8, "\x80\x80\x40")); + testing.expectError(error.Overflow, test_read_ileb128(i16, "\x80\x80\x80\x40")); + testing.expectError(error.Overflow, test_read_ileb128(i32, "\x80\x80\x80\x80\x40")); + testing.expectError(error.Overflow, test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x40")); + testing.expectError(error.Overflow, test_read_ileb128(i8, "\xff\x7e")); + + // Decode SLEB128 + testing.expect((try test_read_ileb128(i64, "\x00")) == 0); + testing.expect((try test_read_ileb128(i64, "\x01")) == 1); + testing.expect((try test_read_ileb128(i64, "\x3f")) == 63); + testing.expect((try test_read_ileb128(i64, "\x40")) == -64); + testing.expect((try test_read_ileb128(i64, "\x41")) == -63); + testing.expect((try test_read_ileb128(i64, "\x7f")) == -1); + testing.expect((try test_read_ileb128(i64, "\x80\x01")) == 128); + testing.expect((try test_read_ileb128(i64, "\x81\x01")) == 129); + testing.expect((try test_read_ileb128(i64, "\xff\x7e")) == -129); + testing.expect((try test_read_ileb128(i64, "\x80\x7f")) == -128); + testing.expect((try test_read_ileb128(i64, "\x81\x7f")) == -127); + testing.expect((try test_read_ileb128(i64, "\xc0\x00")) == 64); + testing.expect((try test_read_ileb128(i64, "\xc7\x9f\x7f")) == -12345); + testing.expect((try test_read_ileb128(i8, "\xff\x7f")) == -1); + testing.expect((try test_read_ileb128(i16, "\xff\xff\x7f")) == -1); + testing.expect((try test_read_ileb128(i32, "\xff\xff\xff\xff\x7f")) == -1); + testing.expect((try test_read_ileb128(i32, "\x80\x80\x80\x80\x08")) == -0x80000000); + testing.expect((try test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01")) == @bitCast(i64, @intCast(u64, 0x8000000000000000))); + testing.expect((try test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x40")) == -0x4000000000000000); + testing.expect((try test_read_ileb128(i64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7f")) == -0x8000000000000000); + + // Decode unnormalized SLEB128 with extra padding bytes. + testing.expect((try test_read_ileb128(i64, "\x80\x00")) == 0); + testing.expect((try test_read_ileb128(i64, "\x80\x80\x00")) == 0); + testing.expect((try test_read_ileb128(i64, "\xff\x00")) == 0x7f); + testing.expect((try test_read_ileb128(i64, "\xff\x80\x00")) == 0x7f); + testing.expect((try test_read_ileb128(i64, "\x80\x81\x00")) == 0x80); + testing.expect((try test_read_ileb128(i64, "\x80\x81\x80\x00")) == 0x80); +} + +test "deserialize unsigned LEB128" { + // Truncated + testing.expectError(error.EndOfStream, test_read_stream_uleb128(u64, "\x80")); + + // Overflow + testing.expectError(error.Overflow, test_read_uleb128(u8, "\x80\x02")); + testing.expectError(error.Overflow, test_read_uleb128(u8, "\x80\x80\x40")); + testing.expectError(error.Overflow, test_read_uleb128(u16, "\x80\x80\x84")); + testing.expectError(error.Overflow, test_read_uleb128(u16, "\x80\x80\x80\x40")); + testing.expectError(error.Overflow, test_read_uleb128(u32, "\x80\x80\x80\x80\x90")); + testing.expectError(error.Overflow, test_read_uleb128(u32, "\x80\x80\x80\x80\x40")); + testing.expectError(error.Overflow, test_read_uleb128(u64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x40")); + + // Decode ULEB128 + testing.expect((try test_read_uleb128(u64, "\x00")) == 0); + testing.expect((try test_read_uleb128(u64, "\x01")) == 1); + testing.expect((try test_read_uleb128(u64, "\x3f")) == 63); + testing.expect((try test_read_uleb128(u64, "\x40")) == 64); + testing.expect((try test_read_uleb128(u64, "\x7f")) == 0x7f); + testing.expect((try test_read_uleb128(u64, "\x80\x01")) == 0x80); + testing.expect((try test_read_uleb128(u64, "\x81\x01")) == 0x81); + testing.expect((try test_read_uleb128(u64, "\x90\x01")) == 0x90); + testing.expect((try test_read_uleb128(u64, "\xff\x01")) == 0xff); + testing.expect((try test_read_uleb128(u64, "\x80\x02")) == 0x100); + testing.expect((try test_read_uleb128(u64, "\x81\x02")) == 0x101); + testing.expect((try test_read_uleb128(u64, "\x80\xc1\x80\x80\x10")) == 4294975616); + testing.expect((try test_read_uleb128(u64, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01")) == 0x8000000000000000); + + // Decode ULEB128 with extra padding bytes + testing.expect((try test_read_uleb128(u64, "\x80\x00")) == 0); + testing.expect((try test_read_uleb128(u64, "\x80\x80\x00")) == 0); + testing.expect((try test_read_uleb128(u64, "\xff\x00")) == 0x7f); + testing.expect((try test_read_uleb128(u64, "\xff\x80\x00")) == 0x7f); + testing.expect((try test_read_uleb128(u64, "\x80\x81\x00")) == 0x80); + testing.expect((try test_read_uleb128(u64, "\x80\x81\x80\x00")) == 0x80); +} diff --git a/std/dwarf.zig b/std/dwarf.zig index 2cf8ed953e..2f3b29302d 100644 --- a/std/dwarf.zig +++ b/std/dwarf.zig @@ -13,6 +13,7 @@ pub const TAG_reference_type = 0x10; pub const TAG_compile_unit = 0x11; pub const TAG_string_type = 0x12; pub const TAG_structure_type = 0x13; +pub const TAG_subroutine = 0x14; pub const TAG_subroutine_type = 0x15; pub const TAG_typedef = 0x16; pub const TAG_union_type = 0x17; @@ -241,6 +242,9 @@ pub const AT_const_expr = 0x6c; pub const AT_enum_class = 0x6d; pub const AT_linkage_name = 0x6e; +// DWARF 5 +pub const AT_alignment = 0x88; + pub const AT_lo_user = 0x2000; // Implementation-defined range start. pub const AT_hi_user = 0x3fff; // Implementation-defined range end. diff --git a/std/dynamic_library.zig b/std/dynamic_library.zig index 698b0eb216..b2064311db 100644 --- a/std/dynamic_library.zig +++ b/std/dynamic_library.zig @@ -19,6 +19,89 @@ pub const DynLib = switch (builtin.os) { else => void, }; +// The link_map structure is not completely specified beside the fields +// reported below, any libc is free to store additional data in the remaining +// space. +// An iterator is provided in order to traverse the linked list in a idiomatic +// fashion. +const LinkMap = extern struct { + l_addr: usize, + l_name: [*]const u8, + l_ld: ?*elf.Dyn, + l_next: ?*LinkMap, + l_prev: ?*LinkMap, + + pub const Iterator = struct { + current: ?*LinkMap, + + fn end(self: *Iterator) bool { + return self.current == null; + } + + fn next(self: *Iterator) ?*LinkMap { + if (self.current) |it| { + self.current = it.l_next; + return it; + } + return null; + } + }; +}; + +const RDebug = extern struct { + r_version: i32, + r_map: ?*LinkMap, + r_brk: usize, + r_ldbase: usize, +}; + +fn elf_get_va_offset(phdrs: []elf.Phdr) !usize { + for (phdrs) |*phdr| { + if (phdr.p_type == elf.PT_LOAD) { + return @ptrToInt(phdr) - phdr.p_vaddr; + } + } + return error.InvalidExe; +} + +pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator { + const va_offset = try elf_get_va_offset(phdrs); + + const dyn_table = init: { + for (phdrs) |*phdr| { + if (phdr.p_type == elf.PT_DYNAMIC) { + const ptr = @intToPtr([*]elf.Dyn, va_offset + phdr.p_vaddr); + break :init ptr[0..phdr.p_memsz / @sizeOf(elf.Dyn)]; + } + } + // No PT_DYNAMIC means this is either a statically-linked program or a + // badly corrupted one + return LinkMap.Iterator{.current = null}; + }; + + const link_map_ptr = init: { + for (dyn_table) |*dyn| { + switch (dyn.d_tag) { + elf.DT_DEBUG => { + const r_debug = @intToPtr(*RDebug, dyn.d_un.d_ptr); + if (r_debug.r_version != 1) return error.InvalidExe; + break :init r_debug.r_map; + }, + elf.DT_PLTGOT => { + const got_table = @intToPtr([*]usize, dyn.d_un.d_ptr); + // The address to the link_map structure is stored in the + // second slot + break :init @intToPtr(?*LinkMap, got_table[1]); + }, + else => { } + } + } + return error.InvalidExe; + }; + + return LinkMap.Iterator{.current = link_map_ptr}; +} + pub const LinuxDynLib = struct { elf_lib: ElfLib, fd: i32, diff --git a/std/elf.zig b/std/elf.zig index d0ec5fb2ae..49f2f7d137 100644 --- a/std/elf.zig +++ b/std/elf.zig @@ -877,6 +877,11 @@ pub const Phdr = switch (@sizeOf(usize)) { 8 => Elf64_Phdr, else => @compileError("expected pointer size of 32 or 64"), }; +pub const Dyn = switch (@sizeOf(usize)) { + 4 => Elf32_Dyn, + 8 => Elf64_Dyn, + else => @compileError("expected pointer size of 32 or 64"), +}; pub const Shdr = switch (@sizeOf(usize)) { 4 => Elf32_Shdr, 8 => Elf64_Shdr, diff --git a/std/fmt.zig b/std/fmt.zig index 6402271563..e4738d430d 100644 --- a/std/fmt.zig +++ b/std/fmt.zig @@ -8,6 +8,8 @@ const builtin = @import("builtin"); const errol = @import("fmt/errol.zig"); const lossyCast = std.math.lossyCast; +pub const default_max_depth = 3; + /// Renders fmt string with args, calling output with slices of bytes. /// If `output` returns an error, the error is returned from `format` and /// `output` is not called again. @@ -49,7 +51,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), start_index = i; }, '}' => { - try formatType(args[next_arg], fmt[0..0], context, Errors, output); + try formatType(args[next_arg], fmt[0..0], context, Errors, output, default_max_depth); next_arg += 1; state = State.Start; start_index = i + 1; @@ -69,7 +71,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context), State.FormatString => switch (c) { '}' => { const s = start_index + 1; - try formatType(args[next_arg], fmt[s..i], context, Errors, output); + try formatType(args[next_arg], fmt[s..i], context, Errors, output, default_max_depth); next_arg += 1; state = State.Start; start_index = i + 1; @@ -108,6 +110,7 @@ pub fn formatType( context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, + max_depth: usize, ) Errors!void { const T = @typeOf(value); switch (@typeInfo(T)) { @@ -122,16 +125,16 @@ pub fn formatType( }, builtin.TypeId.Optional => { if (value) |payload| { - return formatType(payload, fmt, context, Errors, output); + return formatType(payload, fmt, context, Errors, output, max_depth); } else { return output(context, "null"); } }, builtin.TypeId.ErrorUnion => { if (value) |payload| { - return formatType(payload, fmt, context, Errors, output); + return formatType(payload, fmt, context, Errors, output, max_depth); } else |err| { - return formatType(err, fmt, context, Errors, output); + return formatType(err, fmt, context, Errors, output, max_depth); } }, builtin.TypeId.ErrorSet => { @@ -164,10 +167,13 @@ pub fn formatType( switch (comptime @typeId(T)) { builtin.TypeId.Enum => { try output(context, "."); - try formatType(@tagName(value), "", context, Errors, output); + try formatType(@tagName(value), "", context, Errors, output, max_depth); return; }, builtin.TypeId.Struct => { + if (max_depth == 0) { + return output(context, "{ ... }"); + } comptime var field_i = 0; inline while (field_i < @memberCount(T)) : (field_i += 1) { if (field_i == 0) { @@ -177,11 +183,14 @@ pub fn formatType( } try output(context, @memberName(T, field_i)); try output(context, " = "); - try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output); + try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth-1); } try output(context, " }"); }, builtin.TypeId.Union => { + if (max_depth == 0) { + return output(context, "{ ... }"); + } const info = @typeInfo(T).Union; if (info.tag_type) |UnionTagType| { try output(context, "{ ."); @@ -189,7 +198,7 @@ pub fn formatType( try output(context, " = "); inline for (info.fields) |u_field| { if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) { - try formatType(@field(value, u_field.name), "", context, Errors, output); + try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth-1); } } try output(context, " }"); @@ -210,7 +219,7 @@ pub fn formatType( return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); }, builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { - return formatType(value.*, fmt, context, Errors, output); + return formatType(value.*, fmt, context, Errors, output, max_depth); }, else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)), }, @@ -986,17 +995,17 @@ test "fmt.format" { { var buf1: [32]u8 = undefined; var context = BufPrintContext{ .remaining = buf1[0..] }; - try formatType(1234, "", &context, error{BufferTooSmall}, bufPrintWrite); + try formatType(1234, "", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); var res = buf1[0 .. buf1.len - context.remaining.len]; testing.expect(mem.eql(u8, res, "1234")); context = BufPrintContext{ .remaining = buf1[0..] }; - try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite); + try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); res = buf1[0 .. buf1.len - context.remaining.len]; testing.expect(mem.eql(u8, res, "a")); context = BufPrintContext{ .remaining = buf1[0..] }; - try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite); + try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); res = buf1[0 .. buf1.len - context.remaining.len]; testing.expect(mem.eql(u8, res, "1100")); } @@ -1364,6 +1373,20 @@ test "fmt.format" { try testFmt("E.Two", "{}", inst); } + //self-referential struct format + { + const S = struct { + const SelfType = @This(); + a: ?*SelfType, + }; + + var inst = S{ + .a = null, + }; + inst.a = &inst; + + try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst); + } //print bytes as hex { const some_bytes = "\xCA\xFE\xBA\xBE"; @@ -1449,3 +1472,64 @@ test "fmt.formatIntValue with comptime_int" { try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); assert(mem.eql(u8, buf.toSlice(), "123456789123456789")); } + +test "fmt.formatType max_depth" { + const Vec2 = struct { + const SelfType = @This(); + x: f32, + y: f32, + + pub fn format( + self: SelfType, + comptime fmt: []const u8, + context: var, + comptime Errors: type, + output: fn (@typeOf(context), []const u8) Errors!void, + ) Errors!void { + return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y); + } + }; + const E = enum { + One, + Two, + Three, + }; + const TU = union(enum) { + const SelfType = @This(); + float: f32, + int: u32, + ptr: ?*SelfType, + }; + const S = struct { + const SelfType = @This(); + a: ?*SelfType, + tu: TU, + e: E, + vec: Vec2, + }; + + var inst = S{ + .a = null, + .tu = TU{ .ptr = null }, + .e = E.Two, + .vec = Vec2{ .x = 10.2, .y = 2.22 }, + }; + inst.a = &inst; + inst.tu.ptr = &inst.tu; + + var buf0 = try std.Buffer.init(std.debug.global_allocator, ""); + try formatType(inst, "", &buf0, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0); + assert(mem.eql(u8, buf0.toSlice(), "S{ ... }")); + + var buf1 = try std.Buffer.init(std.debug.global_allocator, ""); + try formatType(inst, "", &buf1, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1); + assert(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); + + var buf2 = try std.Buffer.init(std.debug.global_allocator, ""); + try formatType(inst, "", &buf2, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2); + assert(mem.eql(u8, buf2.toSlice(), "S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }")); + + var buf3 = try std.Buffer.init(std.debug.global_allocator, ""); + try formatType(inst, "", &buf3, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3); + assert(mem.eql(u8, buf3.toSlice(), "S{ .a = S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ .ptr = TU{ ... } } }, .e = E.Two, .vec = (10.200,2.220) }")); +} diff --git a/std/hash_map.zig b/std/hash_map.zig index f4c0b87167..9cd1ea052c 100644 --- a/std/hash_map.zig +++ b/std/hash_map.zig @@ -118,7 +118,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 }; } self.incrementModificationCount(); - try self.ensureCapacity(); + try self.autoCapacity(); const put_result = self.internalPut(key); assert(put_result.old_kv == null); return GetOrPutResult{ @@ -135,15 +135,37 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 return res.kv; } - fn ensureCapacity(self: *Self) !void { - if (self.entries.len == 0) { - return self.initCapacity(16); + fn optimizedCapacity(expected_count: usize) usize { + // ensure that the hash map will be at most 60% full if + // expected_count items are put into it + var optimized_capacity = expected_count * 5 / 3; + // round capacity to the next power of two + const pow = math.log2_int_ceil(usize, optimized_capacity); + return math.pow(usize, 2, pow); + } + + /// Increases capacity so that the hash map will be at most + /// 60% full when expected_count items are put into it + pub fn ensureCapacity(self: *Self, expected_count: usize) !void { + const optimized_capacity = optimizedCapacity(expected_count); + return self.ensureCapacityExact(optimized_capacity); + } + + /// Sets the capacity to the new capacity if the new + /// capacity is greater than the current capacity. + /// New capacity must be a power of two. + fn ensureCapacityExact(self: *Self, new_capacity: usize) !void { + const is_power_of_two = new_capacity & (new_capacity-1) == 0; + assert(is_power_of_two); + + if (new_capacity <= self.entries.len) { + return; } - // if we get too full (60%), double the capacity - if (self.size * 5 >= self.entries.len * 3) { - const old_entries = self.entries; - try self.initCapacity(self.entries.len * 2); + const old_entries = self.entries; + try self.initCapacity(new_capacity); + self.incrementModificationCount(); + if (old_entries.len > 0) { // dump all of the old elements into the new table for (old_entries) |*old_entry| { if (old_entry.used) { @@ -156,8 +178,13 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 /// Returns the kv pair that was already there. pub fn put(self: *Self, key: K, value: V) !?KV { + try self.autoCapacity(); + return putAssumeCapacity(self, key, value); + } + + pub fn putAssumeCapacity(self: *Self, key: K, value: V) ?KV { + assert(self.count() < self.entries.len); self.incrementModificationCount(); - try self.ensureCapacity(); const put_result = self.internalPut(key); put_result.new_entry.kv.value = value; @@ -175,7 +202,7 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 return hm.get(key) != null; } - pub fn remove(hm: *Self, key: K) ?*KV { + pub fn remove(hm: *Self, key: K) ?KV { if (hm.entries.len == 0) return null; hm.incrementModificationCount(); const start_index = hm.keyToIndex(key); @@ -189,13 +216,14 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 if (!eql(entry.kv.key, key)) continue; + const removed_kv = entry.kv; while (roll_over < hm.entries.len) : (roll_over += 1) { const next_index = (start_index + roll_over + 1) % hm.entries.len; const next_entry = &hm.entries[next_index]; if (!next_entry.used or next_entry.distance_from_start_index == 0) { entry.used = false; hm.size -= 1; - return &entry.kv; + return removed_kv; } entry.* = next_entry.*; entry.distance_from_start_index -= 1; @@ -226,6 +254,16 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3 return other; } + fn autoCapacity(self: *Self) !void { + if (self.entries.len == 0) { + return self.ensureCapacityExact(16); + } + // if we get too full (60%), double the capacity + if (self.size * 5 >= self.entries.len * 3) { + return self.ensureCapacityExact(self.entries.len * 2); + } + } + fn initCapacity(hm: *Self, capacity: usize) !void { hm.entries = try hm.allocator.alloc(Entry, capacity); hm.size = 0; @@ -371,7 +409,10 @@ test "basic hash map usage" { testing.expect(map.contains(2)); testing.expect(map.get(2).?.value == 22); - _ = map.remove(2); + + const rmv1 = map.remove(2); + testing.expect(rmv1.?.key == 2); + testing.expect(rmv1.?.value == 22); testing.expect(map.remove(2) == null); testing.expect(map.get(2) == null); } @@ -423,6 +464,24 @@ test "iterator hash map" { testing.expect(entry.value == values[0]); } +test "ensure capacity" { + var direct_allocator = std.heap.DirectAllocator.init(); + defer direct_allocator.deinit(); + + var map = AutoHashMap(i32, i32).init(&direct_allocator.allocator); + defer map.deinit(); + + try map.ensureCapacity(20); + const initialCapacity = map.entries.len; + testing.expect(initialCapacity >= 20); + var i : i32 = 0; + while (i < 20) : (i += 1) { + testing.expect(map.putAssumeCapacity(i, i+10) == null); + } + // shouldn't resize from putAssumeCapacity + testing.expect(initialCapacity == map.entries.len); +} + pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) { return struct { fn hash(key: K) u32 { diff --git a/std/heap.zig b/std/heap.zig index dd5cfa4cd7..8e3cccc365 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -34,9 +34,6 @@ fn cShrink(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new /// Thread-safe and lock-free. pub const DirectAllocator = struct { allocator: Allocator, - heap_handle: ?HeapHandle, - - const HeapHandle = if (builtin.os == Os.windows) os.windows.HANDLE else void; pub fn init() DirectAllocator { return DirectAllocator{ @@ -44,21 +41,15 @@ pub const DirectAllocator = struct { .reallocFn = realloc, .shrinkFn = shrink, }, - .heap_handle = if (builtin.os == Os.windows) null else {}, }; } - pub fn deinit(self: *DirectAllocator) void { - switch (builtin.os) { - Os.windows => if (self.heap_handle) |heap_handle| { - _ = os.windows.HeapDestroy(heap_handle); - }, - else => {}, - } - } + pub fn deinit(self: *DirectAllocator) void {} fn alloc(allocator: *Allocator, n: usize, alignment: u29) error{OutOfMemory}![]u8 { const self = @fieldParentPtr(DirectAllocator, "allocator", allocator); + if (n == 0) + return (([*]u8)(undefined))[0..0]; switch (builtin.os) { Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { @@ -68,39 +59,76 @@ pub const DirectAllocator = struct { if (addr == p.MAP_FAILED) return error.OutOfMemory; if (alloc_size == n) return @intToPtr([*]u8, addr)[0..n]; - const aligned_addr = (addr & ~usize(alignment - 1)) + alignment; - - // We can unmap the unused portions of our mmap, but we must only - // pass munmap bytes that exist outside our allocated pages or it - // will happily eat us too. - - // Since alignment > page_size, we are by definition on a page boundary. - const unused_start = addr; - const unused_len = aligned_addr - 1 - unused_start; - - const err = p.munmap(unused_start, unused_len); - assert(p.getErrno(err) == 0); + const aligned_addr = mem.alignForward(addr, alignment); - // It is impossible that there is an unoccupied page at the top of our - // mmap. + // Unmap the extra bytes that were only requested in order to guarantee + // that the range of memory we were provided had a proper alignment in + // it somewhere. The extra bytes could be at the beginning, or end, or both. + const unused_start_len = aligned_addr - addr; + if (unused_start_len != 0) { + const err = p.munmap(addr, unused_start_len); + assert(p.getErrno(err) == 0); + } + const aligned_end_addr = std.mem.alignForward(aligned_addr + n, os.page_size); + const unused_end_len = addr + alloc_size - aligned_end_addr; + if (unused_end_len != 0) { + const err = p.munmap(aligned_end_addr, unused_end_len); + assert(p.getErrno(err) == 0); + } return @intToPtr([*]u8, aligned_addr)[0..n]; }, - Os.windows => { - const amt = n + alignment + @sizeOf(usize); - const optional_heap_handle = @atomicLoad(?HeapHandle, &self.heap_handle, builtin.AtomicOrder.SeqCst); - const heap_handle = optional_heap_handle orelse blk: { - const hh = os.windows.HeapCreate(0, amt, 0) orelse return error.OutOfMemory; - const other_hh = @cmpxchgStrong(?HeapHandle, &self.heap_handle, null, hh, builtin.AtomicOrder.SeqCst, builtin.AtomicOrder.SeqCst) orelse break :blk hh; - _ = os.windows.HeapDestroy(hh); - break :blk other_hh.?; // can't be null because of the cmpxchg - }; - const ptr = os.windows.HeapAlloc(heap_handle, 0, amt) orelse return error.OutOfMemory; - const root_addr = @ptrToInt(ptr); - const adjusted_addr = mem.alignForward(root_addr, alignment); - const record_addr = adjusted_addr + n; - @intToPtr(*align(1) usize, record_addr).* = root_addr; - return @intToPtr([*]u8, adjusted_addr)[0..n]; + .windows => { + const w = os.windows; + + // Although officially it's at least aligned to page boundary, + // Windows is known to reserve pages on a 64K boundary. It's + // even more likely that the requested alignment is <= 64K than + // 4K, so we're just allocating blindly and hoping for the best. + // see https://devblogs.microsoft.com/oldnewthing/?p=42223 + const addr = w.VirtualAlloc( + null, + n, + w.MEM_COMMIT | w.MEM_RESERVE, + w.PAGE_READWRITE, + ) orelse return error.OutOfMemory; + + // If the allocation is sufficiently aligned, use it. + if (@ptrToInt(addr) & (alignment - 1) == 0) { + return @ptrCast([*]u8, addr)[0..n]; + } + + // If it wasn't, actually do an explicitely aligned allocation. + if (w.VirtualFree(addr, 0, w.MEM_RELEASE) == 0) unreachable; + const alloc_size = n + alignment; + + const final_addr = while (true) { + // Reserve a range of memory large enough to find a sufficiently + // aligned address. + const reserved_addr = w.VirtualAlloc( + null, + alloc_size, + w.MEM_RESERVE, + w.PAGE_NOACCESS, + ) orelse return error.OutOfMemory; + const aligned_addr = mem.alignForward(@ptrToInt(reserved_addr), alignment); + + // Release the reserved pages (not actually used). + if (w.VirtualFree(reserved_addr, 0, w.MEM_RELEASE) == 0) unreachable; + + // At this point, it is possible that another thread has + // obtained some memory space that will cause the next + // VirtualAlloc call to fail. To handle this, we will retry + // until it succeeds. + if (w.VirtualAlloc( + @intToPtr(*c_void, aligned_addr), + n, + w.MEM_COMMIT | w.MEM_RESERVE, + w.PAGE_READWRITE, + )) |ptr| break ptr; + } else unreachable; // TODO else unreachable should not be necessary + + return @ptrCast([*]u8, final_addr)[0..n]; }, else => @compileError("Unsupported OS"), } @@ -118,13 +146,31 @@ pub const DirectAllocator = struct { } return old_mem[0..new_size]; }, - Os.windows => return realloc(allocator, old_mem, old_align, new_size, new_align) catch { - const old_adjusted_addr = @ptrToInt(old_mem.ptr); - const old_record_addr = old_adjusted_addr + old_mem.len; - const root_addr = @intToPtr(*align(1) usize, old_record_addr).*; - const old_ptr = @intToPtr(*c_void, root_addr); - const new_record_addr = old_record_addr - new_size + old_mem.len; - @intToPtr(*align(1) usize, new_record_addr).* = root_addr; + .windows => { + const w = os.windows; + if (new_size == 0) { + // From the docs: + // "If the dwFreeType parameter is MEM_RELEASE, this parameter + // must be 0 (zero). The function frees the entire region that + // is reserved in the initial allocation call to VirtualAlloc." + // So we can only use MEM_RELEASE when actually releasing the + // whole allocation. + if (w.VirtualFree(old_mem.ptr, 0, w.MEM_RELEASE) == 0) unreachable; + } else { + const base_addr = @ptrToInt(old_mem.ptr); + const old_addr_end = base_addr + old_mem.len; + const new_addr_end = base_addr + new_size; + const new_addr_end_rounded = mem.alignForward(new_addr_end, os.page_size); + if (old_addr_end > new_addr_end_rounded) { + // For shrinking that is not releasing, we will only + // decommit the pages not needed anymore. + if (w.VirtualFree( + @intToPtr(*c_void, new_addr_end_rounded), + old_addr_end - new_addr_end_rounded, + w.MEM_DECOMMIT, + ) == 0) unreachable; + } + } return old_mem[0..new_size]; }, else => @compileError("Unsupported OS"), @@ -138,36 +184,168 @@ pub const DirectAllocator = struct { return shrink(allocator, old_mem, old_align, new_size, new_align); } const result = try alloc(allocator, new_size, new_align); - mem.copy(u8, result, old_mem); - _ = os.posix.munmap(@ptrToInt(old_mem.ptr), old_mem.len); + if (old_mem.len != 0) { + @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); + _ = os.posix.munmap(@ptrToInt(old_mem.ptr), old_mem.len); + } return result; }, - Os.windows => { - if (old_mem.len == 0) return alloc(allocator, new_size, new_align); + .windows => { + if (old_mem.len == 0) { + return alloc(allocator, new_size, new_align); + } + + if (new_size <= old_mem.len and new_align <= old_align) { + return shrink(allocator, old_mem, old_align, new_size, new_align); + } + + const w = os.windows; + const base_addr = @ptrToInt(old_mem.ptr); + + if (new_align > old_align and base_addr & (new_align - 1) != 0) { + // Current allocation doesn't satisfy the new alignment. + // For now we'll do a new one no matter what, but maybe + // there is something smarter to do instead. + const result = try alloc(allocator, new_size, new_align); + assert(old_mem.len != 0); + @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); + if (w.VirtualFree(old_mem.ptr, 0, w.MEM_RELEASE) == 0) unreachable; - const self = @fieldParentPtr(DirectAllocator, "allocator", allocator); + return result; + } + + const old_addr_end = base_addr + old_mem.len; + const old_addr_end_rounded = mem.alignForward(old_addr_end, os.page_size); + const new_addr_end = base_addr + new_size; + const new_addr_end_rounded = mem.alignForward(new_addr_end, os.page_size); + if (new_addr_end_rounded == old_addr_end_rounded) { + // The reallocation fits in the already allocated pages. + return @ptrCast([*]u8, old_mem.ptr)[0..new_size]; + } + assert(new_addr_end_rounded > old_addr_end_rounded); + + // We need to commit new pages. + const additional_size = new_addr_end - old_addr_end_rounded; + const realloc_addr = w.VirtualAlloc( + @intToPtr(*c_void, old_addr_end_rounded), + additional_size, + w.MEM_COMMIT | w.MEM_RESERVE, + w.PAGE_READWRITE, + ) orelse { + // Committing new pages at the end of the existing allocation + // failed, we need to try a new one. + const new_alloc_mem = try alloc(allocator, new_size, new_align); + @memcpy(new_alloc_mem.ptr, old_mem.ptr, old_mem.len); + if (w.VirtualFree(old_mem.ptr, 0, w.MEM_RELEASE) == 0) unreachable; + + return new_alloc_mem; + }; + + assert(@ptrToInt(realloc_addr) == old_addr_end_rounded); + return @ptrCast([*]u8, old_mem.ptr)[0..new_size]; + }, + else => @compileError("Unsupported OS"), + } + } +}; + +pub const HeapAllocator = switch (builtin.os) { + .windows => struct { + allocator: Allocator, + heap_handle: ?HeapHandle, + + const HeapHandle = os.windows.HANDLE; + + pub fn init() HeapAllocator { + return HeapAllocator{ + .allocator = Allocator{ + .reallocFn = realloc, + .shrinkFn = shrink, + }, + .heap_handle = null, + }; + } + + pub fn deinit(self: *HeapAllocator) void { + if (self.heap_handle) |heap_handle| { + _ = os.windows.HeapDestroy(heap_handle); + } + } + + fn alloc(allocator: *Allocator, n: usize, alignment: u29) error{OutOfMemory}![]u8 { + const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); + if (n == 0) + return (([*]u8)(undefined))[0..0]; + + const amt = n + alignment + @sizeOf(usize); + const optional_heap_handle = @atomicLoad(?HeapHandle, &self.heap_handle, builtin.AtomicOrder.SeqCst); + const heap_handle = optional_heap_handle orelse blk: { + const options = if (builtin.single_threaded) os.windows.HEAP_NO_SERIALIZE else 0; + const hh = os.windows.HeapCreate(options, amt, 0) orelse return error.OutOfMemory; + const other_hh = @cmpxchgStrong(?HeapHandle, &self.heap_handle, null, hh, builtin.AtomicOrder.SeqCst, builtin.AtomicOrder.SeqCst) orelse break :blk hh; + _ = os.windows.HeapDestroy(hh); + break :blk other_hh.?; // can't be null because of the cmpxchg + }; + const ptr = os.windows.HeapAlloc(heap_handle, 0, amt) orelse return error.OutOfMemory; + const root_addr = @ptrToInt(ptr); + const adjusted_addr = mem.alignForward(root_addr, alignment); + const record_addr = adjusted_addr + n; + @intToPtr(*align(1) usize, record_addr).* = root_addr; + return @intToPtr([*]u8, adjusted_addr)[0..n]; + } + + fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { + return realloc(allocator, old_mem, old_align, new_size, new_align) catch { const old_adjusted_addr = @ptrToInt(old_mem.ptr); const old_record_addr = old_adjusted_addr + old_mem.len; const root_addr = @intToPtr(*align(1) usize, old_record_addr).*; const old_ptr = @intToPtr(*c_void, root_addr); - const amt = new_size + new_align + @sizeOf(usize); - const new_ptr = os.windows.HeapReAlloc( - self.heap_handle.?, - 0, - old_ptr, - amt, - ) orelse return error.OutOfMemory; - const offset = old_adjusted_addr - root_addr; - const new_root_addr = @ptrToInt(new_ptr); - const new_adjusted_addr = new_root_addr + offset; - assert(new_adjusted_addr % new_align == 0); - const new_record_addr = new_adjusted_addr + new_size; - @intToPtr(*align(1) usize, new_record_addr).* = new_root_addr; - return @intToPtr([*]u8, new_adjusted_addr)[0..new_size]; - }, - else => @compileError("Unsupported OS"), + const new_record_addr = old_record_addr - new_size + old_mem.len; + @intToPtr(*align(1) usize, new_record_addr).* = root_addr; + return old_mem[0..new_size]; + }; } - } + + fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { + if (old_mem.len == 0) return alloc(allocator, new_size, new_align); + + const self = @fieldParentPtr(HeapAllocator, "allocator", allocator); + const old_adjusted_addr = @ptrToInt(old_mem.ptr); + const old_record_addr = old_adjusted_addr + old_mem.len; + const root_addr = @intToPtr(*align(1) usize, old_record_addr).*; + const old_ptr = @intToPtr(*c_void, root_addr); + + if (new_size == 0) { + if (os.windows.HeapFree(self.heap_handle.?, 0, old_ptr) == 0) unreachable; + return old_mem[0..0]; + } + + const amt = new_size + new_align + @sizeOf(usize); + const new_ptr = os.windows.HeapReAlloc( + self.heap_handle.?, + 0, + old_ptr, + amt, + ) orelse return error.OutOfMemory; + const offset = old_adjusted_addr - root_addr; + const new_root_addr = @ptrToInt(new_ptr); + var new_adjusted_addr = new_root_addr + offset; + const offset_is_valid = new_adjusted_addr + new_size + @sizeOf(usize) <= new_root_addr + amt; + const offset_is_aligned = new_adjusted_addr % new_align == 0; + if (!offset_is_valid or !offset_is_aligned) { + // If HeapReAlloc didn't happen to move the memory to the new alignment, + // or the memory starting at the old offset would be outside of the new allocation, + // then we need to copy the memory to a valid aligned address and use that + const new_aligned_addr = mem.alignForward(new_root_addr, new_align); + @memcpy(@intToPtr([*]u8, new_aligned_addr), @intToPtr([*]u8, new_adjusted_addr), std.math.min(old_mem.len, new_size)); + new_adjusted_addr = new_aligned_addr; + } + const new_record_addr = new_adjusted_addr + new_size; + @intToPtr(*align(1) usize, new_record_addr).* = new_root_addr; + return @intToPtr([*]u8, new_adjusted_addr)[0..new_size]; + } + }, + else => @compileError("Unsupported OS"), }; /// This allocator takes an existing allocator, wraps it, and provides an interface @@ -250,7 +428,7 @@ pub const ArenaAllocator = struct { return error.OutOfMemory; } else { const result = try alloc(allocator, new_size, new_align); - mem.copy(u8, result, old_mem); + @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); return result; } } @@ -308,6 +486,103 @@ pub const FixedBufferAllocator = struct { return error.OutOfMemory; } else { const result = try alloc(allocator, new_size, new_align); + @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); + return result; + } + } + + fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { + return old_mem[0..new_size]; + } +}; + +// FIXME: Exposed LLVM intrinsics is a bug +// See: https://github.com/ziglang/zig/issues/2291 +extern fn @"llvm.wasm.memory.size.i32"(u32) u32; +extern fn @"llvm.wasm.memory.grow.i32"(u32, u32) i32; + +pub const wasm_allocator = &wasm_allocator_state.allocator; +var wasm_allocator_state = WasmAllocator{ + .allocator = Allocator{ + .reallocFn = WasmAllocator.realloc, + .shrinkFn = WasmAllocator.shrink, + }, + .start_ptr = undefined, + .num_pages = 0, + .end_index = 0, +}; + +const WasmAllocator = struct { + allocator: Allocator, + start_ptr: [*]u8, + num_pages: usize, + end_index: usize, + + comptime { + if (builtin.arch != .wasm32) { + @compileError("WasmAllocator is only available for wasm32 arch"); + } + } + + fn alloc(allocator: *Allocator, size: usize, alignment: u29) ![]u8 { + const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); + + const addr = @ptrToInt(self.start_ptr) + self.end_index; + const adjusted_addr = mem.alignForward(addr, alignment); + const adjusted_index = self.end_index + (adjusted_addr - addr); + const new_end_index = adjusted_index + size; + + if (new_end_index > self.num_pages * os.page_size) { + const required_memory = new_end_index - (self.num_pages * os.page_size); + + var num_pages: usize = required_memory / os.page_size; + if (required_memory % os.page_size != 0) { + num_pages += 1; + } + + const prev_page = @"llvm.wasm.memory.grow.i32"(0, @intCast(u32, num_pages)); + if (prev_page == -1) { + return error.OutOfMemory; + } + + self.num_pages += num_pages; + } + + const result = self.start_ptr[adjusted_index..new_end_index]; + self.end_index = new_end_index; + + return result; + } + + // Check if memory is the last "item" and is aligned correctly + fn is_last_item(allocator: *Allocator, memory: []u8, alignment: u29) bool { + const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); + return memory.ptr == self.start_ptr + self.end_index - memory.len and mem.alignForward(@ptrToInt(memory.ptr), alignment) == @ptrToInt(memory.ptr); + } + + fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { + const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); + + // Initialize start_ptr at the first realloc + if (self.num_pages == 0) { + self.start_ptr = @intToPtr([*]u8, @intCast(usize, @"llvm.wasm.memory.size.i32"(0)) * os.page_size); + } + + if (is_last_item(allocator, old_mem, new_align)) { + const start_index = self.end_index - old_mem.len; + const new_end_index = start_index + new_size; + + if (new_end_index > self.num_pages * os.page_size) { + _ = try alloc(allocator, new_end_index - self.end_index, new_align); + } + const result = self.start_ptr[start_index..new_end_index]; + + self.end_index = new_end_index; + return result; + } else if (new_size <= old_mem.len and new_align <= old_align) { + return error.OutOfMemory; + } else { + const result = try alloc(allocator, new_size, new_align); mem.copy(u8, result, old_mem); return result; } @@ -360,7 +635,7 @@ pub const ThreadSafeFixedBufferAllocator = blk: { return error.OutOfMemory; } else { const result = try alloc(allocator, new_size, new_align); - mem.copy(u8, result, old_mem); + @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len)); return result; } } @@ -470,6 +745,31 @@ test "DirectAllocator" { try testAllocator(allocator); try testAllocatorAligned(allocator, 16); try testAllocatorLargeAlignment(allocator); + try testAllocatorAlignedShrink(allocator); + + if (builtin.os == .windows) { + // Trying really large alignment. As mentionned in the implementation, + // VirtualAlloc returns 64K aligned addresses. We want to make sure + // DirectAllocator works beyond that, as it's not tested by + // `testAllocatorLargeAlignment`. + const slice = try allocator.alignedAlloc(u8, 1 << 20, 128); + slice[0] = 0x12; + slice[127] = 0x34; + allocator.free(slice); + } +} + +test "HeapAllocator" { + if (builtin.os == .windows) { + var heap_allocator = HeapAllocator.init(); + defer heap_allocator.deinit(); + + const allocator = &heap_allocator.allocator; + try testAllocator(allocator); + try testAllocatorAligned(allocator, 16); + try testAllocatorLargeAlignment(allocator); + try testAllocatorAlignedShrink(allocator); + } } test "ArenaAllocator" { @@ -482,15 +782,17 @@ test "ArenaAllocator" { try testAllocator(&arena_allocator.allocator); try testAllocatorAligned(&arena_allocator.allocator, 16); try testAllocatorLargeAlignment(&arena_allocator.allocator); + try testAllocatorAlignedShrink(&arena_allocator.allocator); } -var test_fixed_buffer_allocator_memory: [30000 * @sizeOf(usize)]u8 = undefined; +var test_fixed_buffer_allocator_memory: [80000 * @sizeOf(u64)]u8 = undefined; test "FixedBufferAllocator" { var fixed_buffer_allocator = FixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..]); try testAllocator(&fixed_buffer_allocator.allocator); try testAllocatorAligned(&fixed_buffer_allocator.allocator, 16); try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator); + try testAllocatorAlignedShrink(&fixed_buffer_allocator.allocator); } test "FixedBufferAllocator Reuse memory on realloc" { @@ -528,6 +830,7 @@ test "ThreadSafeFixedBufferAllocator" { try testAllocator(&fixed_buffer_allocator.allocator); try testAllocatorAligned(&fixed_buffer_allocator.allocator, 16); try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator); + try testAllocatorAlignedShrink(&fixed_buffer_allocator.allocator); } fn testAllocator(allocator: *mem.Allocator) !void { @@ -610,3 +913,32 @@ fn testAllocatorLargeAlignment(allocator: *mem.Allocator) mem.Allocator.Error!vo allocator.free(slice); } + +fn testAllocatorAlignedShrink(allocator: *mem.Allocator) mem.Allocator.Error!void { + var debug_buffer: [1000]u8 = undefined; + const debug_allocator = &FixedBufferAllocator.init(&debug_buffer).allocator; + + const alloc_size = os.page_size * 2 + 50; + var slice = try allocator.alignedAlloc(u8, 16, alloc_size); + defer allocator.free(slice); + + var stuff_to_free = std.ArrayList([]align(16) u8).init(debug_allocator); + // On Windows, VirtualAlloc returns addresses aligned to a 64K boundary, + // which is 16 pages, hence the 32. This test may require to increase + // the size of the allocations feeding the `allocator` parameter if they + // fail, because of this high over-alignment we want to have. + while (@ptrToInt(slice.ptr) == mem.alignForward(@ptrToInt(slice.ptr), os.page_size * 32)) { + try stuff_to_free.append(slice); + slice = try allocator.alignedAlloc(u8, 16, alloc_size); + } + while (stuff_to_free.popOrNull()) |item| { + allocator.free(item); + } + slice[0] = 0x12; + slice[60] = 0x34; + + // realloc to a smaller size but with a larger alignment + slice = try allocator.alignedRealloc(slice, os.page_size * 32, alloc_size / 2); + testing.expect(slice[0] == 0x12); + testing.expect(slice[60] == 0x34); +} diff --git a/std/io.zig b/std/io.zig index afbd8198fd..1f363b47b1 100644 --- a/std/io.zig +++ b/std/io.zig @@ -36,6 +36,7 @@ pub fn getStdIn() GetStdIoErrs!File { } pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; +pub const COutStream = @import("io/c_out_stream.zig").COutStream; pub fn InStream(comptime ReadError: type) type { return struct { @@ -194,8 +195,8 @@ pub fn InStream(comptime ReadError: type) type { return mem.readVarInt(ReturnType, bytes, endian); } - pub fn skipBytes(self: *Self, num_bytes: usize) !void { - var i: usize = 0; + pub fn skipBytes(self: *Self, num_bytes: u64) !void { + var i: u64 = 0; while (i < num_bytes) : (i += 1) { _ = try self.readByte(); } @@ -289,7 +290,7 @@ pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptim var file = try File.openRead(path); defer file.close(); - const size = try file.getEndPos(); + const size = try math.cast(usize, try file.getEndPos()); const buf = try allocator.alignedAlloc(u8, A, size); errdefer allocator.free(buf); @@ -742,7 +743,7 @@ pub fn CountingOutStream(comptime OutStreamError: type) type { pub const Error = OutStreamError; pub stream: Stream, - pub bytes_written: usize, + pub bytes_written: u64, child_stream: *Stream, pub fn init(child_stream: *Stream) Self { @@ -1089,8 +1090,11 @@ test "io.readLineSliceFrom" { } pub const Packing = enum { - Byte, /// Pack data to byte alignment - Bit, /// Pack data to bit alignment + /// Pack data to byte alignment + Byte, + + /// Pack data to bit alignment + Bit, }; /// Creates a deserializer that deserializes types from any stream. @@ -1111,10 +1115,12 @@ pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, pub const Stream = InStream(Error); pub fn init(in_stream: *Stream) Self { - return Self{ .in_stream = switch (packing) { - .Bit => BitInStream(endian, Stream.Error).init(in_stream), - .Byte => in_stream, - } }; + return Self{ + .in_stream = switch (packing) { + .Bit => BitInStream(endian, Stream.Error).init(in_stream), + .Byte => in_stream, + }, + }; } pub fn alignToByte(self: *Self) void { @@ -1281,7 +1287,7 @@ pub fn Deserializer(comptime endian: builtin.Endian, comptime packing: Packing, ptr.* = null; return; } - + ptr.* = OC(undefined); //make it non-null so the following .? is guaranteed safe const val_ptr = &ptr.*.?; try self.deserializeInto(val_ptr); @@ -1320,10 +1326,12 @@ pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, co pub const Stream = OutStream(Error); pub fn init(out_stream: *Stream) Self { - return Self{ .out_stream = switch (packing) { - .Bit => BitOutStream(endian, Stream.Error).init(out_stream), - .Byte => out_stream, - } }; + return Self{ + .out_stream = switch (packing) { + .Bit => BitOutStream(endian, Stream.Error).init(out_stream), + .Byte => out_stream, + }, + }; } /// Flushes any unwritten bits to the stream @@ -1447,7 +1455,6 @@ pub fn Serializer(comptime endian: builtin.Endian, comptime packing: Packing, co test "import io tests" { comptime { - _ = @import("io_test.zig"); + _ = @import("io/test.zig"); } } - diff --git a/std/io/c_out_stream.zig b/std/io/c_out_stream.zig new file mode 100644 index 0000000000..c66b342f1e --- /dev/null +++ b/std/io/c_out_stream.zig @@ -0,0 +1,48 @@ +const std = @import("../std.zig"); +const OutStream = std.io.OutStream; +const builtin = @import("builtin"); +const posix = std.os.posix; + +/// TODO make std.os.FILE use *FILE when linking libc and this just becomes +/// std.io.FileOutStream because std.os.File.write would do this when linking +/// libc. +pub const COutStream = struct { + pub const Error = std.os.File.WriteError; + pub const Stream = OutStream(Error); + + stream: Stream, + c_file: *std.c.FILE, + + pub fn init(c_file: *std.c.FILE) COutStream { + return COutStream{ + .c_file = c_file, + .stream = Stream{ .writeFn = writeFn }, + }; + } + + fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { + const self = @fieldParentPtr(COutStream, "stream", out_stream); + const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, self.c_file); + if (amt_written == bytes.len) return; + // TODO errno on windows. should we have a posix layer for windows? + if (builtin.os == .windows) { + return error.InputOutput; + } + const errno = std.c._errno().*; + switch (errno) { + 0 => unreachable, + posix.EINVAL => unreachable, + posix.EFAULT => unreachable, + posix.EAGAIN => unreachable, // this is a blocking API + posix.EBADF => unreachable, // always a race condition + posix.EDESTADDRREQ => unreachable, // connect was never called + posix.EDQUOT => return error.DiskQuota, + posix.EFBIG => return error.FileTooBig, + posix.EIO => return error.InputOutput, + posix.ENOSPC => return error.NoSpaceLeft, + posix.EPERM => return error.AccessDenied, + posix.EPIPE => return error.BrokenPipe, + else => return std.os.unexpectedErrorPosix(@intCast(usize, errno)), + } + } +}; diff --git a/std/io/seekable_stream.zig b/std/io/seekable_stream.zig index 5529e42ff1..baf479891c 100644 --- a/std/io/seekable_stream.zig +++ b/std/io/seekable_stream.zig @@ -7,25 +7,25 @@ pub fn SeekableStream(comptime SeekErrorType: type, comptime GetSeekPosErrorType pub const SeekError = SeekErrorType; pub const GetSeekPosError = GetSeekPosErrorType; - seekToFn: fn (self: *Self, pos: usize) SeekError!void, - seekForwardFn: fn (self: *Self, pos: isize) SeekError!void, + seekToFn: fn (self: *Self, pos: u64) SeekError!void, + seekForwardFn: fn (self: *Self, pos: i64) SeekError!void, - getPosFn: fn (self: *Self) GetSeekPosError!usize, - getEndPosFn: fn (self: *Self) GetSeekPosError!usize, + getPosFn: fn (self: *Self) GetSeekPosError!u64, + getEndPosFn: fn (self: *Self) GetSeekPosError!u64, - pub fn seekTo(self: *Self, pos: usize) SeekError!void { + pub fn seekTo(self: *Self, pos: u64) SeekError!void { return self.seekToFn(self, pos); } - pub fn seekForward(self: *Self, amt: isize) SeekError!void { + pub fn seekForward(self: *Self, amt: i64) SeekError!void { return self.seekForwardFn(self, amt); } - pub fn getEndPos(self: *Self) GetSeekPosError!usize { + pub fn getEndPos(self: *Self) GetSeekPosError!u64 { return self.getEndPosFn(self); } - pub fn getPos(self: *Self) GetSeekPosError!usize { + pub fn getPos(self: *Self) GetSeekPosError!u64 { return self.getPosFn(self); } }; diff --git a/std/io_test.zig b/std/io/test.zig index d6f2264a56..07a3c0e8dd 100644 --- a/std/io_test.zig +++ b/std/io/test.zig @@ -1,4 +1,4 @@ -const std = @import("std.zig"); +const std = @import("../std.zig"); const io = std.io; const meta = std.meta; const trait = std.trait; @@ -589,3 +589,14 @@ test "Deserializer bad data" { try testBadData(.Big, .Bit); try testBadData(.Little, .Bit); } + +test "c out stream" { + if (!builtin.link_libc) return error.SkipZigTest; + + const filename = c"tmp_io_test_file.txt"; + const out_file = std.c.fopen(filename, c"w") orelse return error.UnableToOpenTestFile; + defer std.os.deleteFileC(filename) catch {}; + + const out_stream = &io.COutStream.init(out_file).stream; + try out_stream.print("hi: {}\n", i32(123)); +} diff --git a/std/json.zig b/std/json.zig index 551c3a8da7..8d42d1bcf0 100644 --- a/std/json.zig +++ b/std/json.zig @@ -1400,3 +1400,7 @@ test "json.parser.dynamic" { const double = image.Object.get("double").?.value; testing.expect(double.Float == 1.3412); } + +test "import more json tests" { + _ = @import("json/test.zig"); +} diff --git a/std/json/test.zig b/std/json/test.zig new file mode 100644 index 0000000000..7c89dcd123 --- /dev/null +++ b/std/json/test.zig @@ -0,0 +1,1904 @@ +// RFC 8529 conformance tests. +// +// Tests are taken from https://github.com/nst/JSONTestSuite +// Read also http://seriot.ch/parsing_json.php for a good overview. + +const std = @import("../std.zig"); + +fn ok(comptime s: []const u8) void { + std.testing.expect(std.json.validate(s)); +} + +fn err(comptime s: []const u8) void { + std.testing.expect(!std.json.validate(s)); +} + +fn any(comptime s: []const u8) void { + std.testing.expect(true); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Additional tests not part of test JSONTestSuite. + +test "y_trailing_comma_after_empty" { + ok( + \\{"1":[],"2":{},"3":"4"} + ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +test "y_array_arraysWithSpaces" { + ok( + \\[[] ] + ); +} + +test "y_array_empty" { + ok( + \\[] + ); +} + +test "y_array_empty-string" { + ok( + \\[""] + ); +} + +test "y_array_ending_with_newline" { + ok( + \\["a"] + ); +} + +test "y_array_false" { + ok( + \\[false] + ); +} + +test "y_array_heterogeneous" { + ok( + \\[null, 1, "1", {}] + ); +} + +test "y_array_null" { + ok( + \\[null] + ); +} + +test "y_array_with_1_and_newline" { + ok( + \\[1 + \\] + ); +} + +test "y_array_with_leading_space" { + ok( + \\ [1] + ); +} + +test "y_array_with_several_null" { + ok( + \\[1,null,null,null,2] + ); +} + +test "y_array_with_trailing_space" { + ok("[2] "); +} + +test "y_number_0e+1" { + ok( + \\[0e+1] + ); +} + +test "y_number_0e1" { + ok( + \\[0e1] + ); +} + +test "y_number_after_space" { + ok( + \\[ 4] + ); +} + +test "y_number_double_close_to_zero" { + ok( + \\[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] + ); +} + +test "y_number_int_with_exp" { + ok( + \\[20e1] + ); +} + +test "y_number" { + ok( + \\[123e65] + ); +} + +test "y_number_minus_zero" { + ok( + \\[-0] + ); +} + +test "y_number_negative_int" { + ok( + \\[-123] + ); +} + +test "y_number_negative_one" { + ok( + \\[-1] + ); +} + +test "y_number_negative_zero" { + ok( + \\[-0] + ); +} + +test "y_number_real_capital_e" { + ok( + \\[1E22] + ); +} + +test "y_number_real_capital_e_neg_exp" { + ok( + \\[1E-2] + ); +} + +test "y_number_real_capital_e_pos_exp" { + ok( + \\[1E+2] + ); +} + +test "y_number_real_exponent" { + ok( + \\[123e45] + ); +} + +test "y_number_real_fraction_exponent" { + ok( + \\[123.456e78] + ); +} + +test "y_number_real_neg_exp" { + ok( + \\[1e-2] + ); +} + +test "y_number_real_pos_exponent" { + ok( + \\[1e+2] + ); +} + +test "y_number_simple_int" { + ok( + \\[123] + ); +} + +test "y_number_simple_real" { + ok( + \\[123.456789] + ); +} + +test "y_object_basic" { + ok( + \\{"asd":"sdf"} + ); +} + +test "y_object_duplicated_key_and_value" { + ok( + \\{"a":"b","a":"b"} + ); +} + +test "y_object_duplicated_key" { + ok( + \\{"a":"b","a":"c"} + ); +} + +test "y_object_empty" { + ok( + \\{} + ); +} + +test "y_object_empty_key" { + ok( + \\{"":0} + ); +} + +test "y_object_escaped_null_in_key" { + ok( + \\{"foo\u0000bar": 42} + ); +} + +test "y_object_extreme_numbers" { + ok( + \\{ "min": -1.0e+28, "max": 1.0e+28 } + ); +} + +test "y_object" { + ok( + \\{"asd":"sdf", "dfg":"fgh"} + ); +} + +test "y_object_long_strings" { + ok( + \\{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} + ); +} + +test "y_object_simple" { + ok( + \\{"a":[]} + ); +} + +test "y_object_string_unicode" { + ok( + \\{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" } + ); +} + +test "y_object_with_newlines" { + ok( + \\{ + \\"a": "b" + \\} + ); +} + +test "y_string_1_2_3_bytes_UTF-8_sequences" { + ok( + \\["\u0060\u012a\u12AB"] + ); +} + +test "y_string_accepted_surrogate_pair" { + ok( + \\["\uD801\udc37"] + ); +} + +test "y_string_accepted_surrogate_pairs" { + ok( + \\["\ud83d\ude39\ud83d\udc8d"] + ); +} + +test "y_string_allowed_escapes" { + ok( + \\["\"\\\/\b\f\n\r\t"] + ); +} + +test "y_string_backslash_and_u_escaped_zero" { + ok( + \\["\\u0000"] + ); +} + +test "y_string_backslash_doublequotes" { + ok( + \\["\""] + ); +} + +test "y_string_comments" { + ok( + \\["a/*b*/c/*d//e"] + ); +} + +test "y_string_double_escape_a" { + ok( + \\["\\a"] + ); +} + +test "y_string_double_escape_n" { + ok( + \\["\\n"] + ); +} + +test "y_string_escaped_control_character" { + ok( + \\["\u0012"] + ); +} + +test "y_string_escaped_noncharacter" { + ok( + \\["\uFFFF"] + ); +} + +test "y_string_in_array" { + ok( + \\["asd"] + ); +} + +test "y_string_in_array_with_leading_space" { + ok( + \\[ "asd"] + ); +} + +test "y_string_last_surrogates_1_and_2" { + ok( + \\["\uDBFF\uDFFF"] + ); +} + +test "y_string_nbsp_uescaped" { + ok( + \\["new\u00A0line"] + ); +} + +test "y_string_nonCharacterInUTF-8_U+10FFFF" { + ok( + \\[""] + ); +} + +test "y_string_nonCharacterInUTF-8_U+FFFF" { + ok( + \\[""] + ); +} + +test "y_string_null_escape" { + ok( + \\["\u0000"] + ); +} + +test "y_string_one-byte-utf-8" { + ok( + \\["\u002c"] + ); +} + +test "y_string_pi" { + ok( + \\["π"] + ); +} + +test "y_string_reservedCharacterInUTF-8_U+1BFFF" { + ok( + \\[""] + ); +} + +test "y_string_simple_ascii" { + ok( + \\["asd "] + ); +} + +test "y_string_space" { + ok( + \\" " + ); +} + +test "y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF" { + ok( + \\["\uD834\uDd1e"] + ); +} + +test "y_string_three-byte-utf-8" { + ok( + \\["\u0821"] + ); +} + +test "y_string_two-byte-utf-8" { + ok( + \\["\u0123"] + ); +} + +test "y_string_u+2028_line_sep" { + ok("[\"\xe2\x80\xa8\"]"); +} + +test "y_string_u+2029_par_sep" { + ok("[\"\xe2\x80\xa9\"]"); +} + +test "y_string_uescaped_newline" { + ok( + \\["new\u000Aline"] + ); +} + +test "y_string_uEscape" { + ok( + \\["\u0061\u30af\u30EA\u30b9"] + ); +} + +test "y_string_unescaped_char_delete" { + ok("[\"\x7f\"]"); +} + +test "y_string_unicode_2" { + ok( + \\["⍂㈴⍂"] + ); +} + +test "y_string_unicodeEscapedBackslash" { + ok( + \\["\u005C"] + ); +} + +test "y_string_unicode_escaped_double_quote" { + ok( + \\["\u0022"] + ); +} + +test "y_string_unicode" { + ok( + \\["\uA66D"] + ); +} + +test "y_string_unicode_U+10FFFE_nonchar" { + ok( + \\["\uDBFF\uDFFE"] + ); +} + +test "y_string_unicode_U+1FFFE_nonchar" { + ok( + \\["\uD83F\uDFFE"] + ); +} + +test "y_string_unicode_U+200B_ZERO_WIDTH_SPACE" { + ok( + \\["\u200B"] + ); +} + +test "y_string_unicode_U+2064_invisible_plus" { + ok( + \\["\u2064"] + ); +} + +test "y_string_unicode_U+FDD0_nonchar" { + ok( + \\["\uFDD0"] + ); +} + +test "y_string_unicode_U+FFFE_nonchar" { + ok( + \\["\uFFFE"] + ); +} + +test "y_string_utf8" { + ok( + \\["€𝄞"] + ); +} + +test "y_string_with_del_character" { + ok("[\"a\x7fa\"]"); +} + +test "y_structure_lonely_false" { + ok( + \\false + ); +} + +test "y_structure_lonely_int" { + ok( + \\42 + ); +} + +test "y_structure_lonely_negative_real" { + ok( + \\-0.1 + ); +} + +test "y_structure_lonely_null" { + ok( + \\null + ); +} + +test "y_structure_lonely_string" { + ok( + \\"asd" + ); +} + +test "y_structure_lonely_true" { + ok( + \\true + ); +} + +test "y_structure_string_empty" { + ok( + \\"" + ); +} + +test "y_structure_trailing_newline" { + ok( + \\["a"] + ); +} + +test "y_structure_true_in_array" { + ok( + \\[true] + ); +} + +test "y_structure_whitespace_array" { + ok(" [] "); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +test "n_array_1_true_without_comma" { + err( + \\[1 true] + ); +} + +test "n_array_a_invalid_utf8" { + err( + \\[aå] + ); +} + +test "n_array_colon_instead_of_comma" { + err( + \\["": 1] + ); +} + +test "n_array_comma_after_close" { + //err( + // \\[""], + //); +} + +test "n_array_comma_and_number" { + err( + \\[,1] + ); +} + +test "n_array_double_comma" { + err( + \\[1,,2] + ); +} + +test "n_array_double_extra_comma" { + err( + \\["x",,] + ); +} + +test "n_array_extra_close" { + err( + \\["x"]] + ); +} + +test "n_array_extra_comma" { + //err( + // \\["",] + //); +} + +test "n_array_incomplete_invalid_value" { + err( + \\[x + ); +} + +test "n_array_incomplete" { + err( + \\["x" + ); +} + +test "n_array_inner_array_no_comma" { + err( + \\[3[4]] + ); +} + +test "n_array_invalid_utf8" { + err( + \\[ÿ] + ); +} + +test "n_array_items_separated_by_semicolon" { + err( + \\[1:2] + ); +} + +test "n_array_just_comma" { + err( + \\[,] + ); +} + +test "n_array_just_minus" { + err( + \\[-] + ); +} + +test "n_array_missing_value" { + err( + \\[ , ""] + ); +} + +test "n_array_newlines_unclosed" { + err( + \\["a", + \\4 + \\,1, + ); +} + +test "n_array_number_and_comma" { + err( + \\[1,] + ); +} + +test "n_array_number_and_several_commas" { + err( + \\[1,,] + ); +} + +test "n_array_spaces_vertical_tab_formfeed" { + err("[\"\x0aa\"\\f]"); +} + +test "n_array_star_inside" { + err( + \\[*] + ); +} + +test "n_array_unclosed" { + err( + \\["" + ); +} + +test "n_array_unclosed_trailing_comma" { + err( + \\[1, + ); +} + +test "n_array_unclosed_with_new_lines" { + err( + \\[1, + \\1 + \\,1 + ); +} + +test "n_array_unclosed_with_object_inside" { + err( + \\[{} + ); +} + +test "n_incomplete_false" { + err( + \\[fals] + ); +} + +test "n_incomplete_null" { + err( + \\[nul] + ); +} + +test "n_incomplete_true" { + err( + \\[tru] + ); +} + +test "n_multidigit_number_then_00" { + err("123\x00"); +} + +test "n_number_0.1.2" { + err( + \\[0.1.2] + ); +} + +test "n_number_-01" { + err( + \\[-01] + ); +} + +test "n_number_0.3e" { + err( + \\[0.3e] + ); +} + +test "n_number_0.3e+" { + err( + \\[0.3e+] + ); +} + +test "n_number_0_capital_E" { + err( + \\[0E] + ); +} + +test "n_number_0_capital_E+" { + err( + \\[0E+] + ); +} + +test "n_number_0.e1" { + err( + \\[0.e1] + ); +} + +test "n_number_0e" { + err( + \\[0e] + ); +} + +test "n_number_0e+" { + err( + \\[0e+] + ); +} + +test "n_number_1_000" { + err( + \\[1 000.0] + ); +} + +test "n_number_1.0e-" { + err( + \\[1.0e-] + ); +} + +test "n_number_1.0e" { + err( + \\[1.0e] + ); +} + +test "n_number_1.0e+" { + err( + \\[1.0e+] + ); +} + +test "n_number_-1.0." { + err( + \\[-1.0.] + ); +} + +test "n_number_1eE2" { + err( + \\[1eE2] + ); +} + +test "n_number_.-1" { + err( + \\[.-1] + ); +} + +test "n_number_+1" { + err( + \\[+1] + ); +} + +test "n_number_.2e-3" { + err( + \\[.2e-3] + ); +} + +test "n_number_2.e-3" { + err( + \\[2.e-3] + ); +} + +test "n_number_2.e+3" { + err( + \\[2.e+3] + ); +} + +test "n_number_2.e3" { + err( + \\[2.e3] + ); +} + +test "n_number_-2." { + err( + \\[-2.] + ); +} + +test "n_number_9.e+" { + err( + \\[9.e+] + ); +} + +test "n_number_expression" { + err( + \\[1+2] + ); +} + +test "n_number_hex_1_digit" { + err( + \\[0x1] + ); +} + +test "n_number_hex_2_digits" { + err( + \\[0x42] + ); +} + +test "n_number_infinity" { + err( + \\[Infinity] + ); +} + +test "n_number_+Inf" { + err( + \\[+Inf] + ); +} + +test "n_number_Inf" { + err( + \\[Inf] + ); +} + +test "n_number_invalid+-" { + err( + \\[0e+-1] + ); +} + +test "n_number_invalid-negative-real" { + err( + \\[-123.123foo] + ); +} + +test "n_number_invalid-utf-8-in-bigger-int" { + err( + \\[123å] + ); +} + +test "n_number_invalid-utf-8-in-exponent" { + err( + \\[1e1å] + ); +} + +test "n_number_invalid-utf-8-in-int" { + err( + \\[0å] + ); +} + +test "n_number_++" { + err( + \\[++1234] + ); +} + +test "n_number_minus_infinity" { + err( + \\[-Infinity] + ); +} + +test "n_number_minus_sign_with_trailing_garbage" { + err( + \\[-foo] + ); +} + +test "n_number_minus_space_1" { + err( + \\[- 1] + ); +} + +test "n_number_-NaN" { + err( + \\[-NaN] + ); +} + +test "n_number_NaN" { + err( + \\[NaN] + ); +} + +test "n_number_neg_int_starting_with_zero" { + err( + \\[-012] + ); +} + +test "n_number_neg_real_without_int_part" { + err( + \\[-.123] + ); +} + +test "n_number_neg_with_garbage_at_end" { + err( + \\[-1x] + ); +} + +test "n_number_real_garbage_after_e" { + err( + \\[1ea] + ); +} + +test "n_number_real_with_invalid_utf8_after_e" { + err( + \\[1eå] + ); +} + +test "n_number_real_without_fractional_part" { + err( + \\[1.] + ); +} + +test "n_number_starting_with_dot" { + err( + \\[.123] + ); +} + +test "n_number_U+FF11_fullwidth_digit_one" { + err( + \\[ï¼] + ); +} + +test "n_number_with_alpha_char" { + err( + \\[1.8011670033376514H-308] + ); +} + +test "n_number_with_alpha" { + err( + \\[1.2a-3] + ); +} + +test "n_number_with_leading_zero" { + err( + \\[012] + ); +} + +test "n_object_bad_value" { + err( + \\["x", truth] + ); +} + +test "n_object_bracket_key" { + err( + \\{[: "x"} + ); +} + +test "n_object_comma_instead_of_colon" { + err( + \\{"x", null} + ); +} + +test "n_object_double_colon" { + err( + \\{"x"::"b"} + ); +} + +test "n_object_emoji" { + err( + \\{ð¨ð} + ); +} + +test "n_object_garbage_at_end" { + err( + \\{"a":"a" 123} + ); +} + +test "n_object_key_with_single_quotes" { + err( + \\{key: 'value'} + ); +} + +test "n_object_lone_continuation_byte_in_key_and_trailing_comma" { + err( + \\{"¹":"0",} + ); +} + +test "n_object_missing_colon" { + err( + \\{"a" b} + ); +} + +test "n_object_missing_key" { + err( + \\{:"b"} + ); +} + +test "n_object_missing_semicolon" { + err( + \\{"a" "b"} + ); +} + +test "n_object_missing_value" { + err( + \\{"a": + ); +} + +test "n_object_no-colon" { + err( + \\{"a" + ); +} + +test "n_object_non_string_key_but_huge_number_instead" { + err( + \\{9999E9999:1} + ); +} + +test "n_object_non_string_key" { + err( + \\{1:1} + ); +} + +test "n_object_repeated_null_null" { + err( + \\{null:null,null:null} + ); +} + +test "n_object_several_trailing_commas" { + err( + \\{"id":0,,,,,} + ); +} + +test "n_object_single_quote" { + err( + \\{'a':0} + ); +} + +test "n_object_trailing_comma" { + err( + \\{"id":0,} + ); +} + +test "n_object_trailing_comment" { + err( + \\{"a":"b"}/**/ + ); +} + +test "n_object_trailing_comment_open" { + err( + \\{"a":"b"}/**// + ); +} + +test "n_object_trailing_comment_slash_open_incomplete" { + err( + \\{"a":"b"}/ + ); +} + +test "n_object_trailing_comment_slash_open" { + err( + \\{"a":"b"}// + ); +} + +test "n_object_two_commas_in_a_row" { + err( + \\{"a":"b",,"c":"d"} + ); +} + +test "n_object_unquoted_key" { + err( + \\{a: "b"} + ); +} + +test "n_object_unterminated-value" { + err( + \\{"a":"a + ); +} + +test "n_object_with_single_string" { + err( + \\{ "foo" : "bar", "a" } + ); +} + +test "n_object_with_trailing_garbage" { + err( + \\{"a":"b"}# + ); +} + +test "n_single_space" { + err(" "); +} + +test "n_string_1_surrogate_then_escape" { + err( + \\["\uD800\"] + ); +} + +test "n_string_1_surrogate_then_escape_u1" { + err( + \\["\uD800\u1"] + ); +} + +test "n_string_1_surrogate_then_escape_u1x" { + err( + \\["\uD800\u1x"] + ); +} + +test "n_string_1_surrogate_then_escape_u" { + err( + \\["\uD800\u"] + ); +} + +test "n_string_accentuated_char_no_quotes" { + err( + \\[é] + ); +} + +test "n_string_backslash_00" { + err("[\"\x00\"]"); +} + +test "n_string_escaped_backslash_bad" { + err( + \\["\\\"] + ); +} + +test "n_string_escaped_ctrl_char_tab" { + err("\x5b\x22\x5c\x09\x22\x5d"); +} + +test "n_string_escaped_emoji" { + err("[\"\x5c\xc3\xb0\xc2\x9f\xc2\x8c\xc2\x80\"]"); +} + +test "n_string_escape_x" { + err( + \\["\x00"] + ); +} + +test "n_string_incomplete_escaped_character" { + err( + \\["\u00A"] + ); +} + +test "n_string_incomplete_escape" { + err( + \\["\"] + ); +} + +test "n_string_incomplete_surrogate_escape_invalid" { + err( + \\["\uD800\uD800\x"] + ); +} + +test "n_string_incomplete_surrogate" { + err( + \\["\uD834\uDd"] + ); +} + +test "n_string_invalid_backslash_esc" { + err( + \\["\a"] + ); +} + +test "n_string_invalid_unicode_escape" { + err( + \\["\uqqqq"] + ); +} + +test "n_string_invalid_utf8_after_escape" { + err("[\"\\\x75\xc3\xa5\"]"); +} + +test "n_string_invalid-utf-8-in-escape" { + err( + \\["\uå"] + ); +} + +test "n_string_leading_uescaped_thinspace" { + err( + \\[\u0020"asd"] + ); +} + +test "n_string_no_quotes_with_bad_escape" { + err( + \\[\n] + ); +} + +test "n_string_single_doublequote" { + err( + \\" + ); +} + +test "n_string_single_quote" { + err( + \\['single quote'] + ); +} + +test "n_string_single_string_no_double_quotes" { + err( + \\abc + ); +} + +test "n_string_start_escape_unclosed" { + err( + \\["\ + ); +} + +test "n_string_unescaped_crtl_char" { + err("[\"a\x00a\"]"); +} + +test "n_string_unescaped_newline" { + err( + \\["new + \\line"] + ); +} + +test "n_string_unescaped_tab" { + err("[\"\t\"]"); +} + +test "n_string_unicode_CapitalU" { + err( + \\"\UA66D" + ); +} + +test "n_string_with_trailing_garbage" { + err( + \\""x + ); +} + +test "n_structure_100000_opening_arrays" { + err("[" ** 100000); +} + +test "n_structure_angle_bracket_." { + err( + \\<.> + ); +} + +test "n_structure_angle_bracket_null" { + err( + \\[<null>] + ); +} + +test "n_structure_array_trailing_garbage" { + err( + \\[1]x + ); +} + +test "n_structure_array_with_extra_array_close" { + err( + \\[1]] + ); +} + +test "n_structure_array_with_unclosed_string" { + err( + \\["asd] + ); +} + +test "n_structure_ascii-unicode-identifier" { + err( + \\aÃ¥ + ); +} + +test "n_structure_capitalized_True" { + err( + \\[True] + ); +} + +test "n_structure_close_unopened_array" { + err( + \\1] + ); +} + +test "n_structure_comma_instead_of_closing_brace" { + err( + \\{"x": true, + ); +} + +test "n_structure_double_array" { + err( + \\[][] + ); +} + +test "n_structure_end_array" { + err( + \\] + ); +} + +test "n_structure_incomplete_UTF8_BOM" { + err( + \\ï»{} + ); +} + +test "n_structure_lone-invalid-utf-8" { + err( + \\å + ); +} + +test "n_structure_lone-open-bracket" { + err( + \\[ + ); +} + +test "n_structure_no_data" { + err( + \\ + ); +} + +test "n_structure_null-byte-outside-string" { + err("[\x00]"); +} + +test "n_structure_number_with_trailing_garbage" { + err( + \\2@ + ); +} + +test "n_structure_object_followed_by_closing_object" { + err( + \\{}} + ); +} + +test "n_structure_object_unclosed_no_value" { + err( + \\{"": + ); +} + +test "n_structure_object_with_comment" { + err( + \\{"a":/*comment*/"b"} + ); +} + +test "n_structure_object_with_trailing_garbage" { + err( + \\{"a": true} "x" + ); +} + +test "n_structure_open_array_apostrophe" { + err( + \\[' + ); +} + +test "n_structure_open_array_comma" { + err( + \\[, + ); +} + +test "n_structure_open_array_object" { + err("[{\"\":" ** 50000); +} + +test "n_structure_open_array_open_object" { + err( + \\[{ + ); +} + +test "n_structure_open_array_open_string" { + err( + \\["a + ); +} + +test "n_structure_open_array_string" { + err( + \\["a" + ); +} + +test "n_structure_open_object_close_array" { + err( + \\{] + ); +} + +test "n_structure_open_object_comma" { + err( + \\{, + ); +} + +test "n_structure_open_object" { + err( + \\{ + ); +} + +test "n_structure_open_object_open_array" { + err( + \\{[ + ); +} + +test "n_structure_open_object_open_string" { + err( + \\{"a + ); +} + +test "n_structure_open_object_string_with_apostrophes" { + err( + \\{'a' + ); +} + +test "n_structure_open_open" { + err( + \\["\{["\{["\{["\{ + ); +} + +test "n_structure_single_eacute" { + err( + \\é + ); +} + +test "n_structure_single_star" { + err( + \\* + ); +} + +test "n_structure_trailing_#" { + err( + \\{"a":"b"}#{} + ); +} + +test "n_structure_U+2060_word_joined" { + err( + \\[â ] + ); +} + +test "n_structure_uescaped_LF_before_string" { + err( + \\[\u000A""] + ); +} + +test "n_structure_unclosed_array" { + err( + \\[1 + ); +} + +test "n_structure_unclosed_array_partial_null" { + err( + \\[ false, nul + ); +} + +test "n_structure_unclosed_array_unfinished_false" { + err( + \\[ true, fals + ); +} + +test "n_structure_unclosed_array_unfinished_true" { + err( + \\[ false, tru + ); +} + +test "n_structure_unclosed_object" { + err( + \\{"asd":"asd" + ); +} + +test "n_structure_unicode-identifier" { + err( + \\Ã¥ + ); +} + +test "n_structure_UTF8_BOM_no_data" { + err( + \\ + ); +} + +test "n_structure_whitespace_formfeed" { + err("[\x0c]"); +} + +test "n_structure_whitespace_U+2060_word_joiner" { + err( + \\[â ] + ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +test "i_number_double_huge_neg_exp" { + any( + \\[123.456e-789] + ); +} + +test "i_number_huge_exp" { + any( + \\[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] + ); +} + +test "i_number_neg_int_huge_exp" { + any( + \\[-1e+9999] + ); +} + +test "i_number_pos_double_huge_exp" { + any( + \\[1.5e+9999] + ); +} + +test "i_number_real_neg_overflow" { + any( + \\[-123123e100000] + ); +} + +test "i_number_real_pos_overflow" { + any( + \\[123123e100000] + ); +} + +test "i_number_real_underflow" { + any( + \\[123e-10000000] + ); +} + +test "i_number_too_big_neg_int" { + any( + \\[-123123123123123123123123123123] + ); +} + +test "i_number_too_big_pos_int" { + any( + \\[100000000000000000000] + ); +} + +test "i_number_very_big_negative_int" { + any( + \\[-237462374673276894279832749832423479823246327846] + ); +} + +test "i_object_key_lone_2nd_surrogate" { + any( + \\{"\uDFAA":0} + ); +} + +test "i_string_1st_surrogate_but_2nd_missing" { + any( + \\["\uDADA"] + ); +} + +test "i_string_1st_valid_surrogate_2nd_invalid" { + any( + \\["\uD888\u1234"] + ); +} + +test "i_string_incomplete_surrogate_and_escape_valid" { + any( + \\["\uD800\n"] + ); +} + +test "i_string_incomplete_surrogate_pair" { + any( + \\["\uDd1ea"] + ); +} + +test "i_string_incomplete_surrogates_escape_valid" { + any( + \\["\uD800\uD800\n"] + ); +} + +test "i_string_invalid_lonely_surrogate" { + any( + \\["\ud800"] + ); +} + +test "i_string_invalid_surrogate" { + any( + \\["\ud800abc"] + ); +} + +test "i_string_invalid_utf-8" { + any( + \\["ÿ"] + ); +} + +test "i_string_inverted_surrogates_U+1D11E" { + any( + \\["\uDd1e\uD834"] + ); +} + +test "i_string_iso_latin_1" { + any( + \\["é"] + ); +} + +test "i_string_lone_second_surrogate" { + any( + \\["\uDFAA"] + ); +} + +test "i_string_lone_utf8_continuation_byte" { + any( + \\[""] + ); +} + +test "i_string_not_in_unicode_range" { + any( + \\["ô¿¿¿"] + ); +} + +test "i_string_overlong_sequence_2_bytes" { + any( + \\["À¯"] + ); +} + +test "i_string_overlong_sequence_6_bytes" { + any( + \\["ü¿¿¿¿"] + ); +} + +test "i_string_overlong_sequence_6_bytes_null" { + any( + \\["ü"] + ); +} + +test "i_string_truncated-utf-8" { + any( + \\["àÿ"] + ); +} + +test "i_string_utf16BE_no_BOM" { + any("\x00\x5b\x00\x22\x00\xc3\xa9\x00\x22\x00\x5d"); +} + +test "i_string_utf16LE_no_BOM" { + any("\x5b\x00\x22\x00\xc3\xa9\x00\x22\x00\x5d\x00"); +} + +test "i_string_UTF-16LE_with_BOM" { + any("\xc3\xbf\xc3\xbe\x5b\x00\x22\x00\xc3\xa9\x00\x22\x00\x5d\x00"); +} + +test "i_string_UTF-8_invalid_sequence" { + any( + \\["æ¥Ñú"] + ); +} + +test "i_string_UTF8_surrogate_U+D800" { + any( + \\["í "] + ); +} + +test "i_structure_500_nested_arrays" { + any(("[" ** 500) ++ ("]" ** 500)); +} + +test "i_structure_UTF-8_BOM_empty_object" { + any( + \\{} + ); +} diff --git a/std/json_test.zig b/std/json_test.zig deleted file mode 100644 index a323e6e979..0000000000 --- a/std/json_test.zig +++ /dev/null @@ -1,1904 +0,0 @@ -// RFC 8529 conformance tests. -// -// Tests are taken from https://github.com/nst/JSONTestSuite -// Read also http://seriot.ch/parsing_json.php for a good overview. - -const std = @import("std.zig"); - -fn ok(comptime s: []const u8) void { - std.testing.expect(std.json.validate(s)); -} - -fn err(comptime s: []const u8) void { - std.testing.expect(!std.json.validate(s)); -} - -fn any(comptime s: []const u8) void { - std.testing.expect(true); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Additional tests not part of test JSONTestSuite. - -test "json.test.y_trailing_comma_after_empty" { - ok( - \\{"1":[],"2":{},"3":"4"} - ); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -test "json.test.y_array_arraysWithSpaces" { - ok( - \\[[] ] - ); -} - -test "json.test.y_array_empty" { - ok( - \\[] - ); -} - -test "json.test.y_array_empty-string" { - ok( - \\[""] - ); -} - -test "json.test.y_array_ending_with_newline" { - ok( - \\["a"] - ); -} - -test "json.test.y_array_false" { - ok( - \\[false] - ); -} - -test "json.test.y_array_heterogeneous" { - ok( - \\[null, 1, "1", {}] - ); -} - -test "json.test.y_array_null" { - ok( - \\[null] - ); -} - -test "json.test.y_array_with_1_and_newline" { - ok( - \\[1 - \\] - ); -} - -test "json.test.y_array_with_leading_space" { - ok( - \\ [1] - ); -} - -test "json.test.y_array_with_several_null" { - ok( - \\[1,null,null,null,2] - ); -} - -test "json.test.y_array_with_trailing_space" { - ok("[2] "); -} - -test "json.test.y_number_0e+1" { - ok( - \\[0e+1] - ); -} - -test "json.test.y_number_0e1" { - ok( - \\[0e1] - ); -} - -test "json.test.y_number_after_space" { - ok( - \\[ 4] - ); -} - -test "json.test.y_number_double_close_to_zero" { - ok( - \\[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] - ); -} - -test "json.test.y_number_int_with_exp" { - ok( - \\[20e1] - ); -} - -test "json.test.y_number" { - ok( - \\[123e65] - ); -} - -test "json.test.y_number_minus_zero" { - ok( - \\[-0] - ); -} - -test "json.test.y_number_negative_int" { - ok( - \\[-123] - ); -} - -test "json.test.y_number_negative_one" { - ok( - \\[-1] - ); -} - -test "json.test.y_number_negative_zero" { - ok( - \\[-0] - ); -} - -test "json.test.y_number_real_capital_e" { - ok( - \\[1E22] - ); -} - -test "json.test.y_number_real_capital_e_neg_exp" { - ok( - \\[1E-2] - ); -} - -test "json.test.y_number_real_capital_e_pos_exp" { - ok( - \\[1E+2] - ); -} - -test "json.test.y_number_real_exponent" { - ok( - \\[123e45] - ); -} - -test "json.test.y_number_real_fraction_exponent" { - ok( - \\[123.456e78] - ); -} - -test "json.test.y_number_real_neg_exp" { - ok( - \\[1e-2] - ); -} - -test "json.test.y_number_real_pos_exponent" { - ok( - \\[1e+2] - ); -} - -test "json.test.y_number_simple_int" { - ok( - \\[123] - ); -} - -test "json.test.y_number_simple_real" { - ok( - \\[123.456789] - ); -} - -test "json.test.y_object_basic" { - ok( - \\{"asd":"sdf"} - ); -} - -test "json.test.y_object_duplicated_key_and_value" { - ok( - \\{"a":"b","a":"b"} - ); -} - -test "json.test.y_object_duplicated_key" { - ok( - \\{"a":"b","a":"c"} - ); -} - -test "json.test.y_object_empty" { - ok( - \\{} - ); -} - -test "json.test.y_object_empty_key" { - ok( - \\{"":0} - ); -} - -test "json.test.y_object_escaped_null_in_key" { - ok( - \\{"foo\u0000bar": 42} - ); -} - -test "json.test.y_object_extreme_numbers" { - ok( - \\{ "min": -1.0e+28, "max": 1.0e+28 } - ); -} - -test "json.test.y_object" { - ok( - \\{"asd":"sdf", "dfg":"fgh"} - ); -} - -test "json.test.y_object_long_strings" { - ok( - \\{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} - ); -} - -test "json.test.y_object_simple" { - ok( - \\{"a":[]} - ); -} - -test "json.test.y_object_string_unicode" { - ok( - \\{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" } - ); -} - -test "json.test.y_object_with_newlines" { - ok( - \\{ - \\"a": "b" - \\} - ); -} - -test "json.test.y_string_1_2_3_bytes_UTF-8_sequences" { - ok( - \\["\u0060\u012a\u12AB"] - ); -} - -test "json.test.y_string_accepted_surrogate_pair" { - ok( - \\["\uD801\udc37"] - ); -} - -test "json.test.y_string_accepted_surrogate_pairs" { - ok( - \\["\ud83d\ude39\ud83d\udc8d"] - ); -} - -test "json.test.y_string_allowed_escapes" { - ok( - \\["\"\\\/\b\f\n\r\t"] - ); -} - -test "json.test.y_string_backslash_and_u_escaped_zero" { - ok( - \\["\\u0000"] - ); -} - -test "json.test.y_string_backslash_doublequotes" { - ok( - \\["\""] - ); -} - -test "json.test.y_string_comments" { - ok( - \\["a/*b*/c/*d//e"] - ); -} - -test "json.test.y_string_double_escape_a" { - ok( - \\["\\a"] - ); -} - -test "json.test.y_string_double_escape_n" { - ok( - \\["\\n"] - ); -} - -test "json.test.y_string_escaped_control_character" { - ok( - \\["\u0012"] - ); -} - -test "json.test.y_string_escaped_noncharacter" { - ok( - \\["\uFFFF"] - ); -} - -test "json.test.y_string_in_array" { - ok( - \\["asd"] - ); -} - -test "json.test.y_string_in_array_with_leading_space" { - ok( - \\[ "asd"] - ); -} - -test "json.test.y_string_last_surrogates_1_and_2" { - ok( - \\["\uDBFF\uDFFF"] - ); -} - -test "json.test.y_string_nbsp_uescaped" { - ok( - \\["new\u00A0line"] - ); -} - -test "json.test.y_string_nonCharacterInUTF-8_U+10FFFF" { - ok( - \\[""] - ); -} - -test "json.test.y_string_nonCharacterInUTF-8_U+FFFF" { - ok( - \\[""] - ); -} - -test "json.test.y_string_null_escape" { - ok( - \\["\u0000"] - ); -} - -test "json.test.y_string_one-byte-utf-8" { - ok( - \\["\u002c"] - ); -} - -test "json.test.y_string_pi" { - ok( - \\["π"] - ); -} - -test "json.test.y_string_reservedCharacterInUTF-8_U+1BFFF" { - ok( - \\[""] - ); -} - -test "json.test.y_string_simple_ascii" { - ok( - \\["asd "] - ); -} - -test "json.test.y_string_space" { - ok( - \\" " - ); -} - -test "json.test.y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF" { - ok( - \\["\uD834\uDd1e"] - ); -} - -test "json.test.y_string_three-byte-utf-8" { - ok( - \\["\u0821"] - ); -} - -test "json.test.y_string_two-byte-utf-8" { - ok( - \\["\u0123"] - ); -} - -test "json.test.y_string_u+2028_line_sep" { - ok("[\"\xe2\x80\xa8\"]"); -} - -test "json.test.y_string_u+2029_par_sep" { - ok("[\"\xe2\x80\xa9\"]"); -} - -test "json.test.y_string_uescaped_newline" { - ok( - \\["new\u000Aline"] - ); -} - -test "json.test.y_string_uEscape" { - ok( - \\["\u0061\u30af\u30EA\u30b9"] - ); -} - -test "json.test.y_string_unescaped_char_delete" { - ok("[\"\x7f\"]"); -} - -test "json.test.y_string_unicode_2" { - ok( - \\["⍂㈴⍂"] - ); -} - -test "json.test.y_string_unicodeEscapedBackslash" { - ok( - \\["\u005C"] - ); -} - -test "json.test.y_string_unicode_escaped_double_quote" { - ok( - \\["\u0022"] - ); -} - -test "json.test.y_string_unicode" { - ok( - \\["\uA66D"] - ); -} - -test "json.test.y_string_unicode_U+10FFFE_nonchar" { - ok( - \\["\uDBFF\uDFFE"] - ); -} - -test "json.test.y_string_unicode_U+1FFFE_nonchar" { - ok( - \\["\uD83F\uDFFE"] - ); -} - -test "json.test.y_string_unicode_U+200B_ZERO_WIDTH_SPACE" { - ok( - \\["\u200B"] - ); -} - -test "json.test.y_string_unicode_U+2064_invisible_plus" { - ok( - \\["\u2064"] - ); -} - -test "json.test.y_string_unicode_U+FDD0_nonchar" { - ok( - \\["\uFDD0"] - ); -} - -test "json.test.y_string_unicode_U+FFFE_nonchar" { - ok( - \\["\uFFFE"] - ); -} - -test "json.test.y_string_utf8" { - ok( - \\["€𝄞"] - ); -} - -test "json.test.y_string_with_del_character" { - ok("[\"a\x7fa\"]"); -} - -test "json.test.y_structure_lonely_false" { - ok( - \\false - ); -} - -test "json.test.y_structure_lonely_int" { - ok( - \\42 - ); -} - -test "json.test.y_structure_lonely_negative_real" { - ok( - \\-0.1 - ); -} - -test "json.test.y_structure_lonely_null" { - ok( - \\null - ); -} - -test "json.test.y_structure_lonely_string" { - ok( - \\"asd" - ); -} - -test "json.test.y_structure_lonely_true" { - ok( - \\true - ); -} - -test "json.test.y_structure_string_empty" { - ok( - \\"" - ); -} - -test "json.test.y_structure_trailing_newline" { - ok( - \\["a"] - ); -} - -test "json.test.y_structure_true_in_array" { - ok( - \\[true] - ); -} - -test "json.test.y_structure_whitespace_array" { - ok(" [] "); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -test "json.test.n_array_1_true_without_comma" { - err( - \\[1 true] - ); -} - -test "json.test.n_array_a_invalid_utf8" { - err( - \\[aå] - ); -} - -test "json.test.n_array_colon_instead_of_comma" { - err( - \\["": 1] - ); -} - -test "json.test.n_array_comma_after_close" { - //err( - // \\[""], - //); -} - -test "json.test.n_array_comma_and_number" { - err( - \\[,1] - ); -} - -test "json.test.n_array_double_comma" { - err( - \\[1,,2] - ); -} - -test "json.test.n_array_double_extra_comma" { - err( - \\["x",,] - ); -} - -test "json.test.n_array_extra_close" { - err( - \\["x"]] - ); -} - -test "json.test.n_array_extra_comma" { - //err( - // \\["",] - //); -} - -test "json.test.n_array_incomplete_invalid_value" { - err( - \\[x - ); -} - -test "json.test.n_array_incomplete" { - err( - \\["x" - ); -} - -test "json.test.n_array_inner_array_no_comma" { - err( - \\[3[4]] - ); -} - -test "json.test.n_array_invalid_utf8" { - err( - \\[ÿ] - ); -} - -test "json.test.n_array_items_separated_by_semicolon" { - err( - \\[1:2] - ); -} - -test "json.test.n_array_just_comma" { - err( - \\[,] - ); -} - -test "json.test.n_array_just_minus" { - err( - \\[-] - ); -} - -test "json.test.n_array_missing_value" { - err( - \\[ , ""] - ); -} - -test "json.test.n_array_newlines_unclosed" { - err( - \\["a", - \\4 - \\,1, - ); -} - -test "json.test.n_array_number_and_comma" { - err( - \\[1,] - ); -} - -test "json.test.n_array_number_and_several_commas" { - err( - \\[1,,] - ); -} - -test "json.test.n_array_spaces_vertical_tab_formfeed" { - err("[\"\x0aa\"\\f]"); -} - -test "json.test.n_array_star_inside" { - err( - \\[*] - ); -} - -test "json.test.n_array_unclosed" { - err( - \\["" - ); -} - -test "json.test.n_array_unclosed_trailing_comma" { - err( - \\[1, - ); -} - -test "json.test.n_array_unclosed_with_new_lines" { - err( - \\[1, - \\1 - \\,1 - ); -} - -test "json.test.n_array_unclosed_with_object_inside" { - err( - \\[{} - ); -} - -test "json.test.n_incomplete_false" { - err( - \\[fals] - ); -} - -test "json.test.n_incomplete_null" { - err( - \\[nul] - ); -} - -test "json.test.n_incomplete_true" { - err( - \\[tru] - ); -} - -test "json.test.n_multidigit_number_then_00" { - err("123\x00"); -} - -test "json.test.n_number_0.1.2" { - err( - \\[0.1.2] - ); -} - -test "json.test.n_number_-01" { - err( - \\[-01] - ); -} - -test "json.test.n_number_0.3e" { - err( - \\[0.3e] - ); -} - -test "json.test.n_number_0.3e+" { - err( - \\[0.3e+] - ); -} - -test "json.test.n_number_0_capital_E" { - err( - \\[0E] - ); -} - -test "json.test.n_number_0_capital_E+" { - err( - \\[0E+] - ); -} - -test "json.test.n_number_0.e1" { - err( - \\[0.e1] - ); -} - -test "json.test.n_number_0e" { - err( - \\[0e] - ); -} - -test "json.test.n_number_0e+" { - err( - \\[0e+] - ); -} - -test "json.test.n_number_1_000" { - err( - \\[1 000.0] - ); -} - -test "json.test.n_number_1.0e-" { - err( - \\[1.0e-] - ); -} - -test "json.test.n_number_1.0e" { - err( - \\[1.0e] - ); -} - -test "json.test.n_number_1.0e+" { - err( - \\[1.0e+] - ); -} - -test "json.test.n_number_-1.0." { - err( - \\[-1.0.] - ); -} - -test "json.test.n_number_1eE2" { - err( - \\[1eE2] - ); -} - -test "json.test.n_number_.-1" { - err( - \\[.-1] - ); -} - -test "json.test.n_number_+1" { - err( - \\[+1] - ); -} - -test "json.test.n_number_.2e-3" { - err( - \\[.2e-3] - ); -} - -test "json.test.n_number_2.e-3" { - err( - \\[2.e-3] - ); -} - -test "json.test.n_number_2.e+3" { - err( - \\[2.e+3] - ); -} - -test "json.test.n_number_2.e3" { - err( - \\[2.e3] - ); -} - -test "json.test.n_number_-2." { - err( - \\[-2.] - ); -} - -test "json.test.n_number_9.e+" { - err( - \\[9.e+] - ); -} - -test "json.test.n_number_expression" { - err( - \\[1+2] - ); -} - -test "json.test.n_number_hex_1_digit" { - err( - \\[0x1] - ); -} - -test "json.test.n_number_hex_2_digits" { - err( - \\[0x42] - ); -} - -test "json.test.n_number_infinity" { - err( - \\[Infinity] - ); -} - -test "json.test.n_number_+Inf" { - err( - \\[+Inf] - ); -} - -test "json.test.n_number_Inf" { - err( - \\[Inf] - ); -} - -test "json.test.n_number_invalid+-" { - err( - \\[0e+-1] - ); -} - -test "json.test.n_number_invalid-negative-real" { - err( - \\[-123.123foo] - ); -} - -test "json.test.n_number_invalid-utf-8-in-bigger-int" { - err( - \\[123å] - ); -} - -test "json.test.n_number_invalid-utf-8-in-exponent" { - err( - \\[1e1å] - ); -} - -test "json.test.n_number_invalid-utf-8-in-int" { - err( - \\[0å] - ); -} - -test "json.test.n_number_++" { - err( - \\[++1234] - ); -} - -test "json.test.n_number_minus_infinity" { - err( - \\[-Infinity] - ); -} - -test "json.test.n_number_minus_sign_with_trailing_garbage" { - err( - \\[-foo] - ); -} - -test "json.test.n_number_minus_space_1" { - err( - \\[- 1] - ); -} - -test "json.test.n_number_-NaN" { - err( - \\[-NaN] - ); -} - -test "json.test.n_number_NaN" { - err( - \\[NaN] - ); -} - -test "json.test.n_number_neg_int_starting_with_zero" { - err( - \\[-012] - ); -} - -test "json.test.n_number_neg_real_without_int_part" { - err( - \\[-.123] - ); -} - -test "json.test.n_number_neg_with_garbage_at_end" { - err( - \\[-1x] - ); -} - -test "json.test.n_number_real_garbage_after_e" { - err( - \\[1ea] - ); -} - -test "json.test.n_number_real_with_invalid_utf8_after_e" { - err( - \\[1eå] - ); -} - -test "json.test.n_number_real_without_fractional_part" { - err( - \\[1.] - ); -} - -test "json.test.n_number_starting_with_dot" { - err( - \\[.123] - ); -} - -test "json.test.n_number_U+FF11_fullwidth_digit_one" { - err( - \\[ï¼] - ); -} - -test "json.test.n_number_with_alpha_char" { - err( - \\[1.8011670033376514H-308] - ); -} - -test "json.test.n_number_with_alpha" { - err( - \\[1.2a-3] - ); -} - -test "json.test.n_number_with_leading_zero" { - err( - \\[012] - ); -} - -test "json.test.n_object_bad_value" { - err( - \\["x", truth] - ); -} - -test "json.test.n_object_bracket_key" { - err( - \\{[: "x"} - ); -} - -test "json.test.n_object_comma_instead_of_colon" { - err( - \\{"x", null} - ); -} - -test "json.test.n_object_double_colon" { - err( - \\{"x"::"b"} - ); -} - -test "json.test.n_object_emoji" { - err( - \\{ð¨ð} - ); -} - -test "json.test.n_object_garbage_at_end" { - err( - \\{"a":"a" 123} - ); -} - -test "json.test.n_object_key_with_single_quotes" { - err( - \\{key: 'value'} - ); -} - -test "json.test.n_object_lone_continuation_byte_in_key_and_trailing_comma" { - err( - \\{"¹":"0",} - ); -} - -test "json.test.n_object_missing_colon" { - err( - \\{"a" b} - ); -} - -test "json.test.n_object_missing_key" { - err( - \\{:"b"} - ); -} - -test "json.test.n_object_missing_semicolon" { - err( - \\{"a" "b"} - ); -} - -test "json.test.n_object_missing_value" { - err( - \\{"a": - ); -} - -test "json.test.n_object_no-colon" { - err( - \\{"a" - ); -} - -test "json.test.n_object_non_string_key_but_huge_number_instead" { - err( - \\{9999E9999:1} - ); -} - -test "json.test.n_object_non_string_key" { - err( - \\{1:1} - ); -} - -test "json.test.n_object_repeated_null_null" { - err( - \\{null:null,null:null} - ); -} - -test "json.test.n_object_several_trailing_commas" { - err( - \\{"id":0,,,,,} - ); -} - -test "json.test.n_object_single_quote" { - err( - \\{'a':0} - ); -} - -test "json.test.n_object_trailing_comma" { - err( - \\{"id":0,} - ); -} - -test "json.test.n_object_trailing_comment" { - err( - \\{"a":"b"}/**/ - ); -} - -test "json.test.n_object_trailing_comment_open" { - err( - \\{"a":"b"}/**// - ); -} - -test "json.test.n_object_trailing_comment_slash_open_incomplete" { - err( - \\{"a":"b"}/ - ); -} - -test "json.test.n_object_trailing_comment_slash_open" { - err( - \\{"a":"b"}// - ); -} - -test "json.test.n_object_two_commas_in_a_row" { - err( - \\{"a":"b",,"c":"d"} - ); -} - -test "json.test.n_object_unquoted_key" { - err( - \\{a: "b"} - ); -} - -test "json.test.n_object_unterminated-value" { - err( - \\{"a":"a - ); -} - -test "json.test.n_object_with_single_string" { - err( - \\{ "foo" : "bar", "a" } - ); -} - -test "json.test.n_object_with_trailing_garbage" { - err( - \\{"a":"b"}# - ); -} - -test "json.test.n_single_space" { - err(" "); -} - -test "json.test.n_string_1_surrogate_then_escape" { - err( - \\["\uD800\"] - ); -} - -test "json.test.n_string_1_surrogate_then_escape_u1" { - err( - \\["\uD800\u1"] - ); -} - -test "json.test.n_string_1_surrogate_then_escape_u1x" { - err( - \\["\uD800\u1x"] - ); -} - -test "json.test.n_string_1_surrogate_then_escape_u" { - err( - \\["\uD800\u"] - ); -} - -test "json.test.n_string_accentuated_char_no_quotes" { - err( - \\[é] - ); -} - -test "json.test.n_string_backslash_00" { - err("[\"\x00\"]"); -} - -test "json.test.n_string_escaped_backslash_bad" { - err( - \\["\\\"] - ); -} - -test "json.test.n_string_escaped_ctrl_char_tab" { - err("\x5b\x22\x5c\x09\x22\x5d"); -} - -test "json.test.n_string_escaped_emoji" { - err("[\"\x5c\xc3\xb0\xc2\x9f\xc2\x8c\xc2\x80\"]"); -} - -test "json.test.n_string_escape_x" { - err( - \\["\x00"] - ); -} - -test "json.test.n_string_incomplete_escaped_character" { - err( - \\["\u00A"] - ); -} - -test "json.test.n_string_incomplete_escape" { - err( - \\["\"] - ); -} - -test "json.test.n_string_incomplete_surrogate_escape_invalid" { - err( - \\["\uD800\uD800\x"] - ); -} - -test "json.test.n_string_incomplete_surrogate" { - err( - \\["\uD834\uDd"] - ); -} - -test "json.test.n_string_invalid_backslash_esc" { - err( - \\["\a"] - ); -} - -test "json.test.n_string_invalid_unicode_escape" { - err( - \\["\uqqqq"] - ); -} - -test "json.test.n_string_invalid_utf8_after_escape" { - err("[\"\\\x75\xc3\xa5\"]"); -} - -test "json.test.n_string_invalid-utf-8-in-escape" { - err( - \\["\uå"] - ); -} - -test "json.test.n_string_leading_uescaped_thinspace" { - err( - \\[\u0020"asd"] - ); -} - -test "json.test.n_string_no_quotes_with_bad_escape" { - err( - \\[\n] - ); -} - -test "json.test.n_string_single_doublequote" { - err( - \\" - ); -} - -test "json.test.n_string_single_quote" { - err( - \\['single quote'] - ); -} - -test "json.test.n_string_single_string_no_double_quotes" { - err( - \\abc - ); -} - -test "json.test.n_string_start_escape_unclosed" { - err( - \\["\ - ); -} - -test "json.test.n_string_unescaped_crtl_char" { - err("[\"a\x00a\"]"); -} - -test "json.test.n_string_unescaped_newline" { - err( - \\["new - \\line"] - ); -} - -test "json.test.n_string_unescaped_tab" { - err("[\"\t\"]"); -} - -test "json.test.n_string_unicode_CapitalU" { - err( - \\"\UA66D" - ); -} - -test "json.test.n_string_with_trailing_garbage" { - err( - \\""x - ); -} - -test "json.test.n_structure_100000_opening_arrays" { - err("[" ** 100000); -} - -test "json.test.n_structure_angle_bracket_." { - err( - \\<.> - ); -} - -test "json.test.n_structure_angle_bracket_null" { - err( - \\[<null>] - ); -} - -test "json.test.n_structure_array_trailing_garbage" { - err( - \\[1]x - ); -} - -test "json.test.n_structure_array_with_extra_array_close" { - err( - \\[1]] - ); -} - -test "json.test.n_structure_array_with_unclosed_string" { - err( - \\["asd] - ); -} - -test "json.test.n_structure_ascii-unicode-identifier" { - err( - \\aÃ¥ - ); -} - -test "json.test.n_structure_capitalized_True" { - err( - \\[True] - ); -} - -test "json.test.n_structure_close_unopened_array" { - err( - \\1] - ); -} - -test "json.test.n_structure_comma_instead_of_closing_brace" { - err( - \\{"x": true, - ); -} - -test "json.test.n_structure_double_array" { - err( - \\[][] - ); -} - -test "json.test.n_structure_end_array" { - err( - \\] - ); -} - -test "json.test.n_structure_incomplete_UTF8_BOM" { - err( - \\ï»{} - ); -} - -test "json.test.n_structure_lone-invalid-utf-8" { - err( - \\å - ); -} - -test "json.test.n_structure_lone-open-bracket" { - err( - \\[ - ); -} - -test "json.test.n_structure_no_data" { - err( - \\ - ); -} - -test "json.test.n_structure_null-byte-outside-string" { - err("[\x00]"); -} - -test "json.test.n_structure_number_with_trailing_garbage" { - err( - \\2@ - ); -} - -test "json.test.n_structure_object_followed_by_closing_object" { - err( - \\{}} - ); -} - -test "json.test.n_structure_object_unclosed_no_value" { - err( - \\{"": - ); -} - -test "json.test.n_structure_object_with_comment" { - err( - \\{"a":/*comment*/"b"} - ); -} - -test "json.test.n_structure_object_with_trailing_garbage" { - err( - \\{"a": true} "x" - ); -} - -test "json.test.n_structure_open_array_apostrophe" { - err( - \\[' - ); -} - -test "json.test.n_structure_open_array_comma" { - err( - \\[, - ); -} - -test "json.test.n_structure_open_array_object" { - err("[{\"\":" ** 50000); -} - -test "json.test.n_structure_open_array_open_object" { - err( - \\[{ - ); -} - -test "json.test.n_structure_open_array_open_string" { - err( - \\["a - ); -} - -test "json.test.n_structure_open_array_string" { - err( - \\["a" - ); -} - -test "json.test.n_structure_open_object_close_array" { - err( - \\{] - ); -} - -test "json.test.n_structure_open_object_comma" { - err( - \\{, - ); -} - -test "json.test.n_structure_open_object" { - err( - \\{ - ); -} - -test "json.test.n_structure_open_object_open_array" { - err( - \\{[ - ); -} - -test "json.test.n_structure_open_object_open_string" { - err( - \\{"a - ); -} - -test "json.test.n_structure_open_object_string_with_apostrophes" { - err( - \\{'a' - ); -} - -test "json.test.n_structure_open_open" { - err( - \\["\{["\{["\{["\{ - ); -} - -test "json.test.n_structure_single_eacute" { - err( - \\é - ); -} - -test "json.test.n_structure_single_star" { - err( - \\* - ); -} - -test "json.test.n_structure_trailing_#" { - err( - \\{"a":"b"}#{} - ); -} - -test "json.test.n_structure_U+2060_word_joined" { - err( - \\[â ] - ); -} - -test "json.test.n_structure_uescaped_LF_before_string" { - err( - \\[\u000A""] - ); -} - -test "json.test.n_structure_unclosed_array" { - err( - \\[1 - ); -} - -test "json.test.n_structure_unclosed_array_partial_null" { - err( - \\[ false, nul - ); -} - -test "json.test.n_structure_unclosed_array_unfinished_false" { - err( - \\[ true, fals - ); -} - -test "json.test.n_structure_unclosed_array_unfinished_true" { - err( - \\[ false, tru - ); -} - -test "json.test.n_structure_unclosed_object" { - err( - \\{"asd":"asd" - ); -} - -test "json.test.n_structure_unicode-identifier" { - err( - \\Ã¥ - ); -} - -test "json.test.n_structure_UTF8_BOM_no_data" { - err( - \\ - ); -} - -test "json.test.n_structure_whitespace_formfeed" { - err("[\x0c]"); -} - -test "json.test.n_structure_whitespace_U+2060_word_joiner" { - err( - \\[â ] - ); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - -test "json.test.i_number_double_huge_neg_exp" { - any( - \\[123.456e-789] - ); -} - -test "json.test.i_number_huge_exp" { - any( - \\[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] - ); -} - -test "json.test.i_number_neg_int_huge_exp" { - any( - \\[-1e+9999] - ); -} - -test "json.test.i_number_pos_double_huge_exp" { - any( - \\[1.5e+9999] - ); -} - -test "json.test.i_number_real_neg_overflow" { - any( - \\[-123123e100000] - ); -} - -test "json.test.i_number_real_pos_overflow" { - any( - \\[123123e100000] - ); -} - -test "json.test.i_number_real_underflow" { - any( - \\[123e-10000000] - ); -} - -test "json.test.i_number_too_big_neg_int" { - any( - \\[-123123123123123123123123123123] - ); -} - -test "json.test.i_number_too_big_pos_int" { - any( - \\[100000000000000000000] - ); -} - -test "json.test.i_number_very_big_negative_int" { - any( - \\[-237462374673276894279832749832423479823246327846] - ); -} - -test "json.test.i_object_key_lone_2nd_surrogate" { - any( - \\{"\uDFAA":0} - ); -} - -test "json.test.i_string_1st_surrogate_but_2nd_missing" { - any( - \\["\uDADA"] - ); -} - -test "json.test.i_string_1st_valid_surrogate_2nd_invalid" { - any( - \\["\uD888\u1234"] - ); -} - -test "json.test.i_string_incomplete_surrogate_and_escape_valid" { - any( - \\["\uD800\n"] - ); -} - -test "json.test.i_string_incomplete_surrogate_pair" { - any( - \\["\uDd1ea"] - ); -} - -test "json.test.i_string_incomplete_surrogates_escape_valid" { - any( - \\["\uD800\uD800\n"] - ); -} - -test "json.test.i_string_invalid_lonely_surrogate" { - any( - \\["\ud800"] - ); -} - -test "json.test.i_string_invalid_surrogate" { - any( - \\["\ud800abc"] - ); -} - -test "json.test.i_string_invalid_utf-8" { - any( - \\["ÿ"] - ); -} - -test "json.test.i_string_inverted_surrogates_U+1D11E" { - any( - \\["\uDd1e\uD834"] - ); -} - -test "json.test.i_string_iso_latin_1" { - any( - \\["é"] - ); -} - -test "json.test.i_string_lone_second_surrogate" { - any( - \\["\uDFAA"] - ); -} - -test "json.test.i_string_lone_utf8_continuation_byte" { - any( - \\[""] - ); -} - -test "json.test.i_string_not_in_unicode_range" { - any( - \\["ô¿¿¿"] - ); -} - -test "json.test.i_string_overlong_sequence_2_bytes" { - any( - \\["À¯"] - ); -} - -test "json.test.i_string_overlong_sequence_6_bytes" { - any( - \\["ü¿¿¿¿"] - ); -} - -test "json.test.i_string_overlong_sequence_6_bytes_null" { - any( - \\["ü"] - ); -} - -test "json.test.i_string_truncated-utf-8" { - any( - \\["àÿ"] - ); -} - -test "json.test.i_string_utf16BE_no_BOM" { - any("\x00\x5b\x00\x22\x00\xc3\xa9\x00\x22\x00\x5d"); -} - -test "json.test.i_string_utf16LE_no_BOM" { - any("\x5b\x00\x22\x00\xc3\xa9\x00\x22\x00\x5d\x00"); -} - -test "json.test.i_string_UTF-16LE_with_BOM" { - any("\xc3\xbf\xc3\xbe\x5b\x00\x22\x00\xc3\xa9\x00\x22\x00\x5d\x00"); -} - -test "json.test.i_string_UTF-8_invalid_sequence" { - any( - \\["æ¥Ñú"] - ); -} - -test "json.test.i_string_UTF8_surrogate_U+D800" { - any( - \\["í "] - ); -} - -test "json.test.i_structure_500_nested_arrays" { - any(("[" ** 500) ++ ("]" ** 500)); -} - -test "json.test.i_structure_UTF-8_BOM_empty_object" { - any( - \\{} - ); -} diff --git a/std/math/acos.zig b/std/math/acos.zig index 763d9d8abd..de07da8fe0 100644 --- a/std/math/acos.zig +++ b/std/math/acos.zig @@ -1,11 +1,17 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - acos(x) = nan if x < -1 or x > 1 +// https://git.musl-libc.org/cgit/musl/tree/src/math/acosf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/acos.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the arc-cosine of x. +/// +/// Special cases: +/// - acos(x) = nan if x < -1 or x > 1 pub fn acos(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/acosh.zig b/std/math/acosh.zig index a2a9926863..503c0433fc 100644 --- a/std/math/acosh.zig +++ b/std/math/acosh.zig @@ -1,13 +1,19 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - acosh(x) = snan if x < 1 -// - acosh(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/acoshf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/acosh.c const builtin = @import("builtin"); const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the hyperbolic arc-cosine of x. +/// +/// Special cases: +/// - acosh(x) = snan if x < 1 +/// - acosh(nan) = nan pub fn acosh(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/asin.zig b/std/math/asin.zig index b01a1ac49e..2db9f86ff1 100644 --- a/std/math/asin.zig +++ b/std/math/asin.zig @@ -1,12 +1,18 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - asin(+-0) = +-0 -// - asin(x) = nan if x < -1 or x > 1 +// https://git.musl-libc.org/cgit/musl/tree/src/math/asinf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/asin.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the arc-sin of x. +/// +/// Special Cases: +/// - asin(+-0) = +-0 +/// - asin(x) = nan if x < -1 or x > 1 pub fn asin(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/asinh.zig b/std/math/asinh.zig index cef9bfb23f..0fb51d1b43 100644 --- a/std/math/asinh.zig +++ b/std/math/asinh.zig @@ -1,14 +1,20 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - asinh(+-0) = +-0 -// - asinh(+-inf) = +-inf -// - asinh(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/asinhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/asinh.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns the hyperbolic arc-sin of x. +/// +/// Special Cases: +/// - asinh(+-0) = +-0 +/// - asinh(+-inf) = +-inf +/// - asinh(nan) = nan pub fn asinh(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/atan.zig b/std/math/atan.zig index ba5a11dd10..cc4cad0dd4 100644 --- a/std/math/atan.zig +++ b/std/math/atan.zig @@ -1,12 +1,18 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - atan(+-0) = +-0 -// - atan(+-inf) = +-pi/2 +// https://git.musl-libc.org/cgit/musl/tree/src/math/atanf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/atan.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the arc-tangent of x. +/// +/// Special Cases: +/// - atan(+-0) = +-0 +/// - atan(+-inf) = +-pi/2 pub fn atan(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/atan2.zig b/std/math/atan2.zig index 7f13507402..68e381607d 100644 --- a/std/math/atan2.zig +++ b/std/math/atan2.zig @@ -1,27 +1,33 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// atan2(y, nan) = nan -// atan2(nan, x) = nan -// atan2(+0, x>=0) = +0 -// atan2(-0, x>=0) = -0 -// atan2(+0, x<=-0) = +pi -// atan2(-0, x<=-0) = -pi -// atan2(y>0, 0) = +pi/2 -// atan2(y<0, 0) = -pi/2 -// atan2(+inf, +inf) = +pi/4 -// atan2(-inf, +inf) = -pi/4 -// atan2(+inf, -inf) = 3pi/4 -// atan2(-inf, -inf) = -3pi/4 -// atan2(y, +inf) = 0 -// atan2(y>0, -inf) = +pi -// atan2(y<0, -inf) = -pi -// atan2(+inf, x) = +pi/2 -// atan2(-inf, x) = -pi/2 +// https://git.musl-libc.org/cgit/musl/tree/src/math/atan2f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/atan2.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the arc-tangent of y/x. +/// +/// Special Cases: +/// - atan2(y, nan) = nan +/// - atan2(nan, x) = nan +/// - atan2(+0, x>=0) = +0 +/// - atan2(-0, x>=0) = -0 +/// - atan2(+0, x<=-0) = +pi +/// - atan2(-0, x<=-0) = -pi +/// - atan2(y>0, 0) = +pi/2 +/// - atan2(y<0, 0) = -pi/2 +/// - atan2(+inf, +inf) = +pi/4 +/// - atan2(-inf, +inf) = -pi/4 +/// - atan2(+inf, -inf) = 3pi/4 +/// - atan2(-inf, -inf) = -3pi/4 +/// - atan2(y, +inf) = 0 +/// - atan2(y>0, -inf) = +pi +/// - atan2(y<0, -inf) = -pi +/// - atan2(+inf, x) = +pi/2 +/// - atan2(-inf, x) = -pi/2 pub fn atan2(comptime T: type, y: T, x: T) T { return switch (T) { f32 => atan2_32(y, x), diff --git a/std/math/atanh.zig b/std/math/atanh.zig index 9056064f5a..8ba29be761 100644 --- a/std/math/atanh.zig +++ b/std/math/atanh.zig @@ -1,14 +1,20 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - atanh(+-1) = +-inf with signal -// - atanh(x) = nan if |x| > 1 with signal -// - atanh(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/atanhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/atanh.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns the hyperbolic arc-tangent of x. +/// +/// Special Cases: +/// - atanh(+-1) = +-inf with signal +/// - atanh(x) = nan if |x| > 1 with signal +/// - atanh(nan) = nan pub fn atanh(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/big.zig b/std/math/big.zig index 94b6d864e7..44b5ce675f 100644 --- a/std/math/big.zig +++ b/std/math/big.zig @@ -1,5 +1,7 @@ pub use @import("big/int.zig"); +pub use @import("big/rational.zig"); test "math.big" { _ = @import("big/int.zig"); + _ = @import("big/rational.zig"); } diff --git a/std/math/big/int.zig b/std/math/big/int.zig index 8800c2c7a9..beac3c85fe 100644 --- a/std/math/big/int.zig +++ b/std/math/big/int.zig @@ -21,78 +21,160 @@ comptime { debug.assert(Limb.is_signed == false); } +/// An arbitrary-precision big integer. +/// +/// Memory is allocated by an Int as needed to ensure operations never overflow. The range of an +/// Int is bounded only by available memory. pub const Int = struct { - allocator: *Allocator, - positive: bool, - // - little-endian ordered - // - len >= 1 always - // - zero value -> len == 1 with limbs[0] == 0 + const sign_bit: usize = 1 << (usize.bit_count - 1); + + /// Default number of limbs to allocate on creation of an Int. + pub const default_capacity = 4; + + /// Allocator used by the Int when requesting memory. + allocator: ?*Allocator, + + /// Raw digits. These are: + /// + /// * Little-endian ordered + /// * limbs.len >= 1 + /// * Zero is represent as Int.len() == 1 with limbs[0] == 0. + /// + /// Accessing limbs directly should be avoided. limbs: []Limb, - len: usize, - const default_capacity = 4; + /// High bit is the sign bit. If set, Int is negative, else Int is positive. + /// The remaining bits represent the number of limbs used by Int. + metadata: usize, + /// Creates a new Int. default_capacity limbs will be allocated immediately. + /// Int will be zeroed. pub fn init(allocator: *Allocator) !Int { return try Int.initCapacity(allocator, default_capacity); } + /// Creates a new Int. Int will be set to `value`. + /// + /// This is identical to an `init`, followed by a `set`. pub fn initSet(allocator: *Allocator, value: var) !Int { var s = try Int.init(allocator); try s.set(value); return s; } + /// Creates a new Int with a specific capacity. If capacity < default_capacity then the + /// default capacity will be used instead. pub fn initCapacity(allocator: *Allocator, capacity: usize) !Int { return Int{ .allocator = allocator, - .positive = true, + .metadata = 1, .limbs = block: { var limbs = try allocator.alloc(Limb, math.max(default_capacity, capacity)); limbs[0] = 0; break :block limbs; }, - .len = 1, }; } + /// Returns the number of limbs currently in use. + pub fn len(self: Int) usize { + return self.metadata & ~sign_bit; + } + + /// Returns whether an Int is positive. + pub fn isPositive(self: Int) bool { + return self.metadata & sign_bit == 0; + } + + /// Sets the sign of an Int. + pub fn setSign(self: *Int, positive: bool) void { + if (positive) { + self.metadata &= ~sign_bit; + } else { + self.metadata |= sign_bit; + } + } + + /// Sets the length of an Int. + /// + /// If setLen is used, then the Int must be normalized to suit. + pub fn setLen(self: *Int, new_len: usize) void { + self.metadata &= sign_bit; + self.metadata |= new_len; + } + + /// Returns an Int backed by a fixed set of limb values. + /// This is read-only and cannot be used as a result argument. If the Int tries to allocate + /// memory a runtime panic will occur. + pub fn initFixed(limbs: []const Limb) Int { + var self = Int{ + .allocator = null, + .metadata = limbs.len, + // Cast away the const, invalid use to pass as a pointer argument. + .limbs = @intToPtr([*]Limb, @ptrToInt(limbs.ptr))[0..limbs.len], + }; + + self.normalize(limbs.len); + return self; + } + + /// Ensures an Int has enough space allocated for capacity limbs. If the Int does not have + /// sufficient capacity, the exact amount will be allocated. This occurs even if the requested + /// capacity is only greater than the current capacity by one limb. pub fn ensureCapacity(self: *Int, capacity: usize) !void { + self.assertWritable(); if (capacity <= self.limbs.len) { return; } - self.limbs = try self.allocator.realloc(self.limbs, capacity); + self.limbs = try self.allocator.?.realloc(self.limbs, capacity); } + fn assertWritable(self: Int) void { + if (self.allocator == null) { + @panic("provided Int value is read-only but must be writable"); + } + } + + /// Frees all memory associated with an Int. pub fn deinit(self: *Int) void { - self.allocator.free(self.limbs); + self.assertWritable(); + self.allocator.?.free(self.limbs); self.* = undefined; } + /// Clones an Int and returns a new Int with the same value. The new Int is a deep copy and + /// can be modified separately from the original. pub fn clone(other: Int) !Int { + other.assertWritable(); return Int{ .allocator = other.allocator, - .positive = other.positive, + .metadata = other.metadata, .limbs = block: { - var limbs = try other.allocator.alloc(Limb, other.len); - mem.copy(Limb, limbs[0..], other.limbs[0..other.len]); + var limbs = try other.allocator.?.alloc(Limb, other.len()); + mem.copy(Limb, limbs[0..], other.limbs[0..other.len()]); break :block limbs; }, - .len = other.len, }; } + /// Copies the value of an Int to an existing Int so that they both have the same value. + /// Extra memory will be allocated if the receiver does not have enough capacity. pub fn copy(self: *Int, other: Int) !void { - if (self == &other) { + self.assertWritable(); + if (self.limbs.ptr == other.limbs.ptr) { return; } - self.positive = other.positive; - try self.ensureCapacity(other.len); - mem.copy(Limb, self.limbs[0..], other.limbs[0..other.len]); - self.len = other.len; + try self.ensureCapacity(other.len()); + mem.copy(Limb, self.limbs[0..], other.limbs[0..other.len()]); + self.metadata = other.metadata; } + /// Efficiently swap an Int with another. This swaps the limb pointers and a full copy is not + /// performed. The address of the limbs field will not be the same after this function. pub fn swap(self: *Int, other: *Int) void { + self.assertWritable(); mem.swap(Int, self, other); } @@ -103,45 +185,49 @@ pub const Int = struct { debug.warn("\n"); } - pub fn negate(r: *Int) void { - r.positive = !r.positive; + /// Negate the sign of an Int. + pub fn negate(self: *Int) void { + self.metadata ^= sign_bit; } - pub fn abs(r: *Int) void { - r.positive = true; + /// Make an Int positive. + pub fn abs(self: *Int) void { + self.metadata &= ~sign_bit; } - pub fn isOdd(r: Int) bool { - return r.limbs[0] & 1 != 0; + /// Returns true if an Int is odd. + pub fn isOdd(self: Int) bool { + return self.limbs[0] & 1 != 0; } - pub fn isEven(r: Int) bool { - return !r.isOdd(); + /// Returns true if an Int is even. + pub fn isEven(self: Int) bool { + return !self.isOdd(); } - // Returns the number of bits required to represent the absolute value of self. + /// Returns the number of bits required to represent the absolute value an Int. fn bitCountAbs(self: Int) usize { - return (self.len - 1) * Limb.bit_count + (Limb.bit_count - @clz(self.limbs[self.len - 1])); + return (self.len() - 1) * Limb.bit_count + (Limb.bit_count - @clz(self.limbs[self.len() - 1])); } - // Returns the number of bits required to represent the integer in twos-complement form. - // - // If the integer is negative the value returned is the number of bits needed by a signed - // integer to represent the value. If positive the value is the number of bits for an - // unsigned integer. Any unsigned integer will fit in the signed integer with bitcount - // one greater than the returned value. - // - // e.g. -127 returns 8 as it will fit in an i8. 127 returns 7 since it fits in a u7. + /// Returns the number of bits required to represent the integer in twos-complement form. + /// + /// If the integer is negative the value returned is the number of bits needed by a signed + /// integer to represent the value. If positive the value is the number of bits for an + /// unsigned integer. Any unsigned integer will fit in the signed integer with bitcount + /// one greater than the returned value. + /// + /// e.g. -127 returns 8 as it will fit in an i8. 127 returns 7 since it fits in a u7. fn bitCountTwosComp(self: Int) usize { var bits = self.bitCountAbs(); // If the entire value has only one bit set (e.g. 0b100000000) then the negation in twos // complement requires one less bit. - if (!self.positive) block: { + if (!self.isPositive()) block: { bits += 1; - if (@popCount(self.limbs[self.len - 1]) == 1) { - for (self.limbs[0 .. self.len - 1]) |limb| { + if (@popCount(self.limbs[self.len() - 1]) == 1) { + for (self.limbs[0 .. self.len() - 1]) |limb| { if (@popCount(limb) != 0) { break :block; } @@ -154,31 +240,34 @@ pub const Int = struct { return bits; } - pub fn fitsInTwosComp(self: Int, is_signed: bool, bit_count: usize) bool { + fn fitsInTwosComp(self: Int, is_signed: bool, bit_count: usize) bool { if (self.eqZero()) { return true; } - if (!is_signed and !self.positive) { + if (!is_signed and !self.isPositive()) { return false; } - const req_bits = self.bitCountTwosComp() + @boolToInt(self.positive and is_signed); + const req_bits = self.bitCountTwosComp() + @boolToInt(self.isPositive() and is_signed); return bit_count >= req_bits; } + /// Returns whether self can fit into an integer of the requested type. pub fn fits(self: Int, comptime T: type) bool { return self.fitsInTwosComp(T.is_signed, T.bit_count); } - // Returns the approximate size of the integer in the given base. Negative values accommodate for - // the minus sign. This is used for determining the number of characters needed to print the - // value. It is inexact and will exceed the given value by 1-2 digits. + /// Returns the approximate size of the integer in the given base. Negative values accommodate for + /// the minus sign. This is used for determining the number of characters needed to print the + /// value. It is inexact and may exceed the given value by ~1-2 bytes. pub fn sizeInBase(self: Int, base: usize) usize { - const bit_count = usize(@boolToInt(!self.positive)) + self.bitCountAbs(); + const bit_count = usize(@boolToInt(!self.isPositive())) + self.bitCountAbs(); return (bit_count / math.log2(base)) + 1; } + /// Sets an Int to value. Value must be an primitive integer type. pub fn set(self: *Int, value: var) Allocator.Error!void { + self.assertWritable(); const T = @typeOf(value); switch (@typeInfo(T)) { @@ -186,19 +275,19 @@ pub const Int = struct { const UT = if (T.is_signed) @IntType(false, T.bit_count - 1) else T; try self.ensureCapacity(@sizeOf(UT) / @sizeOf(Limb)); - self.positive = value >= 0; - self.len = 0; + self.metadata = 0; + self.setSign(value >= 0); var w_value: UT = if (value < 0) @intCast(UT, -value) else @intCast(UT, value); if (info.bits <= Limb.bit_count) { self.limbs[0] = Limb(w_value); - self.len = 1; + self.metadata += 1; } else { var i: usize = 0; while (w_value != 0) : (i += 1) { self.limbs[i] = @truncate(Limb, w_value); - self.len += 1; + self.metadata += 1; // TODO: shift == 64 at compile-time fails. Fails on u128 limbs. w_value >>= Limb.bit_count / 2; @@ -212,8 +301,8 @@ pub const Int = struct { const req_limbs = @divFloor(math.log2(w_value), Limb.bit_count) + 1; try self.ensureCapacity(req_limbs); - self.positive = value >= 0; - self.len = req_limbs; + self.metadata = req_limbs; + self.setSign(value >= 0); if (w_value <= maxInt(Limb)) { self.limbs[0] = w_value; @@ -240,6 +329,9 @@ pub const Int = struct { TargetTooSmall, }; + /// Convert self to type T. + /// + /// Returns an error if self cannot be narrowed into the requested type without truncation. pub fn to(self: Int, comptime T: type) ConvertError!T { switch (@typeId(T)) { TypeId.Int => { @@ -254,17 +346,17 @@ pub const Int = struct { if (@sizeOf(UT) <= @sizeOf(Limb)) { r = @intCast(UT, self.limbs[0]); } else { - for (self.limbs[0..self.len]) |_, ri| { - const limb = self.limbs[self.len - ri - 1]; + for (self.limbs[0..self.len()]) |_, ri| { + const limb = self.limbs[self.len() - ri - 1]; r <<= Limb.bit_count; r |= limb; } } if (!T.is_signed) { - return if (self.positive) @intCast(T, r) else error.NegativeIntoUnsigned; + return if (self.isPositive()) @intCast(T, r) else error.NegativeIntoUnsigned; } else { - if (self.positive) { + if (self.isPositive()) { return @intCast(T, r); } else { if (math.cast(T, r)) |ok| { @@ -303,7 +395,15 @@ pub const Int = struct { }; } + /// Set self from the string representation `value`. + /// + /// value must contain only digits <= `base`. Base prefixes are not allowed (e.g. 0x43 should + /// simply be 43). + /// + /// Returns an error if memory could not be allocated or `value` has invalid digits for the + /// requested base. pub fn setString(self: *Int, base: u8, value: []const u8) !void { + self.assertWritable(); if (base < 2 or base > 16) { return error.InvalidBase; } @@ -315,27 +415,22 @@ pub const Int = struct { i += 1; } - // TODO values less than limb size should guarantee non allocating - var base_buffer: [512]u8 = undefined; - const base_al = &std.heap.FixedBufferAllocator.init(base_buffer[0..]).allocator; - const base_ap = try Int.initSet(base_al, base); - - var d_buffer: [512]u8 = undefined; - var d_fba = std.heap.FixedBufferAllocator.init(d_buffer[0..]); - const d_al = &d_fba.allocator; - + const ap_base = Int.initFixed(([]Limb{base})[0..]); try self.set(0); + for (value[i..]) |ch| { const d = try charToDigit(ch, base); - d_fba.end_index = 0; - const d_ap = try Int.initSet(d_al, d); - try self.mul(self.*, base_ap); - try self.add(self.*, d_ap); + const ap_d = Int.initFixed(([]Limb{d})[0..]); + + try self.mul(self.*, ap_base); + try self.add(self.*, ap_d); } - self.positive = positive; + self.setSign(positive); } + /// Converts self to a string in the requested base. Memory is allocated from the provided + /// allocator and not the one present in self. /// TODO make this call format instead of the other way around pub fn toString(self: Int, allocator: *Allocator, base: u8) ![]const u8 { if (base < 2 or base > 16) { @@ -355,7 +450,7 @@ pub const Int = struct { if (base & (base - 1) == 0) { const base_shift = math.log2_int(Limb, base); - for (self.limbs[0..self.len]) |limb| { + for (self.limbs[0..self.len()]) |limb| { var shift: usize = 0; while (shift < Limb.bit_count) : (shift += base_shift) { const r = @intCast(u8, (limb >> @intCast(Log2Limb, shift)) & Limb(base - 1)); @@ -382,11 +477,11 @@ pub const Int = struct { } var q = try self.clone(); - q.positive = true; + q.abs(); var r = try Int.init(allocator); var b = try Int.initSet(allocator, limb_base); - while (q.len >= 2) { + while (q.len() >= 2) { try Int.divTrunc(&q, &r, q, b); var r_word = r.limbs[0]; @@ -399,7 +494,7 @@ pub const Int = struct { } { - debug.assert(q.len == 1); + debug.assert(q.len() == 1); var r_word = q.limbs[0]; while (r_word != 0) { @@ -410,7 +505,7 @@ pub const Int = struct { } } - if (!self.positive) { + if (!self.isPositive()) { try digits.append('-'); } @@ -419,7 +514,7 @@ pub const Int = struct { return s; } - /// for the std lib format function + /// To allow `std.fmt.printf` to work with Int. /// TODO make this non-allocating pub fn format( self: Int, @@ -428,22 +523,24 @@ pub const Int = struct { comptime FmtError: type, output: fn (@typeOf(context), []const u8) FmtError!void, ) FmtError!void { + self.assertWritable(); // TODO look at fmt and support other bases - const str = self.toString(self.allocator, 10) catch @panic("TODO make this non allocating"); - defer self.allocator.free(str); + // TODO support read-only fixed integers + const str = self.toString(self.allocator.?, 10) catch @panic("TODO make this non allocating"); + defer self.allocator.?.free(str); return output(context, str); } - // returns -1, 0, 1 if |a| < |b|, |a| == |b| or |a| > |b| respectively. + /// Returns -1, 0, 1 if |a| < |b|, |a| == |b| or |a| > |b| respectively. pub fn cmpAbs(a: Int, b: Int) i8 { - if (a.len < b.len) { + if (a.len() < b.len()) { return -1; } - if (a.len > b.len) { + if (a.len() > b.len()) { return 1; } - var i: usize = a.len - 1; + var i: usize = a.len() - 1; while (i != 0) : (i -= 1) { if (a.limbs[i] != b.limbs[i]) { break; @@ -459,53 +556,37 @@ pub const Int = struct { } } - // returns -1, 0, 1 if a < b, a == b or a > b respectively. + /// Returns -1, 0, 1 if a < b, a == b or a > b respectively. pub fn cmp(a: Int, b: Int) i8 { - if (a.positive != b.positive) { - return if (a.positive) i8(1) else -1; + if (a.isPositive() != b.isPositive()) { + return if (a.isPositive()) i8(1) else -1; } else { const r = cmpAbs(a, b); - return if (a.positive) r else -r; + return if (a.isPositive()) r else -r; } } - // if a == 0 + /// Returns true if a == 0. pub fn eqZero(a: Int) bool { - return a.len == 1 and a.limbs[0] == 0; + return a.len() == 1 and a.limbs[0] == 0; } - // if |a| == |b| + /// Returns true if |a| == |b|. pub fn eqAbs(a: Int, b: Int) bool { return cmpAbs(a, b) == 0; } - // if a == b + /// Returns true if a == b. pub fn eq(a: Int, b: Int) bool { return cmp(a, b) == 0; } - // Normalize for a possible single carry digit. - // - // [1, 2, 3, 4, 0] -> [1, 2, 3, 4] - // [1, 2, 3, 4, 5] -> [1, 2, 3, 4, 5] - // [0] -> [0] - fn norm1(r: *Int, length: usize) void { - debug.assert(length > 0); - debug.assert(length <= r.limbs.len); - - if (r.limbs[length - 1] == 0) { - r.len = if (length > 1) length - 1 else 1; - } else { - r.len = length; - } - } - // Normalize a possible sequence of leading zeros. // // [1, 2, 3, 4, 0] -> [1, 2, 3, 4] // [1, 2, 0, 0, 0] -> [1, 2] // [0, 0, 0, 0, 0] -> [0] - fn normN(r: *Int, length: usize) void { + fn normalize(r: *Int, length: usize) void { debug.assert(length > 0); debug.assert(length <= r.limbs.len); @@ -517,11 +598,25 @@ pub const Int = struct { } // Handle zero - r.len = if (j != 0) j else 1; + r.setLen(if (j != 0) j else 1); } - // r = a + b + // Cannot be used as a result argument to any function. + fn readOnlyPositive(a: Int) Int { + return Int{ + .allocator = null, + .metadata = a.len(), + .limbs = a.limbs, + }; + } + + /// r = a + b + /// + /// r, a and b may be aliases. + /// + /// Returns an error if memory could not be allocated. pub fn add(r: *Int, a: Int, b: Int) Allocator.Error!void { + r.assertWritable(); if (a.eqZero()) { try r.copy(b); return; @@ -530,38 +625,26 @@ pub const Int = struct { return; } - if (a.positive != b.positive) { - if (a.positive) { + if (a.isPositive() != b.isPositive()) { + if (a.isPositive()) { // (a) + (-b) => a - b - const bp = Int{ - .allocator = undefined, - .positive = true, - .limbs = b.limbs, - .len = b.len, - }; - try r.sub(a, bp); + try r.sub(a, readOnlyPositive(b)); } else { // (-a) + (b) => b - a - const ap = Int{ - .allocator = undefined, - .positive = true, - .limbs = a.limbs, - .len = a.len, - }; - try r.sub(b, ap); + try r.sub(b, readOnlyPositive(a)); } } else { - if (a.len >= b.len) { - try r.ensureCapacity(a.len + 1); - lladd(r.limbs[0..], a.limbs[0..a.len], b.limbs[0..b.len]); - r.norm1(a.len + 1); + if (a.len() >= b.len()) { + try r.ensureCapacity(a.len() + 1); + lladd(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(a.len() + 1); } else { - try r.ensureCapacity(b.len + 1); - lladd(r.limbs[0..], b.limbs[0..b.len], a.limbs[0..a.len]); - r.norm1(b.len + 1); + try r.ensureCapacity(b.len() + 1); + lladd(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(b.len() + 1); } - r.positive = a.positive; + r.setSign(a.isPositive()); } } @@ -589,55 +672,48 @@ pub const Int = struct { r[i] = carry; } - // r = a - b + /// r = a - b + /// + /// r, a and b may be aliases. + /// + /// Returns an error if memory could not be allocated. pub fn sub(r: *Int, a: Int, b: Int) !void { - if (a.positive != b.positive) { - if (a.positive) { + r.assertWritable(); + if (a.isPositive() != b.isPositive()) { + if (a.isPositive()) { // (a) - (-b) => a + b - const bp = Int{ - .allocator = undefined, - .positive = true, - .limbs = b.limbs, - .len = b.len, - }; - try r.add(a, bp); + try r.add(a, readOnlyPositive(b)); } else { // (-a) - (b) => -(a + b) - const ap = Int{ - .allocator = undefined, - .positive = true, - .limbs = a.limbs, - .len = a.len, - }; - try r.add(ap, b); - r.positive = false; + try r.add(readOnlyPositive(a), b); + r.setSign(false); } } else { - if (a.positive) { + if (a.isPositive()) { // (a) - (b) => a - b if (a.cmp(b) >= 0) { - try r.ensureCapacity(a.len + 1); - llsub(r.limbs[0..], a.limbs[0..a.len], b.limbs[0..b.len]); - r.normN(a.len); - r.positive = true; + try r.ensureCapacity(a.len() + 1); + llsub(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(a.len()); + r.setSign(true); } else { - try r.ensureCapacity(b.len + 1); - llsub(r.limbs[0..], b.limbs[0..b.len], a.limbs[0..a.len]); - r.normN(b.len); - r.positive = false; + try r.ensureCapacity(b.len() + 1); + llsub(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(b.len()); + r.setSign(false); } } else { // (-a) - (-b) => -(a - b) if (a.cmp(b) < 0) { - try r.ensureCapacity(a.len + 1); - llsub(r.limbs[0..], a.limbs[0..a.len], b.limbs[0..b.len]); - r.normN(a.len); - r.positive = false; + try r.ensureCapacity(a.len() + 1); + llsub(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(a.len()); + r.setSign(false); } else { - try r.ensureCapacity(b.len + 1); - llsub(r.limbs[0..], b.limbs[0..b.len], a.limbs[0..a.len]); - r.normN(b.len); - r.positive = true; + try r.ensureCapacity(b.len() + 1); + llsub(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(b.len()); + r.setSign(true); } } } @@ -667,16 +743,20 @@ pub const Int = struct { debug.assert(borrow == 0); } - // rma = a * b - // - // For greatest efficiency, ensure rma does not alias a or b. + /// rma = a * b + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. pub fn mul(rma: *Int, a: Int, b: Int) !void { + rma.assertWritable(); + var r = rma; var aliased = rma.limbs.ptr == a.limbs.ptr or rma.limbs.ptr == b.limbs.ptr; var sr: Int = undefined; if (aliased) { - sr = try Int.initCapacity(rma.allocator, a.len + b.len); + sr = try Int.initCapacity(rma.allocator.?, a.len() + b.len()); r = &sr; aliased = true; } @@ -685,16 +765,16 @@ pub const Int = struct { r.deinit(); }; - try r.ensureCapacity(a.len + b.len); + try r.ensureCapacity(a.len() + b.len()); - if (a.len >= b.len) { - llmul(r.limbs, a.limbs[0..a.len], b.limbs[0..b.len]); + if (a.len() >= b.len()) { + llmul(r.limbs, a.limbs[0..a.len()], b.limbs[0..b.len()]); } else { - llmul(r.limbs, b.limbs[0..b.len], a.limbs[0..a.len]); + llmul(r.limbs, b.limbs[0..b.len()], a.limbs[0..a.len()]); } - r.positive = a.positive == b.positive; - r.normN(a.len + b.len); + r.normalize(a.len() + b.len()); + r.setSign(a.isPositive() == b.isPositive()); } // a + b * c + *carry, sets carry to the overflow bits @@ -740,29 +820,34 @@ pub const Int = struct { } } + /// q = a / b (rem r) + /// + /// a / b are floored (rounded towards 0). pub fn divFloor(q: *Int, r: *Int, a: Int, b: Int) !void { try div(q, r, a, b); // Trunc -> Floor. - if (!q.positive) { - // TODO values less than limb size should guarantee non allocating - var one_buffer: [512]u8 = undefined; - const one_al = &std.heap.FixedBufferAllocator.init(one_buffer[0..]).allocator; - const one_ap = try Int.initSet(one_al, 1); - - try q.sub(q.*, one_ap); - try r.add(q.*, one_ap); + if (!q.isPositive()) { + const one = Int.initFixed(([]Limb{1})[0..]); + try q.sub(q.*, one); + try r.add(q.*, one); } - r.positive = b.positive; + r.setSign(b.isPositive()); } + /// q = a / b (rem r) + /// + /// a / b are truncated (rounded towards -inf). pub fn divTrunc(q: *Int, r: *Int, a: Int, b: Int) !void { try div(q, r, a, b); - r.positive = a.positive; + r.setSign(a.isPositive()); } // Truncates by default. fn div(quo: *Int, rem: *Int, a: Int, b: Int) !void { + quo.assertWritable(); + rem.assertWritable(); + if (b.eqZero()) { @panic("division by zero"); } @@ -773,36 +858,67 @@ pub const Int = struct { if (a.cmpAbs(b) < 0) { // quo may alias a so handle rem first try rem.copy(a); - rem.positive = a.positive == b.positive; + rem.setSign(a.isPositive() == b.isPositive()); - quo.positive = true; - quo.len = 1; + quo.metadata = 1; quo.limbs[0] = 0; return; } - if (b.len == 1) { - try quo.ensureCapacity(a.len); + // Handle trailing zero-words of divisor/dividend. These are not handled in the following + // algorithms. + const a_zero_limb_count = blk: { + var i: usize = 0; + while (i < a.len()) : (i += 1) { + if (a.limbs[i] != 0) break; + } + break :blk i; + }; + const b_zero_limb_count = blk: { + var i: usize = 0; + while (i < b.len()) : (i += 1) { + if (b.limbs[i] != 0) break; + } + break :blk i; + }; - lldiv1(quo.limbs[0..], &rem.limbs[0], a.limbs[0..a.len], b.limbs[0]); - quo.norm1(a.len); - quo.positive = a.positive == b.positive; + const ab_zero_limb_count = std.math.min(a_zero_limb_count, b_zero_limb_count); - rem.len = 1; - rem.positive = true; + if (b.len() - ab_zero_limb_count == 1) { + try quo.ensureCapacity(a.len()); + + lldiv1(quo.limbs[0..], &rem.limbs[0], a.limbs[ab_zero_limb_count..a.len()], b.limbs[b.len() - 1]); + quo.normalize(a.len() - ab_zero_limb_count); + quo.setSign(a.isPositive() == b.isPositive()); + + rem.metadata = 1; } else { // x and y are modified during division - var x = try a.clone(); + var x = try Int.initCapacity(quo.allocator.?, a.len()); defer x.deinit(); + try x.copy(a); - var y = try b.clone(); + var y = try Int.initCapacity(quo.allocator.?, b.len()); defer y.deinit(); + try y.copy(b); // x may grow one limb during normalization - try quo.ensureCapacity(a.len + y.len); - try divN(quo.allocator, quo, rem, &x, &y); + try quo.ensureCapacity(a.len() + y.len()); + + // Shrink x, y such that the trailing zero limbs shared between are removed. + if (ab_zero_limb_count != 0) { + std.mem.copy(Limb, x.limbs[0..], x.limbs[ab_zero_limb_count..]); + std.mem.copy(Limb, y.limbs[0..], y.limbs[ab_zero_limb_count..]); + x.metadata -= ab_zero_limb_count; + y.metadata -= ab_zero_limb_count; + } - quo.positive = a.positive == b.positive; + try divN(quo.allocator.?, quo, rem, &x, &y); + quo.setSign(a.isPositive() == b.isPositive()); + } + + if (ab_zero_limb_count != 0) { + try rem.shiftLeft(rem.*, ab_zero_limb_count * Limb.bit_count); } } @@ -837,25 +953,28 @@ pub const Int = struct { // // x = qy + r where 0 <= r < y fn divN(allocator: *Allocator, q: *Int, r: *Int, x: *Int, y: *Int) !void { - debug.assert(y.len >= 2); - debug.assert(x.len >= y.len); - debug.assert(q.limbs.len >= x.len + y.len - 1); + debug.assert(y.len() >= 2); + debug.assert(x.len() >= y.len()); + debug.assert(q.limbs.len >= x.len() + y.len() - 1); debug.assert(default_capacity >= 3); // see 3.2 var tmp = try Int.init(allocator); defer tmp.deinit(); - // Normalize so y > Limb.bit_count / 2 (i.e. leading bit is set) - const norm_shift = @clz(y.limbs[y.len - 1]); + // Normalize so y > Limb.bit_count / 2 (i.e. leading bit is set) and even + var norm_shift = @clz(y.limbs[y.len() - 1]); + if (norm_shift == 0 and y.isOdd()) { + norm_shift = Limb.bit_count; + } try x.shiftLeft(x.*, norm_shift); try y.shiftLeft(y.*, norm_shift); - const n = x.len - 1; - const t = y.len - 1; + const n = x.len() - 1; + const t = y.len() - 1; // 1. - q.len = n - t + 1; - mem.set(Limb, q.limbs[0..q.len], 0); + q.metadata = n - t + 1; + mem.set(Limb, q.limbs[0..q.len()], 0); // 2. try tmp.shiftLeft(y.*, Limb.bit_count * (n - t)); @@ -880,7 +999,7 @@ pub const Int = struct { tmp.limbs[0] = if (i >= 2) x.limbs[i - 2] else 0; tmp.limbs[1] = if (i >= 1) x.limbs[i - 1] else 0; tmp.limbs[2] = x.limbs[i]; - tmp.normN(3); + tmp.normalize(3); while (true) { // 2x1 limb multiplication unrolled against single-limb q[i-t-1] @@ -888,7 +1007,7 @@ pub const Int = struct { r.limbs[0] = addMulLimbWithCarry(0, if (t >= 1) y.limbs[t - 1] else 0, q.limbs[i - t - 1], &carry); r.limbs[1] = addMulLimbWithCarry(0, y.limbs[t], q.limbs[i - t - 1], &carry); r.limbs[2] = carry; - r.normN(3); + r.normalize(3); if (r.cmpAbs(tmp) <= 0) { break; @@ -903,7 +1022,7 @@ pub const Int = struct { try tmp.shiftLeft(tmp, Limb.bit_count * (i - t - 1)); try x.sub(x.*, tmp); - if (!x.positive) { + if (!x.isPositive()) { try tmp.shiftLeft(y.*, Limb.bit_count * (i - t - 1)); try x.add(x.*, tmp); q.limbs[i - t - 1] -= 1; @@ -911,18 +1030,20 @@ pub const Int = struct { } // Denormalize - q.normN(q.len); + q.normalize(q.len()); try r.shiftRight(x.*, norm_shift); - r.normN(r.len); + r.normalize(r.len()); } - // r = a << shift, in other words, r = a * 2^shift + /// r = a << shift, in other words, r = a * 2^shift pub fn shiftLeft(r: *Int, a: Int, shift: usize) !void { - try r.ensureCapacity(a.len + (shift / Limb.bit_count) + 1); - llshl(r.limbs[0..], a.limbs[0..a.len], shift); - r.norm1(a.len + (shift / Limb.bit_count) + 1); - r.positive = a.positive; + r.assertWritable(); + + try r.ensureCapacity(a.len() + (shift / Limb.bit_count) + 1); + llshl(r.limbs[0..], a.limbs[0..a.len()], shift); + r.normalize(a.len() + (shift / Limb.bit_count) + 1); + r.setSign(a.isPositive()); } fn llshl(r: []Limb, a: []const Limb, shift: usize) void { @@ -948,19 +1069,20 @@ pub const Int = struct { mem.set(Limb, r[0 .. limb_shift - 1], 0); } - // r = a >> shift + /// r = a >> shift pub fn shiftRight(r: *Int, a: Int, shift: usize) !void { - if (a.len <= shift / Limb.bit_count) { - r.len = 1; + r.assertWritable(); + + if (a.len() <= shift / Limb.bit_count) { + r.metadata = 1; r.limbs[0] = 0; - r.positive = true; return; } - try r.ensureCapacity(a.len - (shift / Limb.bit_count)); - const r_len = llshr(r.limbs[0..], a.limbs[0..a.len], shift); - r.len = a.len - (shift / Limb.bit_count); - r.positive = a.positive; + try r.ensureCapacity(a.len() - (shift / Limb.bit_count)); + const r_len = llshr(r.limbs[0..], a.limbs[0..a.len()], shift); + r.metadata = a.len() - (shift / Limb.bit_count); + r.setSign(a.isPositive()); } fn llshr(r: []Limb, a: []const Limb, shift: usize) void { @@ -983,16 +1105,20 @@ pub const Int = struct { } } - // r = a | b + /// r = a | b + /// + /// a and b are zero-extended to the longer of a or b. pub fn bitOr(r: *Int, a: Int, b: Int) !void { - if (a.len > b.len) { - try r.ensureCapacity(a.len); - llor(r.limbs[0..], a.limbs[0..a.len], b.limbs[0..b.len]); - r.len = a.len; + r.assertWritable(); + + if (a.len() > b.len()) { + try r.ensureCapacity(a.len()); + llor(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.setLen(a.len()); } else { - try r.ensureCapacity(b.len); - llor(r.limbs[0..], b.limbs[0..b.len], a.limbs[0..a.len]); - r.len = b.len; + try r.ensureCapacity(b.len()); + llor(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.setLen(b.len()); } } @@ -1010,16 +1136,18 @@ pub const Int = struct { } } - // r = a & b + /// r = a & b pub fn bitAnd(r: *Int, a: Int, b: Int) !void { - if (a.len > b.len) { - try r.ensureCapacity(b.len); - lland(r.limbs[0..], a.limbs[0..a.len], b.limbs[0..b.len]); - r.normN(b.len); + r.assertWritable(); + + if (a.len() > b.len()) { + try r.ensureCapacity(b.len()); + lland(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(b.len()); } else { - try r.ensureCapacity(a.len); - lland(r.limbs[0..], b.limbs[0..b.len], a.limbs[0..a.len]); - r.normN(a.len); + try r.ensureCapacity(a.len()); + lland(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(a.len()); } } @@ -1034,16 +1162,18 @@ pub const Int = struct { } } - // r = a ^ b + /// r = a ^ b pub fn bitXor(r: *Int, a: Int, b: Int) !void { - if (a.len > b.len) { - try r.ensureCapacity(a.len); - llxor(r.limbs[0..], a.limbs[0..a.len], b.limbs[0..b.len]); - r.normN(a.len); + r.assertWritable(); + + if (a.len() > b.len()) { + try r.ensureCapacity(a.len()); + llxor(r.limbs[0..], a.limbs[0..a.len()], b.limbs[0..b.len()]); + r.normalize(a.len()); } else { - try r.ensureCapacity(b.len); - llxor(r.limbs[0..], b.limbs[0..b.len], a.limbs[0..a.len]); - r.normN(b.len); + try r.ensureCapacity(b.len()); + llxor(r.limbs[0..], b.limbs[0..b.len()], a.limbs[0..a.len()]); + r.normalize(b.len()); } } @@ -1067,7 +1197,9 @@ pub const Int = struct { // They will still run on larger than this and should pass, but the multi-limb code-paths // may be untested in some cases. -const al = debug.global_allocator; +var buffer: [64 * 8192]u8 = undefined; +var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); +const al = &fixed.allocator; test "big.int comptime_int set" { comptime var s = 0xefffffff00000001eeeeeeefaaaaaaab; @@ -1088,14 +1220,14 @@ test "big.int comptime_int set negative" { var a = try Int.initSet(al, -10); testing.expect(a.limbs[0] == 10); - testing.expect(a.positive == false); + testing.expect(a.isPositive() == false); } test "big.int int set unaligned small" { var a = try Int.initSet(al, u7(45)); testing.expect(a.limbs[0] == 45); - testing.expect(a.positive == true); + testing.expect(a.isPositive() == true); } test "big.int comptime_int to" { @@ -1116,7 +1248,7 @@ test "big.int to target too small error" { testing.expectError(error.TargetTooSmall, a.to(u8)); } -test "big.int norm1" { +test "big.int normalize" { var a = try Int.init(al); try a.ensureCapacity(8); @@ -1124,26 +1256,26 @@ test "big.int norm1" { a.limbs[1] = 2; a.limbs[2] = 3; a.limbs[3] = 0; - a.norm1(4); - testing.expect(a.len == 3); + a.normalize(4); + testing.expect(a.len() == 3); a.limbs[0] = 1; a.limbs[1] = 2; a.limbs[2] = 3; - a.norm1(3); - testing.expect(a.len == 3); + a.normalize(3); + testing.expect(a.len() == 3); a.limbs[0] = 0; a.limbs[1] = 0; - a.norm1(2); - testing.expect(a.len == 1); + a.normalize(2); + testing.expect(a.len() == 1); a.limbs[0] = 0; - a.norm1(1); - testing.expect(a.len == 1); + a.normalize(1); + testing.expect(a.len() == 1); } -test "big.int normN" { +test "big.int normalize multi" { var a = try Int.init(al); try a.ensureCapacity(8); @@ -1151,25 +1283,25 @@ test "big.int normN" { a.limbs[1] = 2; a.limbs[2] = 0; a.limbs[3] = 0; - a.normN(4); - testing.expect(a.len == 2); + a.normalize(4); + testing.expect(a.len() == 2); a.limbs[0] = 1; a.limbs[1] = 2; a.limbs[2] = 3; - a.normN(3); - testing.expect(a.len == 3); + a.normalize(3); + testing.expect(a.len() == 3); a.limbs[0] = 0; a.limbs[1] = 0; a.limbs[2] = 0; a.limbs[3] = 0; - a.normN(4); - testing.expect(a.len == 1); + a.normalize(4); + testing.expect(a.len() == 1); a.limbs[0] = 0; - a.normN(1); - testing.expect(a.len == 1); + a.normalize(1); + testing.expect(a.len() == 1); } test "big.int parity" { @@ -1204,7 +1336,7 @@ test "big.int bitcount + sizeInBase" { try a.shiftLeft(a, 5000); testing.expect(a.bitCountAbs() == 5032); testing.expect(a.sizeInBase(2) >= 5032); - a.positive = false; + a.setSign(false); testing.expect(a.bitCountAbs() == 5032); testing.expect(a.sizeInBase(2) >= 5033); @@ -1216,10 +1348,8 @@ test "big.int bitcount/to" { try a.set(0); testing.expect(a.bitCountTwosComp() == 0); - // TODO: stack smashing - // testing.expect((try a.to(u0)) == 0); - // TODO: sigsegv - // testing.expect((try a.to(i0)) == 0); + testing.expect((try a.to(u0)) == 0); + testing.expect((try a.to(i0)) == 0); try a.set(-1); testing.expect(a.bitCountTwosComp() == 1); @@ -1980,6 +2110,98 @@ test "big.int div multi-multi (3.1/3.3 branch)" { testing.expect((try r.to(u256)) == 0x1111111111111111111110b12222222222222222282); } +test "big.int div multi-single zero-limb trailing" { + var a = try Int.initSet(al, 0x60000000000000000000000000000000000000000000000000000000000000000); + var b = try Int.initSet(al, 0x10000000000000000); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + var expected = try Int.initSet(al, 0x6000000000000000000000000000000000000000000000000); + testing.expect(q.eq(expected)); + testing.expect(r.eqZero()); +} + +test "big.int div multi-multi zero-limb trailing (with rem)" { + var a = try Int.initSet(al, 0x86666666555555558888888777777776111111111111111100000000000000000000000000000000); + var b = try Int.initSet(al, 0x8666666655555555444444443333333300000000000000000000000000000000); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == 0x10000000000000000); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "4444444344444443111111111111111100000000000000000000000000000000")); +} + +test "big.int div multi-multi zero-limb trailing (with rem) and dividend zero-limb count > divisor zero-limb count" { + var a = try Int.initSet(al, 0x8666666655555555888888877777777611111111111111110000000000000000); + var b = try Int.initSet(al, 0x8666666655555555444444443333333300000000000000000000000000000000); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + testing.expect((try q.to(u128)) == 0x1); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "444444434444444311111111111111110000000000000000")); +} + +test "big.int div multi-multi zero-limb trailing (with rem) and dividend zero-limb count < divisor zero-limb count" { + var a = try Int.initSet(al, 0x86666666555555558888888777777776111111111111111100000000000000000000000000000000); + var b = try Int.initSet(al, 0x866666665555555544444444333333330000000000000000); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + const qs = try q.toString(al, 16); + testing.expect(std.mem.eql(u8, qs, "10000000000000000820820803105186f")); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "4e11f2baa5896a321d463b543d0104e30000000000000000")); +} + +test "big.int div multi-multi fuzz case #1" { + var a = try Int.init(al); + var b = try Int.init(al); + + try a.setString(16, "ffffffffffffffffffffffffffffc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + try b.setString(16, "3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0000000000000000000000000000000000001ffffffffffffffffffffffffffffffffffffffffffffffffffc000000000000000000000000000000007fffffffffff"); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + const qs = try q.toString(al, 16); + testing.expect(std.mem.eql(u8, qs, "3ffffffffffffffffffffffffffff0000000000000000000000000000000000001ffffffffffffffffffffffffffff7fffffffe000000000000000000000000000180000000000000000000003fffffbfffffffdfffffffffffffeffff800000100101000000100000000020003fffffdfbfffffe3ffffffffffffeffff7fffc00800a100000017ffe000002000400007efbfff7fe9f00000037ffff3fff7fffa004006100000009ffe00000190038200bf7d2ff7fefe80400060000f7d7f8fbf9401fe38e0403ffc0bdffffa51102c300d7be5ef9df4e5060007b0127ad3fa69f97d0f820b6605ff617ddf7f32ad7a05c0d03f2e7bc78a6000e087a8bbcdc59e07a5a079128a7861f553ddebed7e8e56701756f9ead39b48cd1b0831889ea6ec1fddf643d0565b075ff07e6caea4e2854ec9227fd635ed60a2f5eef2893052ffd54718fa08604acbf6a15e78a467c4a3c53c0278af06c4416573f925491b195e8fd79302cb1aaf7caf4ecfc9aec1254cc969786363ac729f914c6ddcc26738d6b0facd54eba026580aba2eb6482a088b0d224a8852420b91ec1")); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "310d1d4c414426b4836c2635bad1df3a424e50cbdd167ffccb4dfff57d36b4aae0d6ca0910698220171a0f3373c1060a046c2812f0027e321f72979daa5e7973214170d49e885de0c0ecc167837d44502430674a82522e5df6a0759548052420b91ec1")); +} + +test "big.int div multi-multi fuzz case #2" { + var a = try Int.init(al); + var b = try Int.init(al); + + try a.setString(16, "3ffffffffe00000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffe000000000000000000000000000000000000000000000000000000000000001fffffffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffc000000000000000000000000000000000000000000000000000000000000000"); + try b.setString(16, "ffc0000000000000000000000000000000000000000000000000"); + + var q = try Int.init(al); + var r = try Int.init(al); + try Int.divTrunc(&q, &r, a, b); + + const qs = try q.toString(al, 16); + testing.expect(std.mem.eql(u8, qs, "40100400fe3f8fe3f8fe3f8fe3f8fe3f8fe4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f93e4f91e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4992649926499264991e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4791e4792e4b92e4b92e4b92e4b92a4a92a4a92a4")); + + const rs = try r.toString(al, 16); + testing.expect(std.mem.eql(u8, rs, "a900000000000000000000000000000000000000000000000000")); +} + test "big.int shift-right single" { var a = try Int.initSet(al, 0xffff0000); try a.shiftRight(a, 16); diff --git a/std/math/big/rational.zig b/std/math/big/rational.zig new file mode 100644 index 0000000000..58a5e3ac76 --- /dev/null +++ b/std/math/big/rational.zig @@ -0,0 +1,938 @@ +const std = @import("../../std.zig"); +const builtin = @import("builtin"); +const debug = std.debug; +const math = std.math; +const mem = std.mem; +const testing = std.testing; +const Allocator = mem.Allocator; +const ArrayList = std.ArrayList; + +const TypeId = builtin.TypeId; + +const bn = @import("int.zig"); +const Limb = bn.Limb; +const DoubleLimb = bn.DoubleLimb; +const Int = bn.Int; + +/// An arbitrary-precision rational number. +/// +/// Memory is allocated as needed for operations to ensure full precision is kept. The precision +/// of a Rational is only bounded by memory. +/// +/// Rational's are always normalized. That is, for a Rational r = p/q where p and q are integers, +/// gcd(p, q) = 1 always. +pub const Rational = struct { + /// Numerator. Determines the sign of the Rational. + p: Int, + + /// Denominator. Sign is ignored. + q: Int, + + /// Create a new Rational. A small amount of memory will be allocated on initialization. + /// This will be 2 * Int.default_capacity. + pub fn init(a: *Allocator) !Rational { + return Rational{ + .p = try Int.init(a), + .q = try Int.initSet(a, 1), + }; + } + + /// Frees all memory associated with a Rational. + pub fn deinit(self: *Rational) void { + self.p.deinit(); + self.q.deinit(); + } + + /// Set a Rational from a primitive integer type. + pub fn setInt(self: *Rational, a: var) !void { + try self.p.set(a); + try self.q.set(1); + } + + /// Set a Rational from a string of the form `A/B` where A and B are base-10 integers. + pub fn setFloatString(self: *Rational, str: []const u8) !void { + // TODO: Accept a/b fractions and exponent form + if (str.len == 0) { + return error.InvalidFloatString; + } + + const State = enum { + Integer, + Fractional, + }; + + var state = State.Integer; + var point: ?usize = null; + + var start: usize = 0; + if (str[0] == '-') { + start += 1; + } + + for (str) |c, i| { + switch (state) { + State.Integer => { + switch (c) { + '.' => { + state = State.Fractional; + point = i; + }, + '0'...'9' => { + // okay + }, + else => { + return error.InvalidFloatString; + }, + } + }, + State.Fractional => { + switch (c) { + '0'...'9' => { + // okay + }, + else => { + return error.InvalidFloatString; + }, + } + }, + } + } + + // TODO: batch the multiplies by 10 + if (point) |i| { + try self.p.setString(10, str[0..i]); + + const base = Int.initFixed(([]Limb{10})[0..]); + + var j: usize = start; + while (j < str.len - i - 1) : (j += 1) { + try self.p.mul(self.p, base); + } + + try self.q.setString(10, str[i + 1 ..]); + try self.p.add(self.p, self.q); + + try self.q.set(1); + var k: usize = i + 1; + while (k < str.len) : (k += 1) { + try self.q.mul(self.q, base); + } + + try self.reduce(); + } else { + try self.p.setString(10, str[0..]); + try self.q.set(1); + } + } + + /// Set a Rational from a floating-point value. The rational will have enough precision to + /// completely represent the provided float. + pub fn setFloat(self: *Rational, comptime T: type, f: T) !void { + // Translated from golang.go/src/math/big/rat.go. + debug.assert(@typeId(T) == builtin.TypeId.Float); + + const UnsignedIntType = @IntType(false, T.bit_count); + const f_bits = @bitCast(UnsignedIntType, f); + + const exponent_bits = math.floatExponentBits(T); + const exponent_bias = (1 << (exponent_bits - 1)) - 1; + const mantissa_bits = math.floatMantissaBits(T); + + const exponent_mask = (1 << exponent_bits) - 1; + const mantissa_mask = (1 << mantissa_bits) - 1; + + var exponent = @intCast(i16, (f_bits >> mantissa_bits) & exponent_mask); + var mantissa = f_bits & mantissa_mask; + + switch (exponent) { + exponent_mask => { + return error.NonFiniteFloat; + }, + 0 => { + // denormal + exponent -= exponent_bias - 1; + }, + else => { + // normal + mantissa |= 1 << mantissa_bits; + exponent -= exponent_bias; + }, + } + + var shift: i16 = mantissa_bits - exponent; + + // factor out powers of two early from rational + while (mantissa & 1 == 0 and shift > 0) { + mantissa >>= 1; + shift -= 1; + } + + try self.p.set(mantissa); + self.p.setSign(f >= 0); + + try self.q.set(1); + if (shift >= 0) { + try self.q.shiftLeft(self.q, @intCast(usize, shift)); + } else { + try self.p.shiftLeft(self.p, @intCast(usize, -shift)); + } + + try self.reduce(); + } + + /// Return a floating-point value that is the closest value to a Rational. + /// + /// The result may not be exact if the Rational is too precise or too large for the + /// target type. + pub fn toFloat(self: Rational, comptime T: type) !T { + // Translated from golang.go/src/math/big/rat.go. + // TODO: Indicate whether the result is not exact. + debug.assert(@typeId(T) == builtin.TypeId.Float); + + const fsize = T.bit_count; + const BitReprType = @IntType(false, T.bit_count); + + const msize = math.floatMantissaBits(T); + const msize1 = msize + 1; + const msize2 = msize1 + 1; + + const esize = math.floatExponentBits(T); + const ebias = (1 << (esize - 1)) - 1; + const emin = 1 - ebias; + const emax = ebias; + + if (self.p.eqZero()) { + return 0; + } + + // 1. left-shift a or sub so that a/b is in [1 << msize1, 1 << (msize2 + 1)] + var exp = @intCast(isize, self.p.bitCountTwosComp()) - @intCast(isize, self.q.bitCountTwosComp()); + + var a2 = try self.p.clone(); + defer a2.deinit(); + + var b2 = try self.q.clone(); + defer b2.deinit(); + + const shift = msize2 - exp; + if (shift >= 0) { + try a2.shiftLeft(a2, @intCast(usize, shift)); + } else { + try b2.shiftLeft(b2, @intCast(usize, -shift)); + } + + // 2. compute quotient and remainder + var q = try Int.init(self.p.allocator.?); + defer q.deinit(); + + // unused + var r = try Int.init(self.p.allocator.?); + defer r.deinit(); + + try Int.divTrunc(&q, &r, a2, b2); + + var mantissa = extractLowBits(q, BitReprType); + var have_rem = r.len() > 0; + + // 3. q didn't fit in msize2 bits, redo division b2 << 1 + if (mantissa >> msize2 == 1) { + if (mantissa & 1 == 1) { + have_rem = true; + } + mantissa >>= 1; + exp += 1; + } + if (mantissa >> msize1 != 1) { + // NOTE: This can be hit if the limb size is small (u8/16). + @panic("unexpected bits in result"); + } + + // 4. Rounding + if (emin - msize <= exp and exp <= emin) { + // denormal + const shift1 = @intCast(math.Log2Int(BitReprType), emin - (exp - 1)); + const lost_bits = mantissa & ((@intCast(BitReprType, 1) << shift1) - 1); + have_rem = have_rem or lost_bits != 0; + mantissa >>= shift1; + exp = 2 - ebias; + } + + // round q using round-half-to-even + var exact = !have_rem; + if (mantissa & 1 != 0) { + exact = false; + if (have_rem or (mantissa & 2 != 0)) { + mantissa += 1; + if (mantissa >= 1 << msize2) { + // 11...1 => 100...0 + mantissa >>= 1; + exp += 1; + } + } + } + mantissa >>= 1; + + const f = math.scalbn(@intToFloat(T, mantissa), @intCast(i32, exp - msize1)); + if (math.isInf(f)) { + exact = false; + } + + return if (self.p.isPositive()) f else -f; + } + + /// Set a rational from an integer ratio. + pub fn setRatio(self: *Rational, p: var, q: var) !void { + try self.p.set(p); + try self.q.set(q); + + self.p.setSign(@boolToInt(self.p.isPositive()) ^ @boolToInt(self.q.isPositive()) == 0); + self.q.setSign(true); + + try self.reduce(); + + if (self.q.eqZero()) { + @panic("cannot set rational with denominator = 0"); + } + } + + /// Set a Rational directly from an Int. + pub fn copyInt(self: *Rational, a: Int) !void { + try self.p.copy(a); + try self.q.set(1); + } + + /// Set a Rational directly from a ratio of two Int's. + pub fn copyRatio(self: *Rational, a: Int, b: Int) !void { + try self.p.copy(a); + try self.q.copy(b); + + self.p.setSign(@boolToInt(self.p.isPositive()) ^ @boolToInt(self.q.isPositive()) == 0); + self.q.setSign(true); + + try self.reduce(); + } + + /// Make a Rational positive. + pub fn abs(r: *Rational) void { + r.p.abs(); + } + + /// Negate the sign of a Rational. + pub fn negate(r: *Rational) void { + r.p.negate(); + } + + /// Efficiently swap a Rational with another. This swaps the limb pointers and a full copy is not + /// performed. The address of the limbs field will not be the same after this function. + pub fn swap(r: *Rational, other: *Rational) void { + r.p.swap(&other.p); + r.q.swap(&other.q); + } + + /// Returns -1, 0, 1 if a < b, a == b or a > b respectively. + pub fn cmp(a: Rational, b: Rational) !i8 { + return cmpInternal(a, b, true); + } + + /// Returns -1, 0, 1 if |a| < |b|, |a| == |b| or |a| > |b| respectively. + pub fn cmpAbs(a: Rational, b: Rational) !i8 { + return cmpInternal(a, b, false); + } + + // p/q > x/y iff p*y > x*q + fn cmpInternal(a: Rational, b: Rational, is_abs: bool) !i8 { + // TODO: Would a div compare algorithm of sorts be viable and quicker? Can we avoid + // the memory allocations here? + var q = try Int.init(a.p.allocator.?); + defer q.deinit(); + + var p = try Int.init(b.p.allocator.?); + defer p.deinit(); + + try q.mul(a.p, b.q); + try p.mul(b.p, a.q); + + return if (is_abs) q.cmpAbs(p) else q.cmp(p); + } + + /// rma = a + b. + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. + pub fn add(rma: *Rational, a: Rational, b: Rational) !void { + var r = rma; + var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr; + + var sr: Rational = undefined; + if (aliased) { + sr = try Rational.init(rma.p.allocator.?); + r = &sr; + aliased = true; + } + defer if (aliased) { + rma.swap(r); + r.deinit(); + }; + + try r.p.mul(a.p, b.q); + try r.q.mul(b.p, a.q); + try r.p.add(r.p, r.q); + + try r.q.mul(a.q, b.q); + try r.reduce(); + } + + /// rma = a - b. + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. + pub fn sub(rma: *Rational, a: Rational, b: Rational) !void { + var r = rma; + var aliased = rma.p.limbs.ptr == a.p.limbs.ptr or rma.p.limbs.ptr == b.p.limbs.ptr; + + var sr: Rational = undefined; + if (aliased) { + sr = try Rational.init(rma.p.allocator.?); + r = &sr; + aliased = true; + } + defer if (aliased) { + rma.swap(r); + r.deinit(); + }; + + try r.p.mul(a.p, b.q); + try r.q.mul(b.p, a.q); + try r.p.sub(r.p, r.q); + + try r.q.mul(a.q, b.q); + try r.reduce(); + } + + /// rma = a * b. + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. + pub fn mul(r: *Rational, a: Rational, b: Rational) !void { + try r.p.mul(a.p, b.p); + try r.q.mul(a.q, b.q); + try r.reduce(); + } + + /// rma = a / b. + /// + /// rma, a and b may be aliases. However, it is more efficient if rma does not alias a or b. + /// + /// Returns an error if memory could not be allocated. + pub fn div(r: *Rational, a: Rational, b: Rational) !void { + if (b.p.eqZero()) { + @panic("division by zero"); + } + + try r.p.mul(a.p, b.q); + try r.q.mul(b.p, a.q); + try r.reduce(); + } + + /// Invert the numerator and denominator fields of a Rational. p/q => q/p. + pub fn invert(r: *Rational) void { + Int.swap(&r.p, &r.q); + } + + // reduce r/q such that gcd(r, q) = 1 + fn reduce(r: *Rational) !void { + var a = try Int.init(r.p.allocator.?); + defer a.deinit(); + + const sign = r.p.isPositive(); + r.p.abs(); + try gcd(&a, r.p, r.q); + r.p.setSign(sign); + + const one = Int.initFixed(([]Limb{1})[0..]); + if (a.cmp(one) != 0) { + var unused = try Int.init(r.p.allocator.?); + defer unused.deinit(); + + // TODO: divexact would be useful here + // TODO: don't copy r.q for div + try Int.divTrunc(&r.p, &unused, r.p, a); + try Int.divTrunc(&r.q, &unused, r.q, a); + } + } +}; + +const SignedDoubleLimb = @IntType(true, DoubleLimb.bit_count); + +fn gcd(rma: *Int, x: Int, y: Int) !void { + rma.assertWritable(); + var r = rma; + var aliased = rma.limbs.ptr == x.limbs.ptr or rma.limbs.ptr == y.limbs.ptr; + + var sr: Int = undefined; + if (aliased) { + sr = try Int.initCapacity(rma.allocator.?, math.max(x.len(), y.len())); + r = &sr; + aliased = true; + } + defer if (aliased) { + rma.swap(r); + r.deinit(); + }; + + try gcdLehmer(r, x, y); +} + +// Storage must live for the lifetime of the returned value +fn FixedIntFromSignedDoubleLimb(A: SignedDoubleLimb, storage: []Limb) Int { + std.debug.assert(storage.len >= 2); + + var A_is_positive = A >= 0; + const Au = @intCast(DoubleLimb, if (A < 0) -A else A); + storage[0] = @truncate(Limb, Au); + storage[1] = @truncate(Limb, Au >> Limb.bit_count); + var Ap = Int.initFixed(storage[0..2]); + Ap.setSign(A_is_positive); + return Ap; +} + +fn gcdLehmer(r: *Int, xa: Int, ya: Int) !void { + var x = try xa.clone(); + x.abs(); + defer x.deinit(); + + var y = try ya.clone(); + y.abs(); + defer y.deinit(); + + if (x.cmp(y) < 0) { + x.swap(&y); + } + + var T = try Int.init(r.allocator.?); + defer T.deinit(); + + while (y.len() > 1) { + debug.assert(x.isPositive() and y.isPositive()); + debug.assert(x.len() >= y.len()); + + var xh: SignedDoubleLimb = x.limbs[x.len() - 1]; + var yh: SignedDoubleLimb = if (x.len() > y.len()) 0 else y.limbs[x.len() - 1]; + + var A: SignedDoubleLimb = 1; + var B: SignedDoubleLimb = 0; + var C: SignedDoubleLimb = 0; + var D: SignedDoubleLimb = 1; + + while (yh + C != 0 and yh + D != 0) { + const q = @divFloor(xh + A, yh + C); + const qp = @divFloor(xh + B, yh + D); + if (q != qp) { + break; + } + + var t = A - q * C; + A = C; + C = t; + t = B - q * D; + B = D; + D = t; + + t = xh - q * yh; + xh = yh; + yh = t; + } + + if (B == 0) { + // T = x % y, r is unused + try Int.divTrunc(r, &T, x, y); + debug.assert(T.isPositive()); + + x.swap(&y); + y.swap(&T); + } else { + var storage: [8]Limb = undefined; + const Ap = FixedIntFromSignedDoubleLimb(A, storage[0..2]); + const Bp = FixedIntFromSignedDoubleLimb(B, storage[2..4]); + const Cp = FixedIntFromSignedDoubleLimb(C, storage[4..6]); + const Dp = FixedIntFromSignedDoubleLimb(D, storage[6..8]); + + // T = Ax + By + try r.mul(x, Ap); + try T.mul(y, Bp); + try T.add(r.*, T); + + // u = Cx + Dy, r as u + try x.mul(x, Cp); + try r.mul(y, Dp); + try r.add(x, r.*); + + x.swap(&T); + y.swap(r); + } + } + + // euclidean algorithm + debug.assert(x.cmp(y) >= 0); + + while (!y.eqZero()) { + try Int.divTrunc(&T, r, x, y); + x.swap(&y); + y.swap(r); + } + + r.swap(&x); +} + +var buffer: [64 * 8192]u8 = undefined; +var fixed = std.heap.FixedBufferAllocator.init(buffer[0..]); +var al = &fixed.allocator; + +test "big.rational gcd non-one small" { + var a = try Int.initSet(al, 17); + var b = try Int.initSet(al, 97); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u32)) == 1); +} + +test "big.rational gcd non-one small" { + var a = try Int.initSet(al, 4864); + var b = try Int.initSet(al, 3458); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u32)) == 38); +} + +test "big.rational gcd non-one large" { + var a = try Int.initSet(al, 0xffffffffffffffff); + var b = try Int.initSet(al, 0xffffffffffffffff7777); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u32)) == 4369); +} + +test "big.rational gcd large multi-limb result" { + var a = try Int.initSet(al, 0x12345678123456781234567812345678123456781234567812345678); + var b = try Int.initSet(al, 0x12345671234567123456712345671234567123456712345671234567); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u256)) == 0xf000000ff00000fff0000ffff000fffff00ffffff1); +} + +test "big.rational gcd one large" { + var a = try Int.initSet(al, 1897056385327307); + var b = try Int.initSet(al, 2251799813685248); + var r = try Int.init(al); + + try gcd(&r, a, b); + + testing.expect((try r.to(u64)) == 1); +} + +fn extractLowBits(a: Int, comptime T: type) T { + testing.expect(@typeId(T) == builtin.TypeId.Int); + + if (T.bit_count <= Limb.bit_count) { + return @truncate(T, a.limbs[0]); + } else { + var r: T = 0; + comptime var i: usize = 0; + + // Remainder is always 0 since if T.bit_count >= Limb.bit_count -> Limb | T and both + // are powers of two. + inline while (i < T.bit_count / Limb.bit_count) : (i += 1) { + r |= math.shl(T, a.limbs[i], i * Limb.bit_count); + } + + return r; + } +} + +test "big.rational extractLowBits" { + var a = try Int.initSet(al, 0x11112222333344441234567887654321); + + const a1 = extractLowBits(a, u8); + testing.expect(a1 == 0x21); + + const a2 = extractLowBits(a, u16); + testing.expect(a2 == 0x4321); + + const a3 = extractLowBits(a, u32); + testing.expect(a3 == 0x87654321); + + const a4 = extractLowBits(a, u64); + testing.expect(a4 == 0x1234567887654321); + + const a5 = extractLowBits(a, u128); + testing.expect(a5 == 0x11112222333344441234567887654321); +} + +test "big.rational set" { + var a = try Rational.init(al); + + try a.setInt(5); + testing.expect((try a.p.to(u32)) == 5); + testing.expect((try a.q.to(u32)) == 1); + + try a.setRatio(7, 3); + testing.expect((try a.p.to(u32)) == 7); + testing.expect((try a.q.to(u32)) == 3); + + try a.setRatio(9, 3); + testing.expect((try a.p.to(i32)) == 3); + testing.expect((try a.q.to(i32)) == 1); + + try a.setRatio(-9, 3); + testing.expect((try a.p.to(i32)) == -3); + testing.expect((try a.q.to(i32)) == 1); + + try a.setRatio(9, -3); + testing.expect((try a.p.to(i32)) == -3); + testing.expect((try a.q.to(i32)) == 1); + + try a.setRatio(-9, -3); + testing.expect((try a.p.to(i32)) == 3); + testing.expect((try a.q.to(i32)) == 1); +} + +test "big.rational setFloat" { + var a = try Rational.init(al); + + try a.setFloat(f64, 2.5); + testing.expect((try a.p.to(i32)) == 5); + testing.expect((try a.q.to(i32)) == 2); + + try a.setFloat(f32, -2.5); + testing.expect((try a.p.to(i32)) == -5); + testing.expect((try a.q.to(i32)) == 2); + + try a.setFloat(f32, 3.141593); + + // = 3.14159297943115234375 + testing.expect((try a.p.to(u32)) == 3294199); + testing.expect((try a.q.to(u32)) == 1048576); + + try a.setFloat(f64, 72.141593120712409172417410926841290461290467124); + + // = 72.1415931207124145885245525278151035308837890625 + testing.expect((try a.p.to(u128)) == 5076513310880537); + testing.expect((try a.q.to(u128)) == 70368744177664); +} + +test "big.rational setFloatString" { + var a = try Rational.init(al); + + try a.setFloatString("72.14159312071241458852455252781510353"); + + // = 72.1415931207124145885245525278151035308837890625 + testing.expect((try a.p.to(u128)) == 7214159312071241458852455252781510353); + testing.expect((try a.q.to(u128)) == 100000000000000000000000000000000000); +} + +test "big.rational toFloat" { + var a = try Rational.init(al); + + // = 3.14159297943115234375 + try a.setRatio(3294199, 1048576); + testing.expect((try a.toFloat(f64)) == 3.14159297943115234375); + + // = 72.1415931207124145885245525278151035308837890625 + try a.setRatio(5076513310880537, 70368744177664); + testing.expect((try a.toFloat(f64)) == 72.141593120712409172417410926841290461290467124); +} + +test "big.rational set/to Float round-trip" { + var a = try Rational.init(al); + var prng = std.rand.DefaultPrng.init(0x5EED); + var i: usize = 0; + while (i < 512) : (i += 1) { + const r = prng.random.float(f64); + try a.setFloat(f64, r); + testing.expect((try a.toFloat(f64)) == r); + } +} + +test "big.rational copy" { + var a = try Rational.init(al); + + const b = try Int.initSet(al, 5); + + try a.copyInt(b); + testing.expect((try a.p.to(u32)) == 5); + testing.expect((try a.q.to(u32)) == 1); + + const c = try Int.initSet(al, 7); + const d = try Int.initSet(al, 3); + + try a.copyRatio(c, d); + testing.expect((try a.p.to(u32)) == 7); + testing.expect((try a.q.to(u32)) == 3); + + const e = try Int.initSet(al, 9); + const f = try Int.initSet(al, 3); + + try a.copyRatio(e, f); + testing.expect((try a.p.to(u32)) == 3); + testing.expect((try a.q.to(u32)) == 1); +} + +test "big.rational negate" { + var a = try Rational.init(al); + + try a.setInt(-50); + testing.expect((try a.p.to(i32)) == -50); + testing.expect((try a.q.to(i32)) == 1); + + a.negate(); + testing.expect((try a.p.to(i32)) == 50); + testing.expect((try a.q.to(i32)) == 1); + + a.negate(); + testing.expect((try a.p.to(i32)) == -50); + testing.expect((try a.q.to(i32)) == 1); +} + +test "big.rational abs" { + var a = try Rational.init(al); + + try a.setInt(-50); + testing.expect((try a.p.to(i32)) == -50); + testing.expect((try a.q.to(i32)) == 1); + + a.abs(); + testing.expect((try a.p.to(i32)) == 50); + testing.expect((try a.q.to(i32)) == 1); + + a.abs(); + testing.expect((try a.p.to(i32)) == 50); + testing.expect((try a.q.to(i32)) == 1); +} + +test "big.rational swap" { + var a = try Rational.init(al); + var b = try Rational.init(al); + + try a.setRatio(50, 23); + try b.setRatio(17, 3); + + testing.expect((try a.p.to(u32)) == 50); + testing.expect((try a.q.to(u32)) == 23); + + testing.expect((try b.p.to(u32)) == 17); + testing.expect((try b.q.to(u32)) == 3); + + a.swap(&b); + + testing.expect((try a.p.to(u32)) == 17); + testing.expect((try a.q.to(u32)) == 3); + + testing.expect((try b.p.to(u32)) == 50); + testing.expect((try b.q.to(u32)) == 23); +} + +test "big.rational cmp" { + var a = try Rational.init(al); + var b = try Rational.init(al); + + try a.setRatio(500, 231); + try b.setRatio(18903, 8584); + testing.expect((try a.cmp(b)) < 0); + + try a.setRatio(890, 10); + try b.setRatio(89, 1); + testing.expect((try a.cmp(b)) == 0); +} + +test "big.rational add single-limb" { + var a = try Rational.init(al); + var b = try Rational.init(al); + + try a.setRatio(500, 231); + try b.setRatio(18903, 8584); + testing.expect((try a.cmp(b)) < 0); + + try a.setRatio(890, 10); + try b.setRatio(89, 1); + testing.expect((try a.cmp(b)) == 0); +} + +test "big.rational add" { + var a = try Rational.init(al); + var b = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + try b.setRatio(123097, 12441414); + try a.add(a, b); + + try r.setRatio(984786924199, 290395044174); + testing.expect((try a.cmp(r)) == 0); +} + +test "big.rational sub" { + var a = try Rational.init(al); + var b = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + try b.setRatio(123097, 12441414); + try a.sub(a, b); + + try r.setRatio(979040510045, 290395044174); + testing.expect((try a.cmp(r)) == 0); +} + +test "big.rational mul" { + var a = try Rational.init(al); + var b = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + try b.setRatio(123097, 12441414); + try a.mul(a, b); + + try r.setRatio(571481443, 17082061422); + testing.expect((try a.cmp(r)) == 0); +} + +test "big.rational div" { + var a = try Rational.init(al); + var b = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + try b.setRatio(123097, 12441414); + try a.div(a, b); + + try r.setRatio(75531824394, 221015929); + testing.expect((try a.cmp(r)) == 0); +} + +test "big.rational div" { + var a = try Rational.init(al); + var r = try Rational.init(al); + + try a.setRatio(78923, 23341); + a.invert(); + + try r.setRatio(23341, 78923); + testing.expect((try a.cmp(r)) == 0); + + try a.setRatio(-78923, 23341); + a.invert(); + + try r.setRatio(-23341, 78923); + testing.expect((try a.cmp(r)) == 0); +} diff --git a/std/math/cbrt.zig b/std/math/cbrt.zig index 9497255269..5241e31323 100644 --- a/std/math/cbrt.zig +++ b/std/math/cbrt.zig @@ -1,13 +1,19 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - cbrt(+-0) = +-0 -// - cbrt(+-inf) = +-inf -// - cbrt(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/cbrtf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/cbrt.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the cube root of x. +/// +/// Special Cases: +/// - cbrt(+-0) = +-0 +/// - cbrt(+-inf) = +-inf +/// - cbrt(nan) = nan pub fn cbrt(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/ceil.zig b/std/math/ceil.zig index da83e0ec59..5f86093a6d 100644 --- a/std/math/ceil.zig +++ b/std/math/ceil.zig @@ -1,14 +1,20 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - ceil(+-0) = +-0 -// - ceil(+-inf) = +-inf -// - ceil(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/ceil.c const builtin = @import("builtin"); const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the least integer value greater than of equal to x. +/// +/// Special Cases: +/// - ceil(+-0) = +-0 +/// - ceil(+-inf) = +-inf +/// - ceil(nan) = nan pub fn ceil(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/complex.zig b/std/math/complex.zig index cc0573b227..e5574f9cee 100644 --- a/std/math/complex.zig +++ b/std/math/complex.zig @@ -23,13 +23,18 @@ pub const sqrt = @import("complex/sqrt.zig").sqrt; pub const tanh = @import("complex/tanh.zig").tanh; pub const tan = @import("complex/tan.zig").tan; +/// A complex number consisting of a real an imaginary part. T must be a floating-point value. pub fn Complex(comptime T: type) type { return struct { const Self = @This(); + /// Real part. re: T, + + /// Imaginary part. im: T, + /// Create a new Complex number from the given real and imaginary parts. pub fn new(re: T, im: T) Self { return Self{ .re = re, @@ -37,6 +42,7 @@ pub fn Complex(comptime T: type) type { }; } + /// Returns the sum of two complex numbers. pub fn add(self: Self, other: Self) Self { return Self{ .re = self.re + other.re, @@ -44,6 +50,7 @@ pub fn Complex(comptime T: type) type { }; } + /// Returns the subtraction of two complex numbers. pub fn sub(self: Self, other: Self) Self { return Self{ .re = self.re - other.re, @@ -51,6 +58,7 @@ pub fn Complex(comptime T: type) type { }; } + /// Returns the product of two complex numbers. pub fn mul(self: Self, other: Self) Self { return Self{ .re = self.re * other.re - self.im * other.im, @@ -58,6 +66,7 @@ pub fn Complex(comptime T: type) type { }; } + /// Returns the quotient of two complex numbers. pub fn div(self: Self, other: Self) Self { const re_num = self.re * other.re + self.im * other.im; const im_num = self.im * other.re - self.re * other.im; @@ -69,6 +78,7 @@ pub fn Complex(comptime T: type) type { }; } + /// Returns the complex conjugate of a number. pub fn conjugate(self: Self) Self { return Self{ .re = self.re, @@ -76,6 +86,7 @@ pub fn Complex(comptime T: type) type { }; } + /// Returns the reciprocal of a complex number. pub fn reciprocal(self: Self) Self { const m = self.re * self.re + self.im * self.im; return Self{ @@ -84,6 +95,7 @@ pub fn Complex(comptime T: type) type { }; } + /// Returns the magnitude of a complex number. pub fn magnitude(self: Self) T { return math.sqrt(self.re * self.re + self.im * self.im); } diff --git a/std/math/complex/abs.zig b/std/math/complex/abs.zig index e1368d6ef6..8105f57218 100644 --- a/std/math/complex/abs.zig +++ b/std/math/complex/abs.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the absolute value (modulus) of z. pub fn abs(z: var) @typeOf(z.re) { const T = @typeOf(z.re); return math.hypot(T, z.re, z.im); diff --git a/std/math/complex/acos.zig b/std/math/complex/acos.zig index 8aed26a71b..f3526cc9ff 100644 --- a/std/math/complex/acos.zig +++ b/std/math/complex/acos.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the arc-cosine of z. pub fn acos(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); const q = cmath.asin(z); diff --git a/std/math/complex/acosh.zig b/std/math/complex/acosh.zig index e72bf431fe..6f0fd2e36c 100644 --- a/std/math/complex/acosh.zig +++ b/std/math/complex/acosh.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the hyperbolic arc-cosine of z. pub fn acosh(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); const q = cmath.acos(z); diff --git a/std/math/complex/arg.zig b/std/math/complex/arg.zig index 0a2441d1fd..d0c9588b8d 100644 --- a/std/math/complex/arg.zig +++ b/std/math/complex/arg.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the angular component (in radians) of z. pub fn arg(z: var) @typeOf(z.re) { const T = @typeOf(z.re); return math.atan2(T, z.im, z.re); diff --git a/std/math/complex/asin.zig b/std/math/complex/asin.zig index 6be775d748..76f94a286c 100644 --- a/std/math/complex/asin.zig +++ b/std/math/complex/asin.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +// Returns the arc-sine of z. pub fn asin(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); const x = z.re; diff --git a/std/math/complex/asinh.zig b/std/math/complex/asinh.zig index 8e09036750..da065aad01 100644 --- a/std/math/complex/asinh.zig +++ b/std/math/complex/asinh.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the hyperbolic arc-sine of z. pub fn asinh(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); const q = Complex(T).new(-z.im, z.re); diff --git a/std/math/complex/atan.zig b/std/math/complex/atan.zig index 6b83adbd97..89bc8dfaf0 100644 --- a/std/math/complex/atan.zig +++ b/std/math/complex/atan.zig @@ -1,9 +1,16 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/complex/catanf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/catan.c + const std = @import("../../std.zig"); const testing = std.testing; const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the arc-tangent of z. pub fn atan(z: var) @typeOf(z) { const T = @typeOf(z.re); return switch (T) { diff --git a/std/math/complex/atanh.zig b/std/math/complex/atanh.zig index 8edfb6e78e..225e7c61de 100644 --- a/std/math/complex/atanh.zig +++ b/std/math/complex/atanh.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the hyperbolic arc-tangent of z. pub fn atanh(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); const q = Complex(T).new(-z.im, z.re); diff --git a/std/math/complex/conj.zig b/std/math/complex/conj.zig index 7a42d365ea..bd71ca3c06 100644 --- a/std/math/complex/conj.zig +++ b/std/math/complex/conj.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the complex conjugate of z. pub fn conj(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); return Complex(T).new(z.re, -z.im); diff --git a/std/math/complex/cos.zig b/std/math/complex/cos.zig index 71f9603c75..332009ffe5 100644 --- a/std/math/complex/cos.zig +++ b/std/math/complex/cos.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the cosine of z. pub fn cos(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); const p = Complex(T).new(-z.im, z.re); diff --git a/std/math/complex/cosh.zig b/std/math/complex/cosh.zig index 9806e41170..be7bfde963 100644 --- a/std/math/complex/cosh.zig +++ b/std/math/complex/cosh.zig @@ -1,3 +1,9 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/complex/ccoshf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/ccosh.c + const std = @import("../../std.zig"); const testing = std.testing; const math = std.math; @@ -6,6 +12,7 @@ const Complex = cmath.Complex; const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; +/// Returns the hyperbolic arc-cosine of z. pub fn cosh(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); return switch (T) { diff --git a/std/math/complex/exp.zig b/std/math/complex/exp.zig index c74ac2fc08..9b686bebc3 100644 --- a/std/math/complex/exp.zig +++ b/std/math/complex/exp.zig @@ -1,3 +1,9 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/complex/cexpf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/cexp.c + const std = @import("../../std.zig"); const testing = std.testing; const math = std.math; @@ -6,6 +12,7 @@ const Complex = cmath.Complex; const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; +/// Returns e raised to the power of z (e^z). pub fn exp(z: var) @typeOf(z) { const T = @typeOf(z.re); diff --git a/std/math/complex/ldexp.zig b/std/math/complex/ldexp.zig index 6b4306bf77..d6f810793f 100644 --- a/std/math/complex/ldexp.zig +++ b/std/math/complex/ldexp.zig @@ -1,9 +1,16 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/complex/__cexpf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/__cexp.c + const std = @import("../../std.zig"); const debug = std.debug; const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns exp(z) scaled to avoid overflow. pub fn ldexp_cexp(z: var, expt: i32) @typeOf(z) { const T = @typeOf(z.re); diff --git a/std/math/complex/log.zig b/std/math/complex/log.zig index 2b43a6970f..762b4fde9a 100644 --- a/std/math/complex/log.zig +++ b/std/math/complex/log.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the natural logarithm of z. pub fn log(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); const r = cmath.abs(z); diff --git a/std/math/complex/pow.zig b/std/math/complex/pow.zig index 9174bb3626..a2480453fc 100644 --- a/std/math/complex/pow.zig +++ b/std/math/complex/pow.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns z raised to the complex power of c. pub fn pow(comptime T: type, z: T, c: T) T { const p = cmath.log(z); const q = c.mul(p); diff --git a/std/math/complex/proj.zig b/std/math/complex/proj.zig index aadcff6ff6..c8f2d9fc6d 100644 --- a/std/math/complex/proj.zig +++ b/std/math/complex/proj.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the projection of z onto the riemann sphere. pub fn proj(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); diff --git a/std/math/complex/sin.zig b/std/math/complex/sin.zig index 049631f31e..9ddc3a7a80 100644 --- a/std/math/complex/sin.zig +++ b/std/math/complex/sin.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the sine of z. pub fn sin(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); const p = Complex(T).new(-z.im, z.re); diff --git a/std/math/complex/sinh.zig b/std/math/complex/sinh.zig index 0b656e5354..6286d8447f 100644 --- a/std/math/complex/sinh.zig +++ b/std/math/complex/sinh.zig @@ -1,3 +1,9 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/complex/csinhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/csinh.c + const std = @import("../../std.zig"); const testing = std.testing; const math = std.math; @@ -6,6 +12,7 @@ const Complex = cmath.Complex; const ldexp_cexp = @import("ldexp.zig").ldexp_cexp; +/// Returns the hyperbolic sine of z. pub fn sinh(z: var) @typeOf(z) { const T = @typeOf(z.re); return switch (T) { diff --git a/std/math/complex/sqrt.zig b/std/math/complex/sqrt.zig index e935d0b238..36f4c28e29 100644 --- a/std/math/complex/sqrt.zig +++ b/std/math/complex/sqrt.zig @@ -1,9 +1,17 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/complex/csqrtf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/csqrt.c + const std = @import("../../std.zig"); const testing = std.testing; const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the square root of z. The real and imaginary parts of the result have the same sign +/// as the imaginary part of z. pub fn sqrt(z: var) @typeOf(z) { const T = @typeOf(z.re); diff --git a/std/math/complex/tan.zig b/std/math/complex/tan.zig index 45e2873eb6..398b8295ca 100644 --- a/std/math/complex/tan.zig +++ b/std/math/complex/tan.zig @@ -4,6 +4,7 @@ const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the tanget of z. pub fn tan(z: var) Complex(@typeOf(z.re)) { const T = @typeOf(z.re); const q = Complex(T).new(-z.im, z.re); diff --git a/std/math/complex/tanh.zig b/std/math/complex/tanh.zig index de905ee3f6..5c14ec66f2 100644 --- a/std/math/complex/tanh.zig +++ b/std/math/complex/tanh.zig @@ -1,9 +1,16 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/complex/ctanhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/complex/ctanh.c + const std = @import("../../std.zig"); const testing = std.testing; const math = std.math; const cmath = math.complex; const Complex = cmath.Complex; +/// Returns the hyperbolic tangent of z. pub fn tanh(z: var) @typeOf(z) { const T = @typeOf(z.re); return switch (T) { diff --git a/std/math/copysign.zig b/std/math/copysign.zig index ff8140b3ab..e4d90c395e 100644 --- a/std/math/copysign.zig +++ b/std/math/copysign.zig @@ -1,8 +1,15 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/math/copysignf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/copysign.c + const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns a value with the magnitude of x and the sign of y. pub fn copysign(comptime T: type, x: T, y: T) T { return switch (T) { f16 => copysign16(x, y), diff --git a/std/math/cos.zig b/std/math/cos.zig index 9479482894..dc4aff59d6 100644 --- a/std/math/cos.zig +++ b/std/math/cos.zig @@ -1,18 +1,23 @@ -// Special Cases: +// Ported from go, which is licensed under a BSD-3 license. +// https://golang.org/LICENSE // -// - cos(+-inf) = nan -// - cos(nan) = nan +// https://golang.org/src/math/sin.go const builtin = @import("builtin"); const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the cosine of the radian value x. +/// +/// Special Cases: +/// - cos(+-inf) = nan +/// - cos(nan) = nan pub fn cos(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { - f32 => cos32(x), - f64 => cos64(x), + f32 => cos_(f32, x), + f64 => cos_(f64, x), else => @compileError("cos not implemented for " ++ @typeName(T)), }; } @@ -33,78 +38,24 @@ const C3 = 2.48015872888517045348E-5; const C4 = -1.38888888888730564116E-3; const C5 = 4.16666666666665929218E-2; -// NOTE: This is taken from the go stdlib. The musl implementation is much more complex. -// -// This may have slight differences on some edge cases and may need to replaced if so. -fn cos32(x_: f32) f32 { - const pi4a = 7.85398125648498535156e-1; - const pi4b = 3.77489470793079817668E-8; - const pi4c = 2.69515142907905952645E-15; - const m4pi = 1.273239544735162542821171882678754627704620361328125; - - var x = x_; - if (math.isNan(x) or math.isInf(x)) { - return math.nan(f32); - } - - var sign = false; - if (x < 0) { - x = -x; - } - - var y = math.floor(x * m4pi); - var j = @floatToInt(i64, y); +const pi4a = 7.85398125648498535156e-1; +const pi4b = 3.77489470793079817668E-8; +const pi4c = 2.69515142907905952645E-15; +const m4pi = 1.273239544735162542821171882678754627704620361328125; - if (j & 1 == 1) { - j += 1; - y += 1; - } - - j &= 7; - if (j > 3) { - j -= 4; - sign = !sign; - } - if (j > 1) { - sign = !sign; - } - - const z = ((x - y * pi4a) - y * pi4b) - y * pi4c; - const w = z * z; - - const r = r: { - if (j == 1 or j == 2) { - break :r z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0))))); - } else { - break :r 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0))))); - } - }; - - if (sign) { - return -r; - } else { - return r; - } -} - -fn cos64(x_: f64) f64 { - const pi4a = 7.85398125648498535156e-1; - const pi4b = 3.77489470793079817668E-8; - const pi4c = 2.69515142907905952645E-15; - const m4pi = 1.273239544735162542821171882678754627704620361328125; +fn cos_(comptime T: type, x_: T) T { + const I = @IntType(true, T.bit_count); var x = x_; if (math.isNan(x) or math.isInf(x)) { - return math.nan(f64); + return math.nan(T); } var sign = false; - if (x < 0) { - x = -x; - } + x = math.fabs(x); var y = math.floor(x * m4pi); - var j = @floatToInt(i64, y); + var j = @floatToInt(I, y); if (j & 1 == 1) { j += 1; @@ -123,56 +74,51 @@ fn cos64(x_: f64) f64 { const z = ((x - y * pi4a) - y * pi4b) - y * pi4c; const w = z * z; - const r = r: { - if (j == 1 or j == 2) { - break :r z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0))))); - } else { - break :r 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0))))); - } - }; + const r = if (j == 1 or j == 2) + z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0))))) + else + 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0))))); - if (sign) { - return -r; - } else { - return r; - } + return if (sign) -r else r; } test "math.cos" { - expect(cos(f32(0.0)) == cos32(0.0)); - expect(cos(f64(0.0)) == cos64(0.0)); + expect(cos(f32(0.0)) == cos_(f32, 0.0)); + expect(cos(f64(0.0)) == cos_(f64, 0.0)); } test "math.cos32" { const epsilon = 0.000001; - expect(math.approxEq(f32, cos32(0.0), 1.0, epsilon)); - expect(math.approxEq(f32, cos32(0.2), 0.980067, epsilon)); - expect(math.approxEq(f32, cos32(0.8923), 0.627623, epsilon)); - expect(math.approxEq(f32, cos32(1.5), 0.070737, epsilon)); - expect(math.approxEq(f32, cos32(37.45), 0.969132, epsilon)); - expect(math.approxEq(f32, cos32(89.123), 0.400798, epsilon)); + expect(math.approxEq(f32, cos_(f32, 0.0), 1.0, epsilon)); + expect(math.approxEq(f32, cos_(f32, 0.2), 0.980067, epsilon)); + expect(math.approxEq(f32, cos_(f32, 0.8923), 0.627623, epsilon)); + expect(math.approxEq(f32, cos_(f32, 1.5), 0.070737, epsilon)); + expect(math.approxEq(f32, cos_(f32, -1.5), 0.070737, epsilon)); + expect(math.approxEq(f32, cos_(f32, 37.45), 0.969132, epsilon)); + expect(math.approxEq(f32, cos_(f32, 89.123), 0.400798, epsilon)); } test "math.cos64" { const epsilon = 0.000001; - expect(math.approxEq(f64, cos64(0.0), 1.0, epsilon)); - expect(math.approxEq(f64, cos64(0.2), 0.980067, epsilon)); - expect(math.approxEq(f64, cos64(0.8923), 0.627623, epsilon)); - expect(math.approxEq(f64, cos64(1.5), 0.070737, epsilon)); - expect(math.approxEq(f64, cos64(37.45), 0.969132, epsilon)); - expect(math.approxEq(f64, cos64(89.123), 0.40080, epsilon)); + expect(math.approxEq(f64, cos_(f64, 0.0), 1.0, epsilon)); + expect(math.approxEq(f64, cos_(f64, 0.2), 0.980067, epsilon)); + expect(math.approxEq(f64, cos_(f64, 0.8923), 0.627623, epsilon)); + expect(math.approxEq(f64, cos_(f64, 1.5), 0.070737, epsilon)); + expect(math.approxEq(f64, cos_(f64, -1.5), 0.070737, epsilon)); + expect(math.approxEq(f64, cos_(f64, 37.45), 0.969132, epsilon)); + expect(math.approxEq(f64, cos_(f64, 89.123), 0.40080, epsilon)); } test "math.cos32.special" { - expect(math.isNan(cos32(math.inf(f32)))); - expect(math.isNan(cos32(-math.inf(f32)))); - expect(math.isNan(cos32(math.nan(f32)))); + expect(math.isNan(cos_(f32, math.inf(f32)))); + expect(math.isNan(cos_(f32, -math.inf(f32)))); + expect(math.isNan(cos_(f32, math.nan(f32)))); } test "math.cos64.special" { - expect(math.isNan(cos64(math.inf(f64)))); - expect(math.isNan(cos64(-math.inf(f64)))); - expect(math.isNan(cos64(math.nan(f64)))); + expect(math.isNan(cos_(f64, math.inf(f64)))); + expect(math.isNan(cos_(f64, -math.inf(f64)))); + expect(math.isNan(cos_(f64, math.nan(f64)))); } diff --git a/std/math/cosh.zig b/std/math/cosh.zig index eb3082e5fa..75c5c15ec1 100644 --- a/std/math/cosh.zig +++ b/std/math/cosh.zig @@ -1,8 +1,8 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - cosh(+-0) = 1 -// - cosh(+-inf) = +inf -// - cosh(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/coshf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/cosh.c const builtin = @import("builtin"); const std = @import("../std.zig"); @@ -11,6 +11,12 @@ const expo2 = @import("expo2.zig").expo2; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns the hyperbolic cosine of x. +/// +/// Special Cases: +/// - cosh(+-0) = 1 +/// - cosh(+-inf) = +inf +/// - cosh(nan) = nan pub fn cosh(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/exp.zig b/std/math/exp.zig index aabccffd0b..ad058646a4 100644 --- a/std/math/exp.zig +++ b/std/math/exp.zig @@ -1,13 +1,19 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - exp(+inf) = +inf -// - exp(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/expf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/exp.c const std = @import("../std.zig"); const math = std.math; const assert = std.debug.assert; const builtin = @import("builtin"); +/// Returns e raised to the power of x (e^x). +/// +/// Special Cases: +/// - exp(+inf) = +inf +/// - exp(nan) = nan pub fn exp(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/exp2.zig b/std/math/exp2.zig index 392d45bf68..07a39576b1 100644 --- a/std/math/exp2.zig +++ b/std/math/exp2.zig @@ -1,12 +1,18 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - exp2(+inf) = +inf -// - exp2(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/exp2f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/exp2.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns 2 raised to the power of x (2^x). +/// +/// Special Cases: +/// - exp2(+inf) = +inf +/// - exp2(nan) = nan pub fn exp2(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/expm1.zig b/std/math/expm1.zig index b83cc3e82e..5e347f86f6 100644 --- a/std/math/expm1.zig +++ b/std/math/expm1.zig @@ -1,14 +1,23 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - expm1(+inf) = +inf -// - expm1(-inf) = -1 -// - expm1(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/expmf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/expm.c + +// TODO: Updated recently. const builtin = @import("builtin"); const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns e raised to the power of x, minus 1 (e^x - 1). This is more accurate than exp(e, x) - 1 +/// when x is near 0. +/// +/// Special Cases: +/// - expm1(+inf) = +inf +/// - expm1(-inf) = -1 +/// - expm1(nan) = nan pub fn expm1(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/expo2.zig b/std/math/expo2.zig index 57e2bf34c9..c00098a5a7 100644 --- a/std/math/expo2.zig +++ b/std/math/expo2.zig @@ -1,5 +1,12 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/math/__expo2f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/__expo2.c + const math = @import("../math.zig"); +/// Returns exp(x) / 2 for x >= log(maxFloat(T)). pub fn expo2(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/fabs.zig b/std/math/fabs.zig index e30f788ae7..6469f38835 100644 --- a/std/math/fabs.zig +++ b/std/math/fabs.zig @@ -1,13 +1,19 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - fabs(+-inf) = +inf -// - fabs(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/fabsf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/fabs.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns the absolute value of x. +/// +/// Special Cases: +/// - fabs(+-inf) = +inf +/// - fabs(nan) = nan pub fn fabs(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/floor.zig b/std/math/floor.zig index ab45a8fee7..e5ff2b1fc1 100644 --- a/std/math/floor.zig +++ b/std/math/floor.zig @@ -1,14 +1,20 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - floor(+-0) = +-0 -// - floor(+-inf) = +-inf -// - floor(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/floorf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/floor.c const builtin = @import("builtin"); const expect = std.testing.expect; const std = @import("../std.zig"); const math = std.math; +/// Returns the greatest integer value less than or equal to x. +/// +/// Special Cases: +/// - floor(+-0) = +-0 +/// - floor(+-inf) = +-inf +/// - floor(nan) = nan pub fn floor(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/fma.zig b/std/math/fma.zig index a317bc96de..19c306fa2a 100644 --- a/std/math/fma.zig +++ b/std/math/fma.zig @@ -1,7 +1,14 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/math/fmaf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/fma.c + const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns x * y + z with a single rounding error. pub fn fma(comptime T: type, x: T, y: T, z: T) T { return switch (T) { f32 => fma32(x, y, z), @@ -16,7 +23,7 @@ fn fma32(x: f32, y: f32, z: f32) f32 { const u = @bitCast(u64, xy_z); const e = (u >> 52) & 0x7FF; - if ((u & 0x1FFFFFFF) != 0x10000000 or e == 0x7FF or xy_z - xy == z) { + if ((u & 0x1FFFFFFF) != 0x10000000 or e == 0x7FF or (xy_z - xy == z and xy_z - z == xy)) { return @floatCast(f32, xy_z); } else { // TODO: Handle inexact case with double-rounding @@ -24,6 +31,7 @@ fn fma32(x: f32, y: f32, z: f32) f32 { } } +// NOTE: Upstream fma.c has been rewritten completely to raise fp exceptions more accurately. fn fma64(x: f64, y: f64, z: f64) f64 { if (!math.isFinite(x) or !math.isFinite(y)) { return x * y + z; diff --git a/std/math/frexp.zig b/std/math/frexp.zig index 35eec315d9..2759cd6492 100644 --- a/std/math/frexp.zig +++ b/std/math/frexp.zig @@ -1,8 +1,8 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - frexp(+-0) = +-0, 0 -// - frexp(+-inf) = +-inf, 0 -// - frexp(nan) = nan, undefined +// https://git.musl-libc.org/cgit/musl/tree/src/math/frexpf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/frexp.c const std = @import("../std.zig"); const math = std.math; @@ -17,6 +17,13 @@ fn frexp_result(comptime T: type) type { pub const frexp32_result = frexp_result(f32); pub const frexp64_result = frexp_result(f64); +/// Breaks x into a normalized fraction and an integral power of two. +/// f == frac * 2^exp, with |frac| in the interval [0.5, 1). +/// +/// Special Cases: +/// - frexp(+-0) = +-0, 0 +/// - frexp(+-inf) = +-inf, 0 +/// - frexp(nan) = nan, undefined pub fn frexp(x: var) frexp_result(@typeOf(x)) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/hypot.zig b/std/math/hypot.zig index fddbe7c068..c15da1495e 100644 --- a/std/math/hypot.zig +++ b/std/math/hypot.zig @@ -1,15 +1,21 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - hypot(+-inf, y) = +inf -// - hypot(x, +-inf) = +inf -// - hypot(nan, y) = nan -// - hypot(x, nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/hypotf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/hypot.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns sqrt(x * x + y * y), avoiding unncessary overflow and underflow. +/// +/// Special Cases: +/// - hypot(+-inf, y) = +inf +/// - hypot(x, +-inf) = +inf +/// - hypot(nan, y) = nan +/// - hypot(x, nan) = nan pub fn hypot(comptime T: type, x: T, y: T) T { return switch (T) { f32 => hypot32(x, y), diff --git a/std/math/ilogb.zig b/std/math/ilogb.zig index 2dfd42a62c..fe4158a6dd 100644 --- a/std/math/ilogb.zig +++ b/std/math/ilogb.zig @@ -1,8 +1,8 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - ilogb(+-inf) = maxInt(i32) -// - ilogb(0) = maxInt(i32) -// - ilogb(nan) = maxInt(i32) +// https://git.musl-libc.org/cgit/musl/tree/src/math/ilogbf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/ilogb.c const std = @import("../std.zig"); const math = std.math; @@ -10,6 +10,12 @@ const expect = std.testing.expect; const maxInt = std.math.maxInt; const minInt = std.math.minInt; +/// Returns the binary exponent of x as an integer. +/// +/// Special Cases: +/// - ilogb(+-inf) = maxInt(i32) +/// - ilogb(0) = maxInt(i32) +/// - ilogb(nan) = maxInt(i32) pub fn ilogb(x: var) i32 { const T = @typeOf(x); return switch (T) { diff --git a/std/math/inf.zig b/std/math/inf.zig index e1bfbb197a..86ff245533 100644 --- a/std/math/inf.zig +++ b/std/math/inf.zig @@ -1,6 +1,7 @@ const std = @import("../std.zig"); const math = std.math; +/// Returns value inf for the type T. pub fn inf(comptime T: type) T { return switch (T) { f16 => math.inf_f16, diff --git a/std/math/isfinite.zig b/std/math/isfinite.zig index ee8a5ff590..99eba668f9 100644 --- a/std/math/isfinite.zig +++ b/std/math/isfinite.zig @@ -3,6 +3,7 @@ const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns whether x is a finite value. pub fn isFinite(x: var) bool { const T = @typeOf(x); switch (T) { diff --git a/std/math/isinf.zig b/std/math/isinf.zig index 1b1759bd36..37934f4cf4 100644 --- a/std/math/isinf.zig +++ b/std/math/isinf.zig @@ -3,6 +3,7 @@ const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns whether x is an infinity, ignoring sign. pub fn isInf(x: var) bool { const T = @typeOf(x); switch (T) { @@ -28,6 +29,7 @@ pub fn isInf(x: var) bool { } } +/// Returns whether x is an infinity with a positive sign. pub fn isPositiveInf(x: var) bool { const T = @typeOf(x); switch (T) { @@ -49,6 +51,7 @@ pub fn isPositiveInf(x: var) bool { } } +/// Returns whether x is an infinity with a negative sign. pub fn isNegativeInf(x: var) bool { const T = @typeOf(x); switch (T) { diff --git a/std/math/isnan.zig b/std/math/isnan.zig index 9e541bf0a2..cf8cd2e1c2 100644 --- a/std/math/isnan.zig +++ b/std/math/isnan.zig @@ -3,13 +3,15 @@ const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns whether x is a nan. pub fn isNan(x: var) bool { return x != x; } -/// Note: A signalling nan is identical to a standard nan right now but may have a different bit -/// representation in the future when required. +/// Returns whether x is a signalling nan. pub fn isSignalNan(x: var) bool { + // Note: A signalling nan is identical to a standard nan right now but may have a different bit + // representation in the future when required. return isNan(x); } diff --git a/std/math/isnormal.zig b/std/math/isnormal.zig index cddcada1d3..f8611ef805 100644 --- a/std/math/isnormal.zig +++ b/std/math/isnormal.zig @@ -3,6 +3,7 @@ const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +// Returns whether x has a normalized representation (i.e. integer part of mantissa is 1). pub fn isNormal(x: var) bool { const T = @typeOf(x); switch (T) { diff --git a/std/math/ln.zig b/std/math/ln.zig index 82b212f00f..c5d4c9ff25 100644 --- a/std/math/ln.zig +++ b/std/math/ln.zig @@ -1,9 +1,8 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - ln(+inf) = +inf -// - ln(0) = -inf -// - ln(x) = nan if x < 0 -// - ln(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/lnf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/ln.c const std = @import("../std.zig"); const math = std.math; @@ -11,6 +10,13 @@ const expect = std.testing.expect; const builtin = @import("builtin"); const TypeId = builtin.TypeId; +/// Returns the natural logarithm of x. +/// +/// Special Cases: +/// - ln(+inf) = +inf +/// - ln(0) = -inf +/// - ln(x) = nan if x < 0 +/// - ln(nan) = nan pub fn ln(x: var) @typeOf(x) { const T = @typeOf(x); switch (@typeId(T)) { diff --git a/std/math/log.zig b/std/math/log.zig index 100dc2fb7f..77f3639fd2 100644 --- a/std/math/log.zig +++ b/std/math/log.zig @@ -1,9 +1,16 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/math/logf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/log.c + const std = @import("../std.zig"); const math = std.math; const builtin = @import("builtin"); const TypeId = builtin.TypeId; const expect = std.testing.expect; +/// Returns the logarithm of x for the provided base. pub fn log(comptime T: type, base: T, x: T) T { if (base == 2) { return math.log2(x); diff --git a/std/math/log10.zig b/std/math/log10.zig index 7311778ddd..9b0bc3ac52 100644 --- a/std/math/log10.zig +++ b/std/math/log10.zig @@ -1,9 +1,8 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - log10(+inf) = +inf -// - log10(0) = -inf -// - log10(x) = nan if x < 0 -// - log10(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/log10f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/log10.c const std = @import("../std.zig"); const math = std.math; @@ -12,6 +11,13 @@ const builtin = @import("builtin"); const TypeId = builtin.TypeId; const maxInt = std.math.maxInt; +/// Returns the base-10 logarithm of x. +/// +/// Special Cases: +/// - log10(+inf) = +inf +/// - log10(0) = -inf +/// - log10(x) = nan if x < 0 +/// - log10(nan) = nan pub fn log10(x: var) @typeOf(x) { const T = @typeOf(x); switch (@typeId(T)) { diff --git a/std/math/log1p.zig b/std/math/log1p.zig index c9be1132be..bae6deb536 100644 --- a/std/math/log1p.zig +++ b/std/math/log1p.zig @@ -1,16 +1,22 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - log1p(+inf) = +inf -// - log1p(+-0) = +-0 -// - log1p(-1) = -inf -// - log1p(x) = nan if x < -1 -// - log1p(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/log1pf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/log1p.c const builtin = @import("builtin"); const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the natural logarithm of 1 + x with greater accuracy when x is near zero. +/// +/// Special Cases: +/// - log1p(+inf) = +inf +/// - log1p(+-0) = +-0 +/// - log1p(-1) = -inf +/// - log1p(x) = nan if x < -1 +/// - log1p(nan) = nan pub fn log1p(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/log2.zig b/std/math/log2.zig index e2dbf4105a..88450a7ffd 100644 --- a/std/math/log2.zig +++ b/std/math/log2.zig @@ -1,9 +1,8 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - log2(+inf) = +inf -// - log2(0) = -inf -// - log2(x) = nan if x < 0 -// - log2(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/log2f.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/log2.c const std = @import("../std.zig"); const math = std.math; @@ -12,6 +11,13 @@ const builtin = @import("builtin"); const TypeId = builtin.TypeId; const maxInt = std.math.maxInt; +/// Returns the base-2 logarithm of x. +/// +/// Special Cases: +/// - log2(+inf) = +inf +/// - log2(0) = -inf +/// - log2(x) = nan if x < 0 +/// - log2(nan) = nan pub fn log2(x: var) @typeOf(x) { const T = @typeOf(x); switch (@typeId(T)) { diff --git a/std/math/modf.zig b/std/math/modf.zig index e5a4964e63..92194d4c75 100644 --- a/std/math/modf.zig +++ b/std/math/modf.zig @@ -1,7 +1,8 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - modf(+-inf) = +-inf, nan -// - modf(nan) = nan, nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/modff.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/modf.c const std = @import("../std.zig"); const math = std.math; @@ -17,6 +18,12 @@ fn modf_result(comptime T: type) type { pub const modf32_result = modf_result(f32); pub const modf64_result = modf_result(f64); +/// Returns the integer and fractional floating-point numbers that sum to x. The sign of each +/// result is the same as the sign of x. +/// +/// Special Cases: +/// - modf(+-inf) = +-inf, nan +/// - modf(nan) = nan, nan pub fn modf(x: var) modf_result(@typeOf(x)) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/nan.zig b/std/math/nan.zig index 1a11a141d5..5a01a5b3bd 100644 --- a/std/math/nan.zig +++ b/std/math/nan.zig @@ -1,5 +1,6 @@ const math = @import("../math.zig"); +/// Returns the nan representation for type T. pub fn nan(comptime T: type) T { return switch (T) { f16 => math.nan_f16, @@ -10,9 +11,10 @@ pub fn nan(comptime T: type) T { }; } -// Note: A signalling nan is identical to a standard right now by may have a different bit -// representation in the future when required. +/// Returns the signalling nan representation for type T. pub fn snan(comptime T: type) T { + // Note: A signalling nan is identical to a standard right now by may have a different bit + // representation in the future when required. return switch (T) { f16 => @bitCast(f16, math.nan_u16), f32 => @bitCast(f32, math.nan_u32), diff --git a/std/math/pow.zig b/std/math/pow.zig index 81bb2d95d5..18c9f80634 100644 --- a/std/math/pow.zig +++ b/std/math/pow.zig @@ -1,32 +1,36 @@ -// Special Cases: +// Ported from go, which is licensed under a BSD-3 license. +// https://golang.org/LICENSE // -// pow(x, +-0) = 1 for any x -// pow(1, y) = 1 for any y -// pow(x, 1) = x for any x -// pow(nan, y) = nan -// pow(x, nan) = nan -// pow(+-0, y) = +-inf for y an odd integer < 0 -// pow(+-0, -inf) = +inf -// pow(+-0, +inf) = +0 -// pow(+-0, y) = +inf for finite y < 0 and not an odd integer -// pow(+-0, y) = +-0 for y an odd integer > 0 -// pow(+-0, y) = +0 for finite y > 0 and not an odd integer -// pow(-1, +-inf) = 1 -// pow(x, +inf) = +inf for |x| > 1 -// pow(x, -inf) = +0 for |x| > 1 -// pow(x, +inf) = +0 for |x| < 1 -// pow(x, -inf) = +inf for |x| < 1 -// pow(+inf, y) = +inf for y > 0 -// pow(+inf, y) = +0 for y < 0 -// pow(-inf, y) = pow(-0, -y) -// pow(x, y) = nan for finite x < 0 and finite non-integer y +// https://golang.org/src/math/pow.go const builtin = @import("builtin"); const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; -// This implementation is taken from the go stlib, musl is a bit more complex. +/// Returns x raised to the power of y (x^y). +/// +/// Special Cases: +/// - pow(x, +-0) = 1 for any x +/// - pow(1, y) = 1 for any y +/// - pow(x, 1) = x for any x +/// - pow(nan, y) = nan +/// - pow(x, nan) = nan +/// - pow(+-0, y) = +-inf for y an odd integer < 0 +/// - pow(+-0, -inf) = +inf +/// - pow(+-0, +inf) = +0 +/// - pow(+-0, y) = +inf for finite y < 0 and not an odd integer +/// - pow(+-0, y) = +-0 for y an odd integer > 0 +/// - pow(+-0, y) = +0 for finite y > 0 and not an odd integer +/// - pow(-1, +-inf) = 1 +/// - pow(x, +inf) = +inf for |x| > 1 +/// - pow(x, -inf) = +0 for |x| > 1 +/// - pow(x, +inf) = +0 for |x| < 1 +/// - pow(x, -inf) = +inf for |x| < 1 +/// - pow(+inf, y) = +inf for y > 0 +/// - pow(+inf, y) = +0 for y < 0 +/// - pow(-inf, y) = pow(-0, -y) +/// - pow(x, y) = nan for finite x < 0 and finite non-integer y pub fn pow(comptime T: type, x: T, y: T) T { if (@typeInfo(T) == builtin.TypeId.Int) { return math.powi(T, x, y) catch unreachable; @@ -53,15 +57,6 @@ pub fn pow(comptime T: type, x: T, y: T) T { return x; } - // special case sqrt - if (y == 0.5) { - return math.sqrt(x); - } - - if (y == -0.5) { - return 1 / math.sqrt(x); - } - if (x == 0) { if (y < 0) { // pow(+-0, y) = +- 0 for y an odd integer @@ -112,14 +107,16 @@ pub fn pow(comptime T: type, x: T, y: T) T { } } - var ay = y; - var flip = false; - if (ay < 0) { - ay = -ay; - flip = true; + // special case sqrt + if (y == 0.5) { + return math.sqrt(x); + } + + if (y == -0.5) { + return 1 / math.sqrt(x); } - const r1 = math.modf(ay); + const r1 = math.modf(math.fabs(y)); var yi = r1.ipart; var yf = r1.fpart; @@ -148,8 +145,18 @@ pub fn pow(comptime T: type, x: T, y: T) T { var xe = r2.exponent; var x1 = r2.significand; - var i = @floatToInt(i32, yi); + var i = @floatToInt(@IntType(true, T.bit_count), yi); while (i != 0) : (i >>= 1) { + const overflow_shift = math.floatExponentBits(T) + 1; + if (xe < -(1 << overflow_shift) or (1 << overflow_shift) < xe) { + // catch xe before it overflows the left shift below + // Since i != 0 it has at least one bit still set, so ae will accumulate xe + // on at least one more iteration, ae += xe is a lower bound on ae + // the lower bound on ae exceeds the size of a float exp + // so the final call to Ldexp will produce under/overflow (0/Inf) + ae += xe; + break; + } if (i & 1 == 1) { a1 *= x1; ae += xe; @@ -163,7 +170,7 @@ pub fn pow(comptime T: type, x: T, y: T) T { } // a *= a1 * 2^ae - if (flip) { + if (y < 0) { a1 = 1 / a1; ae = -ae; } @@ -202,6 +209,9 @@ test "math.pow.special" { expect(pow(f32, 45, 1.0) == 45); expect(pow(f32, -45, 1.0) == -45); expect(math.isNan(pow(f32, math.nan(f32), 5.0))); + expect(math.isPositiveInf(pow(f32, -math.inf(f32), 0.5))); + expect(math.isPositiveInf(pow(f32, -0, -0.5))); + expect(pow(f32, -0, 0.5) == 0); expect(math.isNan(pow(f32, 5.0, math.nan(f32)))); expect(math.isPositiveInf(pow(f32, 0.0, -1.0))); //expect(math.isNegativeInf(pow(f32, -0.0, -3.0))); TODO is this required? @@ -232,3 +242,11 @@ test "math.pow.special" { expect(math.isNan(pow(f32, -1.0, 1.2))); expect(math.isNan(pow(f32, -12.4, 78.5))); } + +test "math.pow.overflow" { + expect(math.isPositiveInf(pow(f64, 2, 1 << 32))); + expect(pow(f64, 2, -(1 << 32)) == 0); + expect(math.isNegativeInf(pow(f64, -2, (1 << 32) + 1))); + expect(pow(f64, 0.5, 1 << 45) == 0); + expect(math.isPositiveInf(pow(f64, 0.5, -(1 << 45)))); +} diff --git a/std/math/powi.zig b/std/math/powi.zig index b7212efcbf..d80700e5cd 100644 --- a/std/math/powi.zig +++ b/std/math/powi.zig @@ -1,12 +1,7 @@ -// Special Cases: +// Based on Rust, which is licensed under the MIT license. +// https://github.com/rust-lang/rust/blob/360432f1e8794de58cd94f34c9c17ad65871e5b5/LICENSE-MIT // -// powi(x, +-0) = 1 for any x -// powi(0, y) = 0 for any y -// powi(1, y) = 1 for any y -// powi(-1, y) = -1 for for y an odd integer -// powi(-1, y) = 1 for for y an even integer -// powi(x, y) = Overflow for for y >= @sizeOf(x) - 1 y > 0 -// powi(x, y) = Underflow for for y > @sizeOf(x) - 1 y < 0 +// https://github.com/rust-lang/rust/blob/360432f1e8794de58cd94f34c9c17ad65871e5b5/src/libcore/num/mod.rs#L3423 const builtin = @import("builtin"); const std = @import("../std.zig"); @@ -14,7 +9,16 @@ const math = std.math; const assert = std.debug.assert; const testing = std.testing; -// This implementation is based on that from the rust stlib +/// Returns the power of x raised by the integer y (x^y). +/// +/// Special Cases: +/// - powi(x, +-0) = 1 for any x +/// - powi(0, y) = 0 for any y +/// - powi(1, y) = 1 for any y +/// - powi(-1, y) = -1 for y an odd integer +/// - powi(-1, y) = 1 for y an even integer +/// - powi(x, y) = Overflow for y >= @sizeOf(x) - 1 or y > 0 +/// - powi(x, y) = Underflow for y > @sizeOf(x) - 1 or y < 0 pub fn powi(comptime T: type, x: T, y: T) (error{ Overflow, Underflow, diff --git a/std/math/round.zig b/std/math/round.zig index 39ff56ca79..0b80a46ce5 100644 --- a/std/math/round.zig +++ b/std/math/round.zig @@ -1,14 +1,20 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - round(+-0) = +-0 -// - round(+-inf) = +-inf -// - round(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/roundf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/round.c const builtin = @import("builtin"); const expect = std.testing.expect; const std = @import("../std.zig"); const math = std.math; +/// Returns x rounded to the nearest integer, rounding half away from zero. +/// +/// Special Cases: +/// - round(+-0) = +-0 +/// - round(+-inf) = +-inf +/// - round(nan) = nan pub fn round(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/scalbn.zig b/std/math/scalbn.zig index d1338f5acb..d5716d621c 100644 --- a/std/math/scalbn.zig +++ b/std/math/scalbn.zig @@ -1,7 +1,14 @@ +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT +// +// https://git.musl-libc.org/cgit/musl/tree/src/math/scalbnf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/scalbn.c + const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns x * 2^n. pub fn scalbn(x: var, n: i32) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/signbit.zig b/std/math/signbit.zig index 9727152b07..e5c5909292 100644 --- a/std/math/signbit.zig +++ b/std/math/signbit.zig @@ -2,6 +2,7 @@ const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns whether x is negative or negative 0. pub fn signbit(x: var) bool { const T = @typeOf(x); return switch (T) { diff --git a/std/math/sin.zig b/std/math/sin.zig index e25b8a292b..f21db4054e 100644 --- a/std/math/sin.zig +++ b/std/math/sin.zig @@ -1,19 +1,24 @@ -// Special Cases: +// Ported from go, which is licensed under a BSD-3 license. +// https://golang.org/LICENSE // -// - sin(+-0) = +-0 -// - sin(+-inf) = nan -// - sin(nan) = nan +// https://golang.org/src/math/sin.go const builtin = @import("builtin"); const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the sine of the radian value x. +/// +/// Special Cases: +/// - sin(+-0) = +-0 +/// - sin(+-inf) = nan +/// - sin(nan) = nan pub fn sin(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { - f32 => sin32(x), - f64 => sin64(x), + f32 => sin_(T, x), + f64 => sin_(T, x), else => @compileError("sin not implemented for " ++ @typeName(T)), }; } @@ -34,83 +39,27 @@ const C3 = 2.48015872888517045348E-5; const C4 = -1.38888888888730564116E-3; const C5 = 4.16666666666665929218E-2; -// NOTE: This is taken from the go stdlib. The musl implementation is much more complex. -// -// This may have slight differences on some edge cases and may need to replaced if so. -fn sin32(x_: f32) f32 { - const pi4a = 7.85398125648498535156e-1; - const pi4b = 3.77489470793079817668E-8; - const pi4c = 2.69515142907905952645E-15; - const m4pi = 1.273239544735162542821171882678754627704620361328125; - - var x = x_; - if (x == 0 or math.isNan(x)) { - return x; - } - if (math.isInf(x)) { - return math.nan(f32); - } - - var sign = false; - if (x < 0) { - x = -x; - sign = true; - } - - var y = math.floor(x * m4pi); - var j = @floatToInt(i64, y); - - if (j & 1 == 1) { - j += 1; - y += 1; - } +const pi4a = 7.85398125648498535156e-1; +const pi4b = 3.77489470793079817668E-8; +const pi4c = 2.69515142907905952645E-15; +const m4pi = 1.273239544735162542821171882678754627704620361328125; - j &= 7; - if (j > 3) { - j -= 4; - sign = !sign; - } - - const z = ((x - y * pi4a) - y * pi4b) - y * pi4c; - const w = z * z; - - const r = r: { - if (j == 1 or j == 2) { - break :r 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0))))); - } else { - break :r z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0))))); - } - }; - - if (sign) { - return -r; - } else { - return r; - } -} - -fn sin64(x_: f64) f64 { - const pi4a = 7.85398125648498535156e-1; - const pi4b = 3.77489470793079817668E-8; - const pi4c = 2.69515142907905952645E-15; - const m4pi = 1.273239544735162542821171882678754627704620361328125; +fn sin_(comptime T: type, x_: T) T { + const I = @IntType(true, T.bit_count); var x = x_; if (x == 0 or math.isNan(x)) { return x; } if (math.isInf(x)) { - return math.nan(f64); + return math.nan(T); } - var sign = false; - if (x < 0) { - x = -x; - sign = true; - } + var sign = x < 0; + x = math.fabs(x); var y = math.floor(x * m4pi); - var j = @floatToInt(i64, y); + var j = @floatToInt(I, y); if (j & 1 == 1) { j += 1; @@ -126,61 +75,56 @@ fn sin64(x_: f64) f64 { const z = ((x - y * pi4a) - y * pi4b) - y * pi4c; const w = z * z; - const r = r: { - if (j == 1 or j == 2) { - break :r 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0))))); - } else { - break :r z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0))))); - } - }; + const r = if (j == 1 or j == 2) + 1.0 - 0.5 * w + w * w * (C5 + w * (C4 + w * (C3 + w * (C2 + w * (C1 + w * C0))))) + else + z + z * w * (S5 + w * (S4 + w * (S3 + w * (S2 + w * (S1 + w * S0))))); - if (sign) { - return -r; - } else { - return r; - } + return if (sign) -r else r; } test "math.sin" { - expect(sin(f32(0.0)) == sin32(0.0)); - expect(sin(f64(0.0)) == sin64(0.0)); + expect(sin(f32(0.0)) == sin_(f32, 0.0)); + expect(sin(f64(0.0)) == sin_(f64, 0.0)); expect(comptime (math.sin(f64(2))) == math.sin(f64(2))); } test "math.sin32" { const epsilon = 0.000001; - expect(math.approxEq(f32, sin32(0.0), 0.0, epsilon)); - expect(math.approxEq(f32, sin32(0.2), 0.198669, epsilon)); - expect(math.approxEq(f32, sin32(0.8923), 0.778517, epsilon)); - expect(math.approxEq(f32, sin32(1.5), 0.997495, epsilon)); - expect(math.approxEq(f32, sin32(37.45), -0.246544, epsilon)); - expect(math.approxEq(f32, sin32(89.123), 0.916166, epsilon)); + expect(math.approxEq(f32, sin_(f32, 0.0), 0.0, epsilon)); + expect(math.approxEq(f32, sin_(f32, 0.2), 0.198669, epsilon)); + expect(math.approxEq(f32, sin_(f32, 0.8923), 0.778517, epsilon)); + expect(math.approxEq(f32, sin_(f32, 1.5), 0.997495, epsilon)); + expect(math.approxEq(f32, sin_(f32, -1.5), -0.997495, epsilon)); + expect(math.approxEq(f32, sin_(f32, 37.45), -0.246544, epsilon)); + expect(math.approxEq(f32, sin_(f32, 89.123), 0.916166, epsilon)); } test "math.sin64" { const epsilon = 0.000001; - expect(math.approxEq(f64, sin64(0.0), 0.0, epsilon)); - expect(math.approxEq(f64, sin64(0.2), 0.198669, epsilon)); - expect(math.approxEq(f64, sin64(0.8923), 0.778517, epsilon)); - expect(math.approxEq(f64, sin64(1.5), 0.997495, epsilon)); - expect(math.approxEq(f64, sin64(37.45), -0.246543, epsilon)); - expect(math.approxEq(f64, sin64(89.123), 0.916166, epsilon)); + expect(math.approxEq(f64, sin_(f64, 0.0), 0.0, epsilon)); + expect(math.approxEq(f64, sin_(f64, 0.2), 0.198669, epsilon)); + expect(math.approxEq(f64, sin_(f64, 0.8923), 0.778517, epsilon)); + expect(math.approxEq(f64, sin_(f64, 1.5), 0.997495, epsilon)); + expect(math.approxEq(f64, sin_(f64, -1.5), -0.997495, epsilon)); + expect(math.approxEq(f64, sin_(f64, 37.45), -0.246543, epsilon)); + expect(math.approxEq(f64, sin_(f64, 89.123), 0.916166, epsilon)); } test "math.sin32.special" { - expect(sin32(0.0) == 0.0); - expect(sin32(-0.0) == -0.0); - expect(math.isNan(sin32(math.inf(f32)))); - expect(math.isNan(sin32(-math.inf(f32)))); - expect(math.isNan(sin32(math.nan(f32)))); + expect(sin_(f32, 0.0) == 0.0); + expect(sin_(f32, -0.0) == -0.0); + expect(math.isNan(sin_(f32, math.inf(f32)))); + expect(math.isNan(sin_(f32, -math.inf(f32)))); + expect(math.isNan(sin_(f32, math.nan(f32)))); } test "math.sin64.special" { - expect(sin64(0.0) == 0.0); - expect(sin64(-0.0) == -0.0); - expect(math.isNan(sin64(math.inf(f64)))); - expect(math.isNan(sin64(-math.inf(f64)))); - expect(math.isNan(sin64(math.nan(f64)))); + expect(sin_(f64, 0.0) == 0.0); + expect(sin_(f64, -0.0) == -0.0); + expect(math.isNan(sin_(f64, math.inf(f64)))); + expect(math.isNan(sin_(f64, -math.inf(f64)))); + expect(math.isNan(sin_(f64, math.nan(f64)))); } diff --git a/std/math/sinh.zig b/std/math/sinh.zig index cf4363c4a9..73ee65ea6f 100644 --- a/std/math/sinh.zig +++ b/std/math/sinh.zig @@ -1,8 +1,8 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - sinh(+-0) = +-0 -// - sinh(+-inf) = +-inf -// - sinh(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/sinhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/sinh.c const builtin = @import("builtin"); const std = @import("../std.zig"); @@ -11,6 +11,12 @@ const expect = std.testing.expect; const expo2 = @import("expo2.zig").expo2; const maxInt = std.math.maxInt; +/// Returns the hyperbolic sine of x. +/// +/// Special Cases: +/// - sinh(+-0) = +-0 +/// - sinh(+-inf) = +-inf +/// - sinh(nan) = nan pub fn sinh(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/sqrt.zig b/std/math/sqrt.zig index 9062f598a1..30af5915d4 100644 --- a/std/math/sqrt.zig +++ b/std/math/sqrt.zig @@ -1,10 +1,3 @@ -// Special Cases: -// -// - sqrt(+inf) = +inf -// - sqrt(+-0) = +-0 -// - sqrt(x) = nan if x < 0 -// - sqrt(nan) = nan - const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; @@ -12,6 +5,13 @@ const builtin = @import("builtin"); const TypeId = builtin.TypeId; const maxInt = std.math.maxInt; +/// Returns the square root of x. +/// +/// Special Cases: +/// - sqrt(+inf) = +inf +/// - sqrt(+-0) = +-0 +/// - sqrt(x) = nan if x < 0 +/// - sqrt(nan) = nan pub fn sqrt(x: var) (if (@typeId(@typeOf(x)) == TypeId.Int) @IntType(false, @typeOf(x).bit_count / 2) else @typeOf(x)) { const T = @typeOf(x); switch (@typeId(T)) { diff --git a/std/math/tan.zig b/std/math/tan.zig index fc11ebdef7..e8259ee7ad 100644 --- a/std/math/tan.zig +++ b/std/math/tan.zig @@ -1,19 +1,24 @@ -// Special Cases: +// Ported from go, which is licensed under a BSD-3 license. +// https://golang.org/LICENSE // -// - tan(+-0) = +-0 -// - tan(+-inf) = nan -// - tan(nan) = nan +// https://golang.org/src/math/tan.go const builtin = @import("builtin"); const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; +/// Returns the tangent of the radian value x. +/// +/// Special Cases: +/// - tan(+-0) = +-0 +/// - tan(+-inf) = nan +/// - tan(nan) = nan pub fn tan(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { - f32 => tan32(x), - f64 => tan64(x), + f32 => tan_(f32, x), + f64 => tan_(f64, x), else => @compileError("tan not implemented for " ++ @typeName(T)), }; } @@ -27,80 +32,27 @@ const Tq2 = -1.32089234440210967447E6; const Tq3 = 2.50083801823357915839E7; const Tq4 = -5.38695755929454629881E7; -// NOTE: This is taken from the go stdlib. The musl implementation is much more complex. -// -// This may have slight differences on some edge cases and may need to replaced if so. -fn tan32(x_: f32) f32 { - const pi4a = 7.85398125648498535156e-1; - const pi4b = 3.77489470793079817668E-8; - const pi4c = 2.69515142907905952645E-15; - const m4pi = 1.273239544735162542821171882678754627704620361328125; - - var x = x_; - if (x == 0 or math.isNan(x)) { - return x; - } - if (math.isInf(x)) { - return math.nan(f32); - } - - var sign = false; - if (x < 0) { - x = -x; - sign = true; - } - - var y = math.floor(x * m4pi); - var j = @floatToInt(i64, y); - - if (j & 1 == 1) { - j += 1; - y += 1; - } - - const z = ((x - y * pi4a) - y * pi4b) - y * pi4c; - const w = z * z; - - var r = r: { - if (w > 1e-14) { - break :r z + z * (w * ((Tp0 * w + Tp1) * w + Tp2) / ((((w + Tq1) * w + Tq2) * w + Tq3) * w + Tq4)); - } else { - break :r z; - } - }; - - if (j & 2 == 2) { - r = -1 / r; - } - if (sign) { - r = -r; - } - - return r; -} +const pi4a = 7.85398125648498535156e-1; +const pi4b = 3.77489470793079817668E-8; +const pi4c = 2.69515142907905952645E-15; +const m4pi = 1.273239544735162542821171882678754627704620361328125; -fn tan64(x_: f64) f64 { - const pi4a = 7.85398125648498535156e-1; - const pi4b = 3.77489470793079817668E-8; - const pi4c = 2.69515142907905952645E-15; - const m4pi = 1.273239544735162542821171882678754627704620361328125; +fn tan_(comptime T: type, x_: T) T { + const I = @IntType(true, T.bit_count); var x = x_; if (x == 0 or math.isNan(x)) { return x; } if (math.isInf(x)) { - return math.nan(f64); + return math.nan(T); } - var sign = false; - if (x < 0) { - x = -x; - sign = true; - } + var sign = x < 0; + x = math.fabs(x); var y = math.floor(x * m4pi); - var j = @floatToInt(i64, y); + var j = @floatToInt(I, y); if (j & 1 == 1) { j += 1; @@ -110,63 +62,57 @@ fn tan64(x_: f64) f64 { const z = ((x - y * pi4a) - y * pi4b) - y * pi4c; const w = z * z; - var r = r: { - if (w > 1e-14) { - break :r z + z * (w * ((Tp0 * w + Tp1) * w + Tp2) / ((((w + Tq1) * w + Tq2) * w + Tq3) * w + Tq4)); - } else { - break :r z; - } - }; + var r = if (w > 1e-14) + z + z * (w * ((Tp0 * w + Tp1) * w + Tp2) / ((((w + Tq1) * w + Tq2) * w + Tq3) * w + Tq4)) + else + z; if (j & 2 == 2) { r = -1 / r; } - if (sign) { - r = -r; - } - return r; + return if (sign) -r else r; } test "math.tan" { - expect(tan(f32(0.0)) == tan32(0.0)); - expect(tan(f64(0.0)) == tan64(0.0)); + expect(tan(f32(0.0)) == tan_(f32, 0.0)); + expect(tan(f64(0.0)) == tan_(f64, 0.0)); } test "math.tan32" { const epsilon = 0.000001; - expect(math.approxEq(f32, tan32(0.0), 0.0, epsilon)); - expect(math.approxEq(f32, tan32(0.2), 0.202710, epsilon)); - expect(math.approxEq(f32, tan32(0.8923), 1.240422, epsilon)); - expect(math.approxEq(f32, tan32(1.5), 14.101420, epsilon)); - expect(math.approxEq(f32, tan32(37.45), -0.254397, epsilon)); - expect(math.approxEq(f32, tan32(89.123), 2.285852, epsilon)); + expect(math.approxEq(f32, tan_(f32, 0.0), 0.0, epsilon)); + expect(math.approxEq(f32, tan_(f32, 0.2), 0.202710, epsilon)); + expect(math.approxEq(f32, tan_(f32, 0.8923), 1.240422, epsilon)); + expect(math.approxEq(f32, tan_(f32, 1.5), 14.101420, epsilon)); + expect(math.approxEq(f32, tan_(f32, 37.45), -0.254397, epsilon)); + expect(math.approxEq(f32, tan_(f32, 89.123), 2.285852, epsilon)); } test "math.tan64" { const epsilon = 0.000001; - expect(math.approxEq(f64, tan64(0.0), 0.0, epsilon)); - expect(math.approxEq(f64, tan64(0.2), 0.202710, epsilon)); - expect(math.approxEq(f64, tan64(0.8923), 1.240422, epsilon)); - expect(math.approxEq(f64, tan64(1.5), 14.101420, epsilon)); - expect(math.approxEq(f64, tan64(37.45), -0.254397, epsilon)); - expect(math.approxEq(f64, tan64(89.123), 2.2858376, epsilon)); + expect(math.approxEq(f64, tan_(f64, 0.0), 0.0, epsilon)); + expect(math.approxEq(f64, tan_(f64, 0.2), 0.202710, epsilon)); + expect(math.approxEq(f64, tan_(f64, 0.8923), 1.240422, epsilon)); + expect(math.approxEq(f64, tan_(f64, 1.5), 14.101420, epsilon)); + expect(math.approxEq(f64, tan_(f64, 37.45), -0.254397, epsilon)); + expect(math.approxEq(f64, tan_(f64, 89.123), 2.2858376, epsilon)); } test "math.tan32.special" { - expect(tan32(0.0) == 0.0); - expect(tan32(-0.0) == -0.0); - expect(math.isNan(tan32(math.inf(f32)))); - expect(math.isNan(tan32(-math.inf(f32)))); - expect(math.isNan(tan32(math.nan(f32)))); + expect(tan_(f32, 0.0) == 0.0); + expect(tan_(f32, -0.0) == -0.0); + expect(math.isNan(tan_(f32, math.inf(f32)))); + expect(math.isNan(tan_(f32, -math.inf(f32)))); + expect(math.isNan(tan_(f32, math.nan(f32)))); } test "math.tan64.special" { - expect(tan64(0.0) == 0.0); - expect(tan64(-0.0) == -0.0); - expect(math.isNan(tan64(math.inf(f64)))); - expect(math.isNan(tan64(-math.inf(f64)))); - expect(math.isNan(tan64(math.nan(f64)))); + expect(tan_(f64, 0.0) == 0.0); + expect(tan_(f64, -0.0) == -0.0); + expect(math.isNan(tan_(f64, math.inf(f64)))); + expect(math.isNan(tan_(f64, -math.inf(f64)))); + expect(math.isNan(tan_(f64, math.nan(f64)))); } diff --git a/std/math/tanh.zig b/std/math/tanh.zig index f88ecfdc9c..48d26d091e 100644 --- a/std/math/tanh.zig +++ b/std/math/tanh.zig @@ -1,8 +1,8 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - sinh(+-0) = +-0 -// - sinh(+-inf) = +-1 -// - sinh(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/tanhf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/tanh.c const builtin = @import("builtin"); const std = @import("../std.zig"); @@ -11,6 +11,12 @@ const expect = std.testing.expect; const expo2 = @import("expo2.zig").expo2; const maxInt = std.math.maxInt; +/// Returns the hyperbolic tangent of x. +/// +/// Special Cases: +/// - sinh(+-0) = +-0 +/// - sinh(+-inf) = +-1 +/// - sinh(nan) = nan pub fn tanh(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/math/trunc.zig b/std/math/trunc.zig index 9449d0e7eb..219bcd4914 100644 --- a/std/math/trunc.zig +++ b/std/math/trunc.zig @@ -1,14 +1,20 @@ -// Special Cases: +// Ported from musl, which is licensed under the MIT license: +// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT // -// - trunc(+-0) = +-0 -// - trunc(+-inf) = +-inf -// - trunc(nan) = nan +// https://git.musl-libc.org/cgit/musl/tree/src/math/truncf.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/trunc.c const std = @import("../std.zig"); const math = std.math; const expect = std.testing.expect; const maxInt = std.math.maxInt; +/// Returns the integer value of x. +/// +/// Special Cases: +/// - trunc(+-0) = +-0 +/// - trunc(+-inf) = +-inf +/// - trunc(nan) = nan pub fn trunc(x: var) @typeOf(x) { const T = @typeOf(x); return switch (T) { diff --git a/std/mem.zig b/std/mem.zig index 9bf68698c9..2407b56f07 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -34,39 +34,39 @@ pub const Allocator = struct { /// The returned slice must have its pointer aligned at least to `new_alignment` bytes. reallocFn: fn ( self: *Allocator, - // Guaranteed to be the same as what was returned from most recent call to - // `reallocFn` or `shrinkFn`. - // If `old_mem.len == 0` then this is a new allocation and `new_byte_count` - // is guaranteed to be >= 1. + /// Guaranteed to be the same as what was returned from most recent call to + /// `reallocFn` or `shrinkFn`. + /// If `old_mem.len == 0` then this is a new allocation and `new_byte_count` + /// is guaranteed to be >= 1. old_mem: []u8, - // If `old_mem.len == 0` then this is `undefined`, otherwise: - // Guaranteed to be the same as what was returned from most recent call to - // `reallocFn` or `shrinkFn`. - // Guaranteed to be >= 1. - // Guaranteed to be a power of 2. + /// If `old_mem.len == 0` then this is `undefined`, otherwise: + /// Guaranteed to be the same as what was returned from most recent call to + /// `reallocFn` or `shrinkFn`. + /// Guaranteed to be >= 1. + /// Guaranteed to be a power of 2. old_alignment: u29, - // If `new_byte_count` is 0 then this is a free and it is guaranteed that - // `old_mem.len != 0`. + /// If `new_byte_count` is 0 then this is a free and it is guaranteed that + /// `old_mem.len != 0`. new_byte_count: usize, - // Guaranteed to be >= 1. - // Guaranteed to be a power of 2. - // Returned slice's pointer must have this alignment. + /// Guaranteed to be >= 1. + /// Guaranteed to be a power of 2. + /// Returned slice's pointer must have this alignment. new_alignment: u29, ) Error![]u8, /// This function deallocates memory. It must succeed. shrinkFn: fn ( self: *Allocator, - // Guaranteed to be the same as what was returned from most recent call to - // `reallocFn` or `shrinkFn`. + /// Guaranteed to be the same as what was returned from most recent call to + /// `reallocFn` or `shrinkFn`. old_mem: []u8, - // Guaranteed to be the same as what was returned from most recent call to - // `reallocFn` or `shrinkFn`. + /// Guaranteed to be the same as what was returned from most recent call to + /// `reallocFn` or `shrinkFn`. old_alignment: u29, - // Guaranteed to be less than or equal to `old_mem.len`. + /// Guaranteed to be less than or equal to `old_mem.len`. new_byte_count: usize, - // If `new_byte_count == 0` then this is `undefined`, otherwise: - // Guaranteed to be less than or equal to `old_alignment`. + /// If `new_byte_count == 0` then this is `undefined`, otherwise: + /// Guaranteed to be less than or equal to `old_alignment`. new_alignment: u29, ) []u8, @@ -104,10 +104,7 @@ pub const Allocator = struct { const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory; const byte_slice = try self.reallocFn(self, ([*]u8)(undefined)[0..0], undefined, byte_count, alignment); assert(byte_slice.len == byte_count); - // This loop gets optimized out in ReleaseFast mode - for (byte_slice) |*byte| { - byte.* = undefined; - } + @memset(byte_slice.ptr, undefined, byte_slice.len); return @bytesToSlice(T, @alignCast(alignment, byte_slice)); } @@ -153,10 +150,7 @@ pub const Allocator = struct { const byte_slice = try self.reallocFn(self, old_byte_slice, Slice.alignment, byte_count, new_alignment); assert(byte_slice.len == byte_count); if (new_n > old_mem.len) { - // This loop gets optimized out in ReleaseFast mode - for (byte_slice[old_byte_slice.len..]) |*byte| { - byte.* = undefined; - } + @memset(byte_slice.ptr + old_byte_slice.len, undefined, byte_slice.len - old_byte_slice.len); } return @bytesToSlice(T, @alignCast(new_alignment, byte_slice)); } diff --git a/std/os.zig b/std/os.zig index d641cf29c9..fe97c1aa61 100644 --- a/std/os.zig +++ b/std/os.zig @@ -23,6 +23,7 @@ test "std.os" { _ = @import("os/time.zig"); _ = @import("os/windows.zig"); _ = @import("os/uefi.zig"); + _ = @import("os/wasi.zig"); _ = @import("os/get_app_data_dir.zig"); } @@ -33,6 +34,7 @@ pub const freebsd = @import("os/freebsd.zig"); pub const netbsd = @import("os/netbsd.zig"); pub const zen = @import("os/zen.zig"); pub const uefi = @import("os/uefi.zig"); +pub const wasi = @import("os/wasi.zig"); pub const posix = switch (builtin.os) { Os.linux => linux, @@ -40,6 +42,7 @@ pub const posix = switch (builtin.os) { Os.freebsd => freebsd, Os.netbsd => netbsd, Os.zen => zen, + Os.wasi => wasi, else => @compileError("Unsupported OS"), }; @@ -50,7 +53,11 @@ pub const path = @import("os/path.zig"); pub const File = @import("os/file.zig").File; pub const time = @import("os/time.zig"); -pub const page_size = 4 * 1024; +pub const page_size = switch (builtin.arch) { + .wasm32, .wasm64 => 64 * 1024, + else => 4 * 1024, +}; + pub const MAX_PATH_BYTES = switch (builtin.os) { Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => posix.PATH_MAX, // Each UTF-16LE character may be expanded to 3 UTF-8 bytes. @@ -139,6 +146,12 @@ pub fn getRandomBytes(buf: []u8) !void { }; } }, + Os.wasi => { + const random_get_result = os.wasi.random_get(buf.ptr, buf.len); + if (random_get_result != os.wasi.ESUCCESS) { + return error.Unknown; + } + }, Os.zen => { const randomness = []u8{ 42, 1, 7, 12, 22, 17, 99, 16, 26, 87, 41, 45 }; var i: usize = 0; @@ -198,6 +211,12 @@ pub fn abort() noreturn { } windows.ExitProcess(3); }, + Os.wasi => { + _ = wasi.proc_raise(wasi.SIGABRT); + // TODO: Is SIGKILL even necessary? + _ = wasi.proc_raise(wasi.SIGKILL); + while (true) {} + }, Os.uefi => { // TODO there's gotta be a better thing to do here than loop forever while (true) {} @@ -226,6 +245,9 @@ pub fn exit(status: u8) noreturn { Os.windows => { windows.ExitProcess(status); }, + Os.wasi => { + wasi.proc_exit(status); + }, else => @compileError("Unsupported OS"), } } @@ -749,6 +771,37 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { try result.setMove(key, value); } + } else if (builtin.os == Os.wasi) { + var environ_count: usize = undefined; + var environ_buf_size: usize = undefined; + + const environ_sizes_get_ret = std.os.wasi.environ_sizes_get(&environ_count, &environ_buf_size); + if (environ_sizes_get_ret != os.wasi.ESUCCESS) { + return unexpectedErrorPosix(environ_sizes_get_ret); + } + + // TODO: Verify that the documentation is incorrect + // https://github.com/WebAssembly/WASI/issues/27 + var environ = try allocator.alloc(?[*]u8, environ_count + 1); + defer allocator.free(environ); + var environ_buf = try std.heap.wasm_allocator.alloc(u8, environ_buf_size); + defer allocator.free(environ_buf); + + const environ_get_ret = std.os.wasi.environ_get(environ.ptr, environ_buf.ptr); + if (environ_get_ret != os.wasi.ESUCCESS) { + return unexpectedErrorPosix(environ_get_ret); + } + + for (environ) |env| { + if (env) |ptr| { + const pair = mem.toSlice(u8, ptr); + var parts = mem.separate(pair, "="); + const key = parts.next().?; + const value = parts.next().?; + try result.set(key, value); + } + } + return result; } else { for (posix_environ_raw) |ptr| { var line_i: usize = 0; @@ -1083,13 +1136,14 @@ pub fn copyFile(source_path: []const u8, dest_path: []const u8) !void { defer in_file.close(); const mode = try in_file.mode(); + const in_stream = &in_file.inStream().stream; var atomic_file = try AtomicFile.init(dest_path, mode); defer atomic_file.deinit(); var buf: [page_size]u8 = undefined; while (true) { - const amt = try in_file.readFull(buf[0..]); + const amt = try in_stream.readFull(buf[0..]); try atomic_file.file.write(buf[0..amt]); if (amt != buf.len) { return atomic_file.finish(); @@ -2127,6 +2181,11 @@ pub const ArgIterator = struct { inner: InnerType, pub fn init() ArgIterator { + if (builtin.os == Os.wasi) { + // TODO: Figure out a compatible interface accomodating WASI + @compileError("ArgIterator is not yet supported in WASI. Use argsAlloc and argsFree instead."); + } + return ArgIterator{ .inner = InnerType.init() }; } @@ -2159,6 +2218,34 @@ pub fn args() ArgIterator { /// Caller must call argsFree on result. pub fn argsAlloc(allocator: *mem.Allocator) ![]const []u8 { + if (builtin.os == Os.wasi) { + var count: usize = undefined; + var buf_size: usize = undefined; + + const args_sizes_get_ret = os.wasi.args_sizes_get(&count, &buf_size); + if (args_sizes_get_ret != os.wasi.ESUCCESS) { + return unexpectedErrorPosix(args_sizes_get_ret); + } + + var argv = try allocator.alloc([*]u8, count); + defer allocator.free(argv); + + var argv_buf = try allocator.alloc(u8, buf_size); + const args_get_ret = os.wasi.args_get(argv.ptr, argv_buf.ptr); + if (args_get_ret != os.wasi.ESUCCESS) { + return unexpectedErrorPosix(args_get_ret); + } + + var result_slice = try allocator.alloc([]u8, count); + + var i: usize = 0; + while (i < count) : (i += 1) { + result_slice[i] = mem.toSlice(u8, argv[i]); + } + + return result_slice; + } + // TODO refactor to only make 1 allocation. var it = args(); var contents = try Buffer.initSize(allocator, 0); @@ -2196,6 +2283,16 @@ pub fn argsAlloc(allocator: *mem.Allocator) ![]const []u8 { } pub fn argsFree(allocator: *mem.Allocator, args_alloc: []const []u8) void { + if (builtin.os == Os.wasi) { + const last_item = args_alloc[args_alloc.len - 1]; + const last_byte_addr = @ptrToInt(last_item.ptr) + last_item.len + 1; // null terminated + const first_item_ptr = args_alloc[0].ptr; + const len = last_byte_addr - @ptrToInt(first_item_ptr); + allocator.free(first_item_ptr[0..len]); + + return allocator.free(args_alloc); + } + var total_bytes: usize = 0; for (args_alloc) |arg| { total_bytes += @sizeOf([]u8) + arg.len; @@ -3030,9 +3127,6 @@ pub const SpawnThreadError = error{ Unexpected, }; -pub var linux_tls_phdr: ?*std.elf.Phdr = null; -pub var linux_tls_img_src: [*]const u8 = undefined; // defined if linux_tls_phdr is - /// caller must call wait on the returned thread /// fn startFn(@typeOf(context)) T /// where T is u8, noreturn, void, or !void @@ -3142,12 +3236,10 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread } // Finally, the Thread Local Storage, if any. if (!Thread.use_pthreads) { - if (linux_tls_phdr) |tls_phdr| { - l = mem.alignForward(l, tls_phdr.p_align); + if (linux.tls.tls_image) |tls_img| { + l = mem.alignForward(l, @alignOf(usize)); tls_start_offset = l; - l += tls_phdr.p_memsz; - // the fs register address - l += @sizeOf(usize); + l += tls_img.alloc_size; } } break :blk l; @@ -3188,10 +3280,8 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; var newtls: usize = undefined; - if (linux_tls_phdr) |tls_phdr| { - @memcpy(@intToPtr([*]u8, mmap_addr + tls_start_offset), linux_tls_img_src, tls_phdr.p_filesz); - newtls = mmap_addr + mmap_len - @sizeOf(usize); - @intToPtr(*usize, newtls).* = newtls; + if (linux.tls.tls_image) |tls_img| { + newtls = linux.tls.copyTLS(mmap_addr + tls_start_offset); flags |= posix.CLONE_SETTLS; } const rc = posix.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle); diff --git a/std/os/darwin.zig b/std/os/darwin.zig index 9dc1ce1173..04122100f4 100644 --- a/std/os/darwin.zig +++ b/std/os/darwin.zig @@ -812,7 +812,7 @@ pub fn sigaction(sig: u5, noalias act: *const Sigaction, noalias oact: ?*Sigacti .sa_mask = act.mask, }; var coact: c.Sigaction = undefined; - const result = errnoWrap(c.sigaction(sig, *cact, *coact)); + const result = errnoWrap(c.sigaction(sig, &cact, &coact)); if (result != 0) { return result; } diff --git a/std/os/file.zig b/std/os/file.zig index c9a0e2b696..814e5e318c 100644 --- a/std/os/file.zig +++ b/std/os/file.zig @@ -235,7 +235,7 @@ pub const File = struct { Unexpected, }; - pub fn seekForward(self: File, amount: isize) SeekError!void { + pub fn seekForward(self: File, amount: i64) SeekError!void { switch (builtin.os) { Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { const result = posix.lseek(self.handle, amount, posix.SEEK_CUR); @@ -266,7 +266,7 @@ pub const File = struct { } } - pub fn seekTo(self: File, pos: usize) SeekError!void { + pub fn seekTo(self: File, pos: u64) SeekError!void { switch (builtin.os) { Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { const ipos = try math.cast(isize, pos); @@ -301,13 +301,12 @@ pub const File = struct { } pub const GetSeekPosError = error{ - Overflow, SystemResources, Unseekable, Unexpected, }; - pub fn getPos(self: File) GetSeekPosError!usize { + pub fn getPos(self: File) GetSeekPosError!u64 { switch (builtin.os) { Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { const result = posix.lseek(self.handle, 0, posix.SEEK_CUR); @@ -324,7 +323,7 @@ pub const File = struct { else => os.unexpectedErrorPosix(err), }; } - return result; + return u64(result); }, Os.windows => { var pos: windows.LARGE_INTEGER = undefined; @@ -336,17 +335,16 @@ pub const File = struct { }; } - assert(pos >= 0); - return math.cast(usize, pos); + return @intCast(u64, pos); }, else => @compileError("unsupported OS"), } } - pub fn getEndPos(self: File) GetSeekPosError!usize { + pub fn getEndPos(self: File) GetSeekPosError!u64 { if (is_posix) { const stat = try os.posixFStat(self.handle); - return @intCast(usize, stat.size); + return @intCast(u64, stat.size); } else if (is_windows) { var file_size: windows.LARGE_INTEGER = undefined; if (windows.GetFileSizeEx(self.handle, &file_size) == 0) { @@ -355,9 +353,7 @@ pub const File = struct { else => os.unexpectedErrorWindows(err), }; } - if (file_size < 0) - return error.Overflow; - return math.cast(usize, @intCast(u64, file_size)); + return @intCast(u64, file_size); } else { @compileError("TODO support getEndPos on this OS"); } @@ -492,22 +488,22 @@ pub const File = struct { pub const Stream = io.SeekableStream(SeekError, GetSeekPosError); - pub fn seekToFn(seekable_stream: *Stream, pos: usize) SeekError!void { + pub fn seekToFn(seekable_stream: *Stream, pos: u64) SeekError!void { const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); return self.file.seekTo(pos); } - pub fn seekForwardFn(seekable_stream: *Stream, amt: isize) SeekError!void { + pub fn seekForwardFn(seekable_stream: *Stream, amt: i64) SeekError!void { const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); return self.file.seekForward(amt); } - pub fn getEndPosFn(seekable_stream: *Stream) GetSeekPosError!usize { + pub fn getEndPosFn(seekable_stream: *Stream) GetSeekPosError!u64 { const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); return self.file.getEndPos(); } - pub fn getPosFn(seekable_stream: *Stream) GetSeekPosError!usize { + pub fn getPosFn(seekable_stream: *Stream) GetSeekPosError!u64 { const self = @fieldParentPtr(SeekableStream, "stream", seekable_stream); return self.file.getPos(); } diff --git a/std/os/linux.zig b/std/os/linux.zig index e7f822185b..1e121b81c8 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -2,7 +2,10 @@ const std = @import("../std.zig"); const assert = std.debug.assert; const builtin = @import("builtin"); const maxInt = std.math.maxInt; +const elf = std.elf; +pub const tls = @import("linux/tls.zig"); const vdso = @import("linux/vdso.zig"); +const dl = @import("../dynamic_library.zig"); pub use switch (builtin.arch) { builtin.Arch.x86_64 => @import("linux/x86_64.zig"), builtin.Arch.i386 => @import("linux/i386.zig"), @@ -12,6 +15,7 @@ pub use switch (builtin.arch) { pub use @import("linux/errno.zig"); pub const PATH_MAX = 4096; +pub const IOV_MAX = 1024; pub const STDIN_FILENO = 0; pub const STDOUT_FILENO = 1; @@ -955,10 +959,13 @@ pub fn waitpid(pid: i32, status: *i32, options: i32) usize { return syscall4(SYS_wait4, @bitCast(usize, isize(pid)), @ptrToInt(status), @bitCast(usize, isize(options)), 0); } +var vdso_clock_gettime = @ptrCast(?*const c_void, init_vdso_clock_gettime); + pub fn clock_gettime(clk_id: i32, tp: *timespec) usize { if (VDSO_CGT_SYM.len != 0) { - const f = @atomicLoad(@typeOf(init_vdso_clock_gettime), &vdso_clock_gettime, builtin.AtomicOrder.Unordered); - if (@ptrToInt(f) != 0) { + const ptr = @atomicLoad(?*const c_void, &vdso_clock_gettime, .Unordered); + if (ptr) |fn_ptr| { + const f = @ptrCast(@typeOf(clock_gettime), fn_ptr); const rc = f(clk_id, tp); switch (rc) { 0, @bitCast(usize, isize(-EINVAL)) => return rc, @@ -968,13 +975,18 @@ pub fn clock_gettime(clk_id: i32, tp: *timespec) usize { } return syscall2(SYS_clock_gettime, @bitCast(usize, isize(clk_id)), @ptrToInt(tp)); } -var vdso_clock_gettime = init_vdso_clock_gettime; + extern fn init_vdso_clock_gettime(clk: i32, ts: *timespec) usize { - const addr = vdso.lookup(VDSO_CGT_VER, VDSO_CGT_SYM); - var f = @intToPtr(@typeOf(init_vdso_clock_gettime), addr); - _ = @cmpxchgStrong(@typeOf(init_vdso_clock_gettime), &vdso_clock_gettime, init_vdso_clock_gettime, f, builtin.AtomicOrder.Monotonic, builtin.AtomicOrder.Monotonic); - if (@ptrToInt(f) == 0) return @bitCast(usize, isize(-ENOSYS)); - return f(clk, ts); + const ptr = @intToPtr(?*const c_void, vdso.lookup(VDSO_CGT_VER, VDSO_CGT_SYM)); + // Note that we may not have a VDSO at all, update the stub address anyway + // so that clock_gettime will fall back on the good old (and slow) syscall + _ = @cmpxchgStrong(?*const c_void, &vdso_clock_gettime, &init_vdso_clock_gettime, ptr, .Monotonic, .Monotonic); + // Call into the VDSO if available + if (ptr) |fn_ptr| { + const f = @ptrCast(@typeOf(clock_gettime), fn_ptr); + return f(clk, ts); + } + return @bitCast(usize, isize(-ENOSYS)); } pub fn clock_getres(clk_id: i32, tp: *timespec) usize { @@ -1100,8 +1112,8 @@ pub fn sigaction(sig: u6, noalias act: *const Sigaction, noalias oact: ?*Sigacti const NSIG = 65; const sigset_t = [128 / @sizeOf(usize)]usize; -const all_mask = []usize{maxInt(usize)}; -const app_mask = []usize{0xfffffffc7fffffff}; +const all_mask = []u32{ 0xffffffff, 0xffffffff }; +const app_mask = []u32{ 0xfffffffc, 0x7fffffff }; const k_sigaction = extern struct { handler: extern fn (i32) void, @@ -1193,6 +1205,16 @@ pub const iovec_const = extern struct { iov_len: usize, }; +pub const mmsghdr = extern struct { + msg_hdr: msghdr, + msg_len: u32, +}; + +pub const mmsghdr_const = extern struct { + msg_hdr: msghdr_const, + msg_len: u32, +}; + pub fn getsockname(fd: i32, noalias addr: *sockaddr, noalias len: *socklen_t) usize { return syscall3(SYS_getsockname, @bitCast(usize, isize(fd)), @ptrToInt(addr), @ptrToInt(len)); } @@ -1213,10 +1235,50 @@ pub fn getsockopt(fd: i32, level: u32, optname: u32, noalias optval: [*]u8, noal return syscall5(SYS_getsockopt, @bitCast(usize, isize(fd)), level, optname, @ptrToInt(optval), @ptrToInt(optlen)); } -pub fn sendmsg(fd: i32, msg: *const msghdr, flags: u32) usize { +pub fn sendmsg(fd: i32, msg: *msghdr_const, flags: u32) usize { return syscall3(SYS_sendmsg, @bitCast(usize, isize(fd)), @ptrToInt(msg), flags); } +pub fn sendmmsg(fd: i32, msgvec: [*]mmsghdr_const, vlen: u32, flags: u32) usize { + if (@typeInfo(usize).Int.bits > @typeInfo(@typeOf(mmsghdr(undefined).msg_len)).Int.bits) { + // workaround kernel brokenness: + // if adding up all iov_len overflows a i32 then split into multiple calls + // see https://www.openwall.com/lists/musl/2014/06/07/5 + const kvlen = if (vlen > IOV_MAX) IOV_MAX else vlen; // matches kernel + var next_unsent: usize = 0; + for (msgvec[0..kvlen]) |*msg, i| { + var size: i32 = 0; + const msg_iovlen = @intCast(usize, msg.msg_hdr.msg_iovlen); // kernel side this is treated as unsigned + for (msg.msg_hdr.msg_iov[0..msg_iovlen]) |iov, j| { + if (iov.iov_len > std.math.maxInt(i32) or @addWithOverflow(i32, size, @intCast(i32, iov.iov_len), &size)) { + // batch-send all messages up to the current message + if (next_unsent < i) { + const batch_size = i - next_unsent; + const r = syscall4(SYS_sendmmsg, @bitCast(usize, isize(fd)), @ptrToInt(&msgvec[next_unsent]), batch_size, flags); + if (getErrno(r) != 0) return next_unsent; + if (r < batch_size) return next_unsent + r; + } + // send current message as own packet + const r = sendmsg(fd, &msg.msg_hdr, flags); + if (getErrno(r) != 0) return r; + // Linux limits the total bytes sent by sendmsg to INT_MAX, so this cast is safe. + msg.msg_len = @intCast(u32, r); + next_unsent = i + 1; + break; + } + } + } + if (next_unsent < kvlen or next_unsent == 0) { // want to make sure at least one syscall occurs (e.g. to trigger MSG_EOR) + const batch_size = kvlen - next_unsent; + const r = syscall4(SYS_sendmmsg, @bitCast(usize, isize(fd)), @ptrToInt(&msgvec[next_unsent]), batch_size, flags); + if (getErrno(r) != 0) return r; + return next_unsent + r; + } + return kvlen; + } + return syscall4(SYS_sendmmsg, @bitCast(usize, isize(fd)), @ptrToInt(msgvec), vlen, flags); +} + pub fn connect(fd: i32, addr: *const c_void, len: socklen_t) usize { return syscall3(SYS_connect, @bitCast(usize, isize(fd)), @ptrToInt(addr), len); } @@ -1339,17 +1401,25 @@ pub fn sched_getaffinity(pid: i32, set: []usize) usize { return syscall3(SYS_sched_getaffinity, @bitCast(usize, isize(pid)), set.len * @sizeOf(usize), @ptrToInt(set.ptr)); } -pub const epoll_data = packed union { +pub const epoll_data = extern union { ptr: usize, fd: i32, @"u32": u32, @"u64": u64, }; -pub const epoll_event = packed struct { - events: u32, - data: epoll_data, -}; +// On x86_64 the structure is packed so that it matches the definition of its +// 32bit counterpart +pub const epoll_event = if (builtin.arch != .x86_64) + extern struct { + events: u32, + data: epoll_data, + } +else + packed struct { + events: u32, + data: epoll_data, + }; pub fn epoll_create() usize { return epoll_create1(0); @@ -1534,6 +1604,70 @@ pub const dirent64 = extern struct { d_name: u8, // field address is the address of first byte of name https://github.com/ziglang/zig/issues/173 }; +pub const dl_phdr_info = extern struct { + dlpi_addr: usize, + dlpi_name: ?[*]const u8, + dlpi_phdr: [*]elf.Phdr, + dlpi_phnum: u16, +}; + +// XXX: This should be weak +extern const __ehdr_start: elf.Ehdr = undefined; + +pub fn dl_iterate_phdr(comptime T: type, callback: extern fn (info: *dl_phdr_info, size: usize, data: ?*T) i32, data: ?*T) isize { + if (builtin.link_libc) { + return std.c.dl_iterate_phdr(@ptrCast(std.c.dl_iterate_phdr_callback, callback), @ptrCast(?*c_void, data)); + } + + const elf_base = @ptrToInt(&__ehdr_start); + const n_phdr = __ehdr_start.e_phnum; + const phdrs = (@intToPtr([*]elf.Phdr, elf_base + __ehdr_start.e_phoff))[0..n_phdr]; + + var it = dl.linkmap_iterator(phdrs) catch return 0; + + // The executable has no dynamic link segment, create a single entry for + // the whole ELF image + if (it.end()) { + var info = dl_phdr_info{ + .dlpi_addr = elf_base, + .dlpi_name = c"/proc/self/exe", + .dlpi_phdr = @intToPtr([*]elf.Phdr, elf_base + __ehdr_start.e_phoff), + .dlpi_phnum = __ehdr_start.e_phnum, + }; + + return callback(&info, @sizeOf(dl_phdr_info), data); + } + + // Last return value from the callback function + var last_r: isize = 0; + while (it.next()) |entry| { + var dlpi_phdr: usize = undefined; + var dlpi_phnum: u16 = undefined; + + if (entry.l_addr != 0) { + const elf_header = @intToPtr(*elf.Ehdr, entry.l_addr); + dlpi_phdr = entry.l_addr + elf_header.e_phoff; + dlpi_phnum = elf_header.e_phnum; + } else { + // This is the running ELF image + dlpi_phdr = elf_base + __ehdr_start.e_phoff; + dlpi_phnum = __ehdr_start.e_phnum; + } + + var info = dl_phdr_info{ + .dlpi_addr = entry.l_addr, + .dlpi_name = entry.l_name, + .dlpi_phdr = @intToPtr([*]elf.Phdr, dlpi_phdr), + .dlpi_phnum = dlpi_phnum, + }; + + last_r = callback(&info, @sizeOf(dl_phdr_info), data); + if (last_r != 0) break; + } + + return last_r; +} + test "import" { if (builtin.os == builtin.Os.linux) { _ = @import("linux/test.zig"); diff --git a/std/os/linux/arm64.zig b/std/os/linux/arm64.zig index ac2c18ee5f..4ae55765fb 100644 --- a/std/os/linux/arm64.zig +++ b/std/os/linux/arm64.zig @@ -2,6 +2,7 @@ const std = @import("../../std.zig"); const linux = std.os.linux; const socklen_t = linux.socklen_t; const iovec = linux.iovec; +const iovec_const = linux.iovec_const; pub const SYS_io_setup = 0; pub const SYS_io_destroy = 1; @@ -415,12 +416,24 @@ pub fn syscall6( pub extern fn clone(func: extern fn (arg: usize) u8, stack: usize, flags: u32, arg: usize, ptid: *i32, tls: usize, ctid: *i32) usize; pub const msghdr = extern struct { - msg_name: *u8, + msg_name: ?*sockaddr, msg_namelen: socklen_t, - msg_iov: *iovec, + msg_iov: [*]iovec, msg_iovlen: i32, __pad1: i32, - msg_control: *u8, + msg_control: ?*c_void, + msg_controllen: socklen_t, + __pad2: socklen_t, + msg_flags: i32, +}; + +pub const msghdr_const = extern struct { + msg_name: ?*const sockaddr, + msg_namelen: socklen_t, + msg_iov: [*]iovec_const, + msg_iovlen: i32, + __pad1: i32, + msg_control: ?*c_void, msg_controllen: socklen_t, __pad2: socklen_t, msg_flags: i32, diff --git a/std/os/linux/test.zig b/std/os/linux/test.zig index 286748b70c..1949d00298 100644 --- a/std/os/linux/test.zig +++ b/std/os/linux/test.zig @@ -1,6 +1,8 @@ const std = @import("../../std.zig"); const builtin = @import("builtin"); const linux = std.os.linux; +const mem = std.mem; +const elf = std.elf; const expect = std.testing.expect; test "getpid" { @@ -42,3 +44,42 @@ test "timer" { // TODO implicit cast from *[N]T to [*]T err = linux.epoll_wait(@intCast(i32, epoll_fd), @ptrCast([*]linux.epoll_event, &events), 8, -1); } + +export fn iter_fn(info: *linux.dl_phdr_info, size: usize, data: ?*usize) i32 { + var counter = data.?; + // Count how many libraries are loaded + counter.* += usize(1); + + // The image should contain at least a PT_LOAD segment + if (info.dlpi_phnum < 1) return -1; + + // Quick & dirty validation of the phdr pointers, make sure we're not + // pointing to some random gibberish + var i: usize = 0; + var found_load = false; + while (i < info.dlpi_phnum) : (i += 1) { + const phdr = info.dlpi_phdr[i]; + + if (phdr.p_type != elf.PT_LOAD) continue; + + // Find the ELF header + const elf_header = @intToPtr(*elf.Ehdr, phdr.p_vaddr - phdr.p_offset); + // Validate the magic + if (!mem.eql(u8, elf_header.e_ident[0..], "\x7fELF")) return -1; + // Consistency check + if (elf_header.e_phnum != info.dlpi_phnum) return -1; + + found_load = true; + break; + } + + if (!found_load) return -1; + + return 42; +} + +test "dl_iterate_phdr" { + var counter: usize = 0; + expect(linux.dl_iterate_phdr(usize, iter_fn, &counter) != 0); + expect(counter != 0); +} diff --git a/std/os/linux/tls.zig b/std/os/linux/tls.zig new file mode 100644 index 0000000000..9c8d816f05 --- /dev/null +++ b/std/os/linux/tls.zig @@ -0,0 +1,247 @@ +const std = @import("std"); +const mem = std.mem; +const posix = std.os.posix; +const elf = std.elf; +const builtin = @import("builtin"); +const assert = std.debug.assert; + +// This file implements the two TLS variants [1] used by ELF-based systems. +// +// The variant I has the following layout in memory: +// ------------------------------------------------------- +// | DTV | Zig | DTV | Alignment | TLS | +// | storage | thread data | pointer | | block | +// ------------------------^------------------------------ +// `-- The thread pointer register points here +// +// In this case we allocate additional space for our control structure that's +// placed _before_ the DTV pointer together with the DTV. +// +// NOTE: Some systems such as power64 or mips use this variant with a twist: the +// alignment is not present and the tp and DTV addresses are offset by a +// constant. +// +// On the other hand the variant II has the following layout in memory: +// --------------------------------------- +// | TLS | TCB | Zig | DTV | +// | block | | thread data | storage | +// --------^------------------------------ +// `-- The thread pointer register points here +// +// The structure of the TCB is not defined by the ABI so we reserve enough space +// for a single pointer as some architectures such as i386 and x86_64 need a +// pointer to the TCB block itself at the address pointed by the tp. +// +// In this case the control structure and DTV are placed one after another right +// after the TLS block data. +// +// At the moment the DTV is very simple since we only support static TLS, all we +// need is a two word vector to hold the number of entries (1) and the address +// of the first TLS block. +// +// [1] https://www.akkadia.org/drepper/tls.pdf + +const TLSVariant = enum { + VariantI, + VariantII, +}; + +const tls_variant = switch (builtin.arch) { + .arm, .armeb, .aarch64, .aarch64_be => TLSVariant.VariantI, + .x86_64, .i386 => TLSVariant.VariantII, + else => @compileError("undefined tls_variant for this architecture"), +}; + +// Controls how many bytes are reserved for the Thread Control Block +const tls_tcb_size = switch (builtin.arch) { + // ARM EABI mandates enough space for two pointers: the first one points to + // the DTV while the second one is unspecified but reserved + .arm, .armeb, .aarch64, .aarch64_be => 2 * @sizeOf(usize), + .i386, .x86_64 => @sizeOf(usize), + else => 0, +}; + +// Controls if the TCB should be aligned according to the TLS segment p_align +const tls_tcb_align_size = switch (builtin.arch) { + .arm, .armeb, .aarch64, .aarch64_be => true, + else => false, +}; + +// Check if the architecture-specific parameters look correct +comptime { + if (tls_tcb_align_size and tls_variant != TLSVariant.VariantI) { + @compileError("tls_tcb_align_size is only meaningful for variant I TLS"); + } +} + +// Some architectures add some offset to the tp and dtv addresses in order to +// make the generated code more efficient + +const tls_tp_offset = switch (builtin.arch) { + else => 0, +}; + +const tls_dtv_offset = switch (builtin.arch) { + else => 0, +}; + +// Per-thread storage for Zig's use +const CustomData = packed struct { +}; + +// Dynamic Thread Vector +const DTV = packed struct { + entries: usize, + tls_block: [1]usize, +}; + +// Holds all the information about the process TLS image +const TLSImage = struct { + data_src: []u8, + alloc_size: usize, + tcb_offset: usize, + dtv_offset: usize, + data_offset: usize, +}; + +pub var tls_image: ?TLSImage = null; + +pub fn setThreadPointer(addr: usize) void { + switch (builtin.arch) { + .x86_64 => { + const rc = std.os.linux.syscall2(std.os.linux.SYS_arch_prctl, + std.os.linux.ARCH_SET_FS, addr); + assert(rc == 0); + }, + .aarch64 => { + asm volatile ( + \\ msr tpidr_el0, %[addr] + : : [addr] "r" (addr) + ); + }, + else => @compileError("Unsupported architecture"), + } +} + +pub fn initTLS() void { + var tls_phdr: ?*elf.Phdr = null; + var img_base: usize = 0; + + const auxv = std.os.linux_elf_aux_maybe.?; + var at_phent: usize = undefined; + var at_phnum: usize = undefined; + var at_phdr: usize = undefined; + + var i: usize = 0; + while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) { + switch (auxv[i].a_type) { + elf.AT_PHENT => at_phent = auxv[i].a_un.a_val, + elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val, + elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val, + else => continue, + } + } + + // Sanity check + assert(at_phent == @sizeOf(elf.Phdr)); + + // Search the TLS section + const phdrs = (@intToPtr([*]elf.Phdr, at_phdr))[0..at_phnum]; + + for (phdrs) |*phdr| { + switch (phdr.p_type) { + elf.PT_PHDR => img_base = at_phdr - phdr.p_vaddr, + elf.PT_TLS => tls_phdr = phdr, + else => continue, + } + } + + if (tls_phdr) |phdr| { + // Offsets into the allocated TLS area + var tcb_offset: usize = undefined; + var dtv_offset: usize = undefined; + var data_offset: usize = undefined; + var thread_data_offset: usize = undefined; + // Compute the total size of the ABI-specific data plus our own control + // structures + const alloc_size = switch (tls_variant) { + .VariantI => blk: { + var l: usize = 0; + dtv_offset = l; + l += @sizeOf(DTV); + thread_data_offset = l; + l += @sizeOf(CustomData); + l = mem.alignForward(l, phdr.p_align); + tcb_offset = l; + if (tls_tcb_align_size) { + l += mem.alignForward(tls_tcb_size, phdr.p_align); + } else { + l += tls_tcb_size; + } + data_offset = l; + l += phdr.p_memsz; + break :blk l; + }, + .VariantII => blk: { + var l: usize = 0; + data_offset = l; + l += phdr.p_memsz; + l = mem.alignForward(l, phdr.p_align); + tcb_offset = l; + l += tls_tcb_size; + thread_data_offset = l; + l += @sizeOf(CustomData); + dtv_offset = l; + l += @sizeOf(DTV); + break :blk l; + } + }; + + tls_image = TLSImage{ + .data_src = @intToPtr([*]u8, phdr.p_vaddr + img_base)[0..phdr.p_filesz], + .alloc_size = alloc_size, + .tcb_offset = tcb_offset, + .dtv_offset = dtv_offset, + .data_offset = data_offset, + }; + } +} + +pub fn copyTLS(addr: usize) usize { + const tls_img = tls_image.?; + + // Be paranoid, clear the area we're going to use + @memset(@intToPtr([*]u8, addr), 0, tls_img.alloc_size); + // Prepare the DTV + const dtv = @intToPtr(*DTV, addr + tls_img.dtv_offset); + dtv.entries = 1; + dtv.tls_block[0] = addr + tls_img.data_offset + tls_dtv_offset; + // Set-up the TCB + const tcb_ptr = @intToPtr(*usize, addr + tls_img.tcb_offset); + if (tls_variant == TLSVariant.VariantI) { + tcb_ptr.* = addr + tls_img.dtv_offset; + } else { + tcb_ptr.* = addr + tls_img.tcb_offset; + } + // Copy the data + @memcpy(@intToPtr([*]u8, addr + tls_img.data_offset), tls_img.data_src.ptr, tls_img.data_src.len); + + // Return the corrected (if needed) value for the tp register + return addr + tls_img.tcb_offset + tls_tp_offset; +} + +var main_thread_tls_buffer: [256]u8 align(32) = undefined; + +pub fn allocateTLS(size: usize) usize { + // Small TLS allocation, use our local buffer + if (size < main_thread_tls_buffer.len) { + return @ptrToInt(&main_thread_tls_buffer); + } + + const addr = posix.mmap(null, size, posix.PROT_READ | posix.PROT_WRITE, + posix.MAP_PRIVATE | posix.MAP_ANONYMOUS, -1, 0); + + if (posix.getErrno(addr) != 0) @panic("out of memory"); + + return addr; +} diff --git a/std/os/linux/x86_64.zig b/std/os/linux/x86_64.zig index d194cd4003..621783d572 100644 --- a/std/os/linux/x86_64.zig +++ b/std/os/linux/x86_64.zig @@ -1,7 +1,9 @@ const std = @import("../../std.zig"); const linux = std.os.linux; +const sockaddr = linux.sockaddr; const socklen_t = linux.socklen_t; const iovec = linux.iovec; +const iovec_const = linux.iovec_const; pub const SYS_read = 0; pub const SYS_write = 1; @@ -386,6 +388,11 @@ pub const VDSO_CGT_VER = "LINUX_2.6"; pub const VDSO_GETCPU_SYM = "__vdso_getcpu"; pub const VDSO_GETCPU_VER = "LINUX_2.6"; +pub const ARCH_SET_GS = 0x1001; +pub const ARCH_SET_FS = 0x1002; +pub const ARCH_GET_FS = 0x1003; +pub const ARCH_GET_GS = 0x1004; + pub fn syscall0(number: usize) usize { return asm volatile ("syscall" : [ret] "={rax}" (-> usize) @@ -483,12 +490,24 @@ pub nakedcc fn restore_rt() void { } pub const msghdr = extern struct { - msg_name: *u8, + msg_name: ?*sockaddr, + msg_namelen: socklen_t, + msg_iov: [*]iovec, + msg_iovlen: i32, + __pad1: i32, + msg_control: ?*c_void, + msg_controllen: socklen_t, + __pad2: socklen_t, + msg_flags: i32, +}; + +pub const msghdr_const = extern struct { + msg_name: ?*const sockaddr, msg_namelen: socklen_t, - msg_iov: *iovec, + msg_iov: [*]iovec_const, msg_iovlen: i32, __pad1: i32, - msg_control: *u8, + msg_control: ?*c_void, msg_controllen: socklen_t, __pad2: socklen_t, msg_flags: i32, diff --git a/std/os/time.zig b/std/os/time.zig index 66ceedb1b6..abb6412843 100644 --- a/std/os/time.zig +++ b/std/os/time.zig @@ -3,41 +3,44 @@ const builtin = @import("builtin"); const Os = builtin.Os; const debug = std.debug; const testing = std.testing; +const math = std.math; const windows = std.os.windows; const linux = std.os.linux; const darwin = std.os.darwin; +const wasi = std.os.wasi; const posix = std.os.posix; pub const epoch = @import("epoch.zig"); -/// Sleep for the specified duration +/// Spurious wakeups are possible and no precision of timing is guaranteed. pub fn sleep(nanoseconds: u64) void { switch (builtin.os) { Os.linux, Os.macosx, Os.ios, Os.freebsd, Os.netbsd => { const s = nanoseconds / ns_per_s; const ns = nanoseconds % ns_per_s; - posixSleep(@intCast(u63, s), @intCast(u63, ns)); + posixSleep(s, ns); }, Os.windows => { const ns_per_ms = ns_per_s / ms_per_s; const milliseconds = nanoseconds / ns_per_ms; - windows.Sleep(@intCast(windows.DWORD, milliseconds)); + const ms_that_will_fit = std.math.cast(windows.DWORD, milliseconds) catch std.math.maxInt(windows.DWORD); + windows.Sleep(ms_that_will_fit); }, else => @compileError("Unsupported OS"), } } -pub fn posixSleep(seconds: u63, nanoseconds: u63) void { +/// Spurious wakeups are possible and no precision of timing is guaranteed. +pub fn posixSleep(seconds: u64, nanoseconds: u64) void { var req = posix.timespec{ - .tv_sec = seconds, - .tv_nsec = nanoseconds, + .tv_sec = std.math.cast(isize, seconds) catch std.math.maxInt(isize), + .tv_nsec = std.math.cast(isize, nanoseconds) catch std.math.maxInt(isize), }; var rem: posix.timespec = undefined; while (true) { const ret_val = posix.nanosleep(&req, &rem); const err = posix.getErrno(ret_val); - if (err == 0) return; switch (err) { posix.EFAULT => unreachable, posix.EINVAL => { @@ -49,6 +52,7 @@ pub fn posixSleep(seconds: u63, nanoseconds: u63) void { req = rem; continue; }, + // This prong handles success as well as unexpected errors. else => return, } } @@ -64,9 +68,21 @@ pub const milliTimestamp = switch (builtin.os) { Os.windows => milliTimestampWindows, Os.linux, Os.freebsd, Os.netbsd => milliTimestampPosix, Os.macosx, Os.ios => milliTimestampDarwin, + Os.wasi => milliTimestampWasi, else => @compileError("Unsupported OS"), }; +fn milliTimestampWasi() u64 { + var ns: wasi.timestamp_t = undefined; + + // TODO: Verify that precision is ignored + const err = wasi.clock_time_get(wasi.CLOCK_REALTIME, 1, &ns); + debug.assert(err == wasi.ESUCCESS); + + const ns_per_ms = 1000; + return @divFloor(ns, ns_per_ms); +} + fn milliTimestampWindows() u64 { //FileTime has a granularity of 100 nanoseconds // and uses the NTFS/Windows epoch diff --git a/std/os/wasi.zig b/std/os/wasi.zig new file mode 100644 index 0000000000..2118db9a2a --- /dev/null +++ b/std/os/wasi.zig @@ -0,0 +1,42 @@ +pub use @import("wasi/core.zig"); + +pub const STDIN_FILENO = 0; +pub const STDOUT_FILENO = 1; +pub const STDERR_FILENO = 2; + +pub fn getErrno(r: usize) usize { + const signed_r = @bitCast(isize, r); + return if (signed_r > -4096 and signed_r < 0) @intCast(usize, -signed_r) else 0; +} + +pub fn write(fd: i32, buf: [*]const u8, count: usize) usize { + var nwritten: usize = undefined; + + const ciovs = ciovec_t{ + .buf = buf, + .buf_len = count, + }; + + const err = fd_write(@bitCast(fd_t, isize(fd)), &ciovs, 1, &nwritten); + if (err == ESUCCESS) { + return nwritten; + } else { + return @bitCast(usize, -isize(err)); + } +} + +pub fn read(fd: i32, buf: [*]u8, nbyte: usize) usize { + var nread: usize = undefined; + + const iovs = iovec_t{ + .buf = buf, + .buf_len = nbyte, + }; + + const err = fd_read(@bitCast(fd_t, isize(fd)), &iovs, 1, &nread); + if (err == ESUCCESS) { + return nread; + } else { + return @bitCast(usize, -isize(err)); + } +} diff --git a/std/os/wasi/core.zig b/std/os/wasi/core.zig new file mode 100644 index 0000000000..f2bef73be9 --- /dev/null +++ b/std/os/wasi/core.zig @@ -0,0 +1,374 @@ +// Based on https://github.com/CraneStation/wasi-sysroot/blob/wasi/libc-bottom-half/headers/public/wasi/core.h +// and https://github.com/WebAssembly/WASI/blob/master/design/WASI-core.md + +pub const advice_t = u8; +pub const ADVICE_NORMAL: advice_t = 0; +pub const ADVICE_SEQUENTIAL: advice_t = 1; +pub const ADVICE_RANDOM: advice_t = 2; +pub const ADVICE_WILLNEED: advice_t = 3; +pub const ADVICE_DONTNEED: advice_t = 4; +pub const ADVICE_NOREUSE: advice_t = 5; + +pub const ciovec_t = extern struct { + buf: [*]const u8, + buf_len: usize, +}; + +pub const clockid_t = u32; +pub const CLOCK_REALTIME: clockid_t = 0; +pub const CLOCK_MONOTONIC: clockid_t = 1; +pub const CLOCK_PROCESS_CPUTIME_ID: clockid_t = 2; +pub const CLOCK_THREAD_CPUTIME_ID: clockid_t = 3; + +pub const device_t = u64; + +pub const dircookie_t = u64; +pub const DIRCOOKIE_START: dircookie_t = 0; + +pub const dirent_t = extern struct { + d_next: dircookie_t, + d_ino: inode_t, + d_namlen: u32, + d_type: filetype_t, +}; + +pub const errno_t = u16; +pub const ESUCCESS: errno_t = 0; +pub const E2BIG: errno_t = 1; +pub const EACCES: errno_t = 2; +pub const EADDRINUSE: errno_t = 3; +pub const EADDRNOTAVAIL: errno_t = 4; +pub const EAFNOSUPPORT: errno_t = 5; +pub const EAGAIN: errno_t = 6; +pub const EALREADY: errno_t = 7; +pub const EBADF: errno_t = 8; +pub const EBADMSG: errno_t = 9; +pub const EBUSY: errno_t = 10; +pub const ECANCELED: errno_t = 11; +pub const ECHILD: errno_t = 12; +pub const ECONNABORTED: errno_t = 13; +pub const ECONNREFUSED: errno_t = 14; +pub const ECONNRESET: errno_t = 15; +pub const EDEADLK: errno_t = 16; +pub const EDESTADDRREQ: errno_t = 17; +pub const EDOM: errno_t = 18; +pub const EDQUOT: errno_t = 19; +pub const EEXIST: errno_t = 20; +pub const EFAULT: errno_t = 21; +pub const EFBIG: errno_t = 22; +pub const EHOSTUNREACH: errno_t = 23; +pub const EIDRM: errno_t = 24; +pub const EILSEQ: errno_t = 25; +pub const EINPROGRESS: errno_t = 26; +pub const EINTR: errno_t = 27; +pub const EINVAL: errno_t = 28; +pub const EIO: errno_t = 29; +pub const EISCONN: errno_t = 30; +pub const EISDIR: errno_t = 31; +pub const ELOOP: errno_t = 32; +pub const EMFILE: errno_t = 33; +pub const EMLINK: errno_t = 34; +pub const EMSGSIZE: errno_t = 35; +pub const EMULTIHOP: errno_t = 36; +pub const ENAMETOOLONG: errno_t = 37; +pub const ENETDOWN: errno_t = 38; +pub const ENETRESET: errno_t = 39; +pub const ENETUNREACH: errno_t = 40; +pub const ENFILE: errno_t = 41; +pub const ENOBUFS: errno_t = 42; +pub const ENODEV: errno_t = 43; +pub const ENOENT: errno_t = 44; +pub const ENOEXEC: errno_t = 45; +pub const ENOLCK: errno_t = 46; +pub const ENOLINK: errno_t = 47; +pub const ENOMEM: errno_t = 48; +pub const ENOMSG: errno_t = 49; +pub const ENOPROTOOPT: errno_t = 50; +pub const ENOSPC: errno_t = 51; +pub const ENOSYS: errno_t = 52; +pub const ENOTCONN: errno_t = 53; +pub const ENOTDIR: errno_t = 54; +pub const ENOTEMPTY: errno_t = 55; +pub const ENOTRECOVERABLE: errno_t = 56; +pub const ENOTSOCK: errno_t = 57; +pub const ENOTSUP: errno_t = 58; +pub const ENOTTY: errno_t = 59; +pub const ENXIO: errno_t = 60; +pub const EOVERFLOW: errno_t = 61; +pub const EOWNERDEAD: errno_t = 62; +pub const EPERM: errno_t = 63; +pub const EPIPE: errno_t = 64; +pub const EPROTO: errno_t = 65; +pub const EPROTONOSUPPORT: errno_t = 66; +pub const EPROTOTYPE: errno_t = 67; +pub const ERANGE: errno_t = 68; +pub const EROFS: errno_t = 69; +pub const ESPIPE: errno_t = 70; +pub const ESRCH: errno_t = 71; +pub const ESTALE: errno_t = 72; +pub const ETIMEDOUT: errno_t = 73; +pub const ETXTBSY: errno_t = 74; +pub const EXDEV: errno_t = 75; +pub const ENOTCAPABLE: errno_t = 76; + +pub const event_t = extern struct { + userdata: userdata_t, + @"error": errno_t, + @"type": eventtype_t, + u: extern union { + fd_readwrite: extern struct { + nbytes: filesize_t, + flags: eventrwflags_t, + }, + }, +}; + +pub const eventrwflags_t = u16; +pub const EVENT_FD_READWRITE_HANGUP: eventrwflags_t = 0x0001; + +pub const eventtype_t = u8; +pub const EVENTTYPE_CLOCK: eventtype_t = 0; +pub const EVENTTYPE_FD_READ: eventtype_t = 1; +pub const EVENTTYPE_FD_WRITE: eventtype_t = 2; + +pub const exitcode_t = u32; + +pub const fd_t = u32; + +pub const fdflags_t = u16; +pub const FDFLAG_APPEND: fdflags_t = 0x0001; +pub const FDFLAG_DSYNC: fdflags_t = 0x0002; +pub const FDFLAG_NONBLOCK: fdflags_t = 0x0004; +pub const FDFLAG_RSYNC: fdflags_t = 0x0008; +pub const FDFLAG_SYNC: fdflags_t = 0x0010; + +const fdstat_t = extern struct { + fs_filetype: filetype_t, + fs_flags: fdflags_t, + fs_rights_base: rights_t, + fs_rights_inheriting: rights_t, +}; + +pub const filedelta_t = i64; + +pub const filesize_t = u64; + +pub const filestat_t = extern struct { + st_dev: device_t, + st_ino: inode_t, + st_filetype: filetype_t, + st_nlink: linkcount_t, + st_size: filesize_t, + st_atim: timestamp_t, + st_mtim: timestamp_t, + st_ctim: timestamp_t, +}; + +pub const filetype_t = u8; +pub const FILETYPE_UNKNOWN: filetype_t = 0; +pub const FILETYPE_BLOCK_DEVICE: filetype_t = 1; +pub const FILETYPE_CHARACTER_DEVICE: filetype_t = 2; +pub const FILETYPE_DIRECTORY: filetype_t = 3; +pub const FILETYPE_REGULAR_FILE: filetype_t = 4; +pub const FILETYPE_SOCKET_DGRAM: filetype_t = 5; +pub const FILETYPE_SOCKET_STREAM: filetype_t = 6; +pub const FILETYPE_SYMBOLIC_LINK: filetype_t = 7; + +pub const fstflags_t = u16; +pub const FILESTAT_SET_ATIM: fstflags_t = 0x0001; +pub const FILESTAT_SET_ATIM_NOW: fstflags_t = 0x0002; +pub const FILESTAT_SET_MTIM: fstflags_t = 0x0004; +pub const FILESTAT_SET_MTIM_NOW: fstflags_t = 0x0008; + +pub const inode_t = u64; + +pub const iovec_t = extern struct { + buf: [*]u8, + buf_len: usize, +}; + +pub const linkcount_t = u32; + +pub const lookupflags_t = u32; +pub const LOOKUP_SYMLINK_FOLLOW: lookupflags_t = 0x00000001; + +pub const oflags_t = u16; +pub const O_CREAT: oflags_t = 0x0001; +pub const O_DIRECTORY: oflags_t = 0x0002; +pub const O_EXCL: oflags_t = 0x0004; +pub const O_TRUNC: oflags_t = 0x0008; + +pub const preopentype_t = u8; +pub const PREOPENTYPE_DIR: preopentype_t = 0; + +pub const prestat_t = extern struct { + pr_type: preopentype_t, + u: extern union { + dir: extern struct { + pr_name_len: usize, + }, + }, +}; + +pub const riflags_t = u16; +pub const SOCK_RECV_PEEK: riflags_t = 0x0001; +pub const SOCK_RECV_WAITALL: riflags_t = 0x0002; + +pub const rights_t = u64; +pub const RIGHT_FD_DATASYNC: rights_t = 0x0000000000000001; +pub const RIGHT_FD_READ: rights_t = 0x0000000000000002; +pub const RIGHT_FD_SEEK: rights_t = 0x0000000000000004; +pub const RIGHT_FD_FDSTAT_SET_FLAGS: rights_t = 0x0000000000000008; +pub const RIGHT_FD_SYNC: rights_t = 0x0000000000000010; +pub const RIGHT_FD_TELL: rights_t = 0x0000000000000020; +pub const RIGHT_FD_WRITE: rights_t = 0x0000000000000040; +pub const RIGHT_FD_ADVISE: rights_t = 0x0000000000000080; +pub const RIGHT_FD_ALLOCATE: rights_t = 0x0000000000000100; +pub const RIGHT_PATH_CREATE_DIRECTORY: rights_t = 0x0000000000000200; +pub const RIGHT_PATH_CREATE_FILE: rights_t = 0x0000000000000400; +pub const RIGHT_PATH_LINK_SOURCE: rights_t = 0x0000000000000800; +pub const RIGHT_PATH_LINK_TARGET: rights_t = 0x0000000000001000; +pub const RIGHT_PATH_OPEN: rights_t = 0x0000000000002000; +pub const RIGHT_FD_READDIR: rights_t = 0x0000000000004000; +pub const RIGHT_PATH_READLINK: rights_t = 0x0000000000008000; +pub const RIGHT_PATH_RENAME_SOURCE: rights_t = 0x0000000000010000; +pub const RIGHT_PATH_RENAME_TARGET: rights_t = 0x0000000000020000; +pub const RIGHT_PATH_FILESTAT_GET: rights_t = 0x0000000000040000; +pub const RIGHT_PATH_FILESTAT_SET_SIZE: rights_t = 0x0000000000080000; +pub const RIGHT_PATH_FILESTAT_SET_TIMES: rights_t = 0x0000000000100000; +pub const RIGHT_FD_FILESTAT_GET: rights_t = 0x0000000000200000; +pub const RIGHT_FD_FILESTAT_SET_SIZE: rights_t = 0x0000000000400000; +pub const RIGHT_FD_FILESTAT_SET_TIMES: rights_t = 0x0000000000800000; +pub const RIGHT_PATH_SYMLINK: rights_t = 0x0000000001000000; +pub const RIGHT_PATH_REMOVE_DIRECTORY: rights_t = 0x0000000002000000; +pub const RIGHT_PATH_UNLINK_FILE: rights_t = 0x0000000004000000; +pub const RIGHT_POLL_FD_READWRITE: rights_t = 0x0000000008000000; +pub const RIGHT_SOCK_SHUTDOWN: rights_t = 0x0000000010000000; + +pub const roflags_t = u16; +pub const SOCK_RECV_DATA_TRUNCATED: roflags_t = 0x0001; + +pub const sdflags_t = u8; +pub const SHUT_RD: sdflags_t = 0x01; +pub const SHUT_WR: sdflags_t = 0x02; + +pub const siflags_t = u16; + +pub const signal_t = u8; +pub const SIGHUP: signal_t = 1; +pub const SIGINT: signal_t = 2; +pub const SIGQUIT: signal_t = 3; +pub const SIGILL: signal_t = 4; +pub const SIGTRAP: signal_t = 5; +pub const SIGABRT: signal_t = 6; +pub const SIGBUS: signal_t = 7; +pub const SIGFPE: signal_t = 8; +pub const SIGKILL: signal_t = 9; +pub const SIGUSR1: signal_t = 10; +pub const SIGSEGV: signal_t = 11; +pub const SIGUSR2: signal_t = 12; +pub const SIGPIPE: signal_t = 13; +pub const SIGALRM: signal_t = 14; +pub const SIGTERM: signal_t = 15; +pub const SIGCHLD: signal_t = 16; +pub const SIGCONT: signal_t = 17; +pub const SIGSTOP: signal_t = 18; +pub const SIGTSTP: signal_t = 19; +pub const SIGTTIN: signal_t = 20; +pub const SIGTTOU: signal_t = 21; +pub const SIGURG: signal_t = 22; +pub const SIGXCPU: signal_t = 23; +pub const SIGXFSZ: signal_t = 24; +pub const SIGVTALRM: signal_t = 25; +pub const SIGPROF: signal_t = 26; +pub const SIGWINCH: signal_t = 27; +pub const SIGPOLL: signal_t = 28; +pub const SIGPWR: signal_t = 29; +pub const SIGSYS: signal_t = 30; + +pub const subclockflags_t = u16; +pub const SUBSCRIPTION_CLOCK_ABSTIME: subclockflags_t = 0x0001; + +pub const subscription_t = extern struct { + userdata: userdata_t, + @"type": eventtype_t, + u: extern union { + clock: extern struct { + identifier: userdata_t, + clock_id: clockid_t, + timeout: timestamp_t, + precision: timestamp_t, + flags: subclockflags_t, + }, + fd_readwrite: extern struct { + fd: fd_t, + }, + }, +}; + +pub const timestamp_t = u64; + +pub const userdata_t = u64; + +pub const whence_t = u8; +pub const WHENCE_CUR: whence_t = 0; +pub const WHENCE_END: whence_t = 1; +pub const WHENCE_SET: whence_t = 2; + +pub extern "wasi_unstable" fn args_get(argv: [*][*]u8, argv_buf: [*]u8) errno_t; +pub extern "wasi_unstable" fn args_sizes_get(argc: *usize, argv_buf_size: *usize) errno_t; + +pub extern "wasi_unstable" fn clock_res_get(clock_id: clockid_t, resolution: *timestamp_t) errno_t; +pub extern "wasi_unstable" fn clock_time_get(clock_id: clockid_t, precision: timestamp_t, timestamp: *timestamp_t) errno_t; + +pub extern "wasi_unstable" fn environ_get(environ: [*]?[*]u8, environ_buf: [*]u8) errno_t; +pub extern "wasi_unstable" fn environ_sizes_get(environ_count: *usize, environ_buf_size: *usize) errno_t; + +pub extern "wasi_unstable" fn fd_advise(fd: fd_t, offset: filesize_t, len: filesize_t, advice: advice_t) errno_t; +pub extern "wasi_unstable" fn fd_allocate(fd: fd_t, offset: filesize_t, len: filesize_t) errno_t; +pub extern "wasi_unstable" fn fd_close(fd: fd_t) errno_t; +pub extern "wasi_unstable" fn fd_datasync(fd: fd_t) errno_t; +pub extern "wasi_unstable" fn fd_pread(fd: fd_t, iovs: *const iovec_t, iovs_len: usize, offset: filesize_t, nread: *usize) errno_t; +pub extern "wasi_unstable" fn fd_pwrite(fd: fd_t, iovs: *const ciovec_t, iovs_len: usize, offset: filesize_t, nwritten: *usize) errno_t; +pub extern "wasi_unstable" fn fd_read(fd: fd_t, iovs: *const iovec_t, iovs_len: usize, nread: *usize) errno_t; +pub extern "wasi_unstable" fn fd_readdir(fd: fd_t, buf: [*]u8, buf_len: usize, cookie: dircookie_t, bufused: *usize) errno_t; +pub extern "wasi_unstable" fn fd_renumber(from: fd_t, to: fd_t) errno_t; +pub extern "wasi_unstable" fn fd_seek(fd: fd_t, offset: filedelta_t, whence: whence_t, newoffset: *filesize_t) errno_t; +pub extern "wasi_unstable" fn fd_sync(fd: fd_t) errno_t; +pub extern "wasi_unstable" fn fd_tell(fd: fd_t, newoffset: *filesize_t) errno_t; +pub extern "wasi_unstable" fn fd_write(fd: fd_t, iovs: *const ciovec_t, iovs_len: usize, nwritten: *usize) errno_t; + +pub extern "wasi_unstable" fn fd_fdstat_get(fd: fd_t, buf: *fdstat_t) errno_t; +pub extern "wasi_unstable" fn fd_fdstat_set_flags(fd: fd_t, flags: fdflags_t) errno_t; +pub extern "wasi_unstable" fn fd_fdstat_set_rights(fd: fd_t, fs_rights_base: rights_t, fs_rights_inheriting: rights_t) errno_t; + +pub extern "wasi_unstable" fn fd_filestat_get(fd: fd_t, buf: *filestat_t) errno_t; +pub extern "wasi_unstable" fn fd_filestat_set_size(fd: fd_t, st_size: filesize_t) errno_t; +pub extern "wasi_unstable" fn fd_filestat_set_times(fd: fd_t, st_atim: timestamp_t, st_mtim: timestamp_t, fstflags: fstflags_t) errno_t; + +pub extern "wasi_unstable" fn fd_prestat_get(fd: fd_t, buf: *prestat_t) errno_t; +pub extern "wasi_unstable" fn fd_prestat_dir_name(fd: fd_t, path: [*]u8, path_len: usize) errno_t; + +pub extern "wasi_unstable" fn path_create_directory(fd: fd_t, path: [*]const u8, path_len: usize) errno_t; +pub extern "wasi_unstable" fn path_filestat_get(fd: fd_t, flags: lookupflags_t, path: [*]const u8, path_len: usize, buf: *filestat_t) errno_t; +pub extern "wasi_unstable" fn path_filestat_set_times(fd: fd_t, flags: lookupflags_t, path: [*]const u8, path_len: usize, st_atim: timestamp_t, st_mtim: timestamp_t, fstflags: fstflags_t) errno_t; +pub extern "wasi_unstable" fn path_link(old_fd: fd_t, old_flags: lookupflags_t, old_path: [*]const u8, old_path_len: usize, new_fd: fd_t, new_path: [*]const u8, new_path_len: usize) errno_t; +pub extern "wasi_unstable" fn path_open(dirfd: fd_t, dirflags: lookupflags_t, path: [*]const u8, path_len: usize, oflags: oflags_t, fs_rights_base: rights_t, fs_rights_inheriting: rights_t, fs_flags: fdflags_t, fd: *fd_t) errno_t; +pub extern "wasi_unstable" fn path_readlink(fd: fd_t, path: [*]const u8, path_len: usize, buf: [*]u8, buf_len: usize, bufused: *usize) errno_t; +pub extern "wasi_unstable" fn path_remove_directory(fd: fd_t, path: [*]const u8, path_len: usize) errno_t; +pub extern "wasi_unstable" fn path_rename(old_fd: fd_t, old_path: [*]const u8, old_path_len: usize, new_fd: fd_t, new_path: [*]const u8, new_path_len: usize) errno_t; +pub extern "wasi_unstable" fn path_symlink(old_path: [*]const u8, old_path_len: usize, fd: fd_t, new_path: [*]const u8, new_path_len: usize) errno_t; +pub extern "wasi_unstable" fn path_unlink_file(fd: fd_t, path: [*]const u8, path_len: usize) errno_t; + +pub extern "wasi_unstable" fn poll_oneoff(in: *const subscription_t, out: *event_t, nsubscriptions: usize, nevents: *usize) errno_t; + +pub extern "wasi_unstable" fn proc_exit(rval: exitcode_t) noreturn; +pub extern "wasi_unstable" fn proc_raise(sig: signal_t) errno_t; + +pub extern "wasi_unstable" fn random_get(buf: [*]u8, buf_len: usize) errno_t; + +pub extern "wasi_unstable" fn sched_yield() errno_t; + +pub extern "wasi_unstable" fn sock_recv(sock: fd_t, ri_data: *const iovec_t, ri_data_len: usize, ri_flags: riflags_t, ro_datalen: *usize, ro_flags: *roflags_t) errno_t; +pub extern "wasi_unstable" fn sock_send(sock: fd_t, si_data: *const ciovec_t, si_data_len: usize, si_flags: siflags_t, so_datalen: *usize) errno_t; +pub extern "wasi_unstable" fn sock_shutdown(sock: fd_t, how: sdflags_t) errno_t; diff --git a/std/os/windows.zig b/std/os/windows.zig index 96274632ce..e134d87eae 100644 --- a/std/os/windows.zig +++ b/std/os/windows.zig @@ -239,6 +239,37 @@ pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000; pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004; pub const HEAP_NO_SERIALIZE = 0x00000001; +// AllocationType values +pub const MEM_COMMIT = 0x1000; +pub const MEM_RESERVE = 0x2000; +pub const MEM_RESET = 0x80000; +pub const MEM_RESET_UNDO = 0x1000000; +pub const MEM_LARGE_PAGES = 0x20000000; +pub const MEM_PHYSICAL = 0x400000; +pub const MEM_TOP_DOWN = 0x100000; +pub const MEM_WRITE_WATCH = 0x200000; + +// Protect values +pub const PAGE_EXECUTE = 0x10; +pub const PAGE_EXECUTE_READ = 0x20; +pub const PAGE_EXECUTE_READWRITE = 0x40; +pub const PAGE_EXECUTE_WRITECOPY = 0x80; +pub const PAGE_NOACCESS = 0x01; +pub const PAGE_READONLY = 0x02; +pub const PAGE_READWRITE = 0x04; +pub const PAGE_WRITECOPY = 0x08; +pub const PAGE_TARGETS_INVALID = 0x40000000; +pub const PAGE_TARGETS_NO_UPDATE = 0x40000000; // Same as PAGE_TARGETS_INVALID +pub const PAGE_GUARD = 0x100; +pub const PAGE_NOCACHE = 0x200; +pub const PAGE_WRITECOMBINE = 0x400; + +// FreeType values +pub const MEM_COALESCE_PLACEHOLDERS = 0x1; +pub const MEM_RESERVE_PLACEHOLDERS = 0x2; +pub const MEM_DECOMMIT = 0x4000; +pub const MEM_RELEASE = 0x8000; + pub const PTHREAD_START_ROUTINE = extern fn (LPVOID) DWORD; pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE; diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig index 64a97ca87d..7e99f7eda9 100644 --- a/std/os/windows/kernel32.zig +++ b/std/os/windows/kernel32.zig @@ -116,6 +116,9 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL; +pub extern "kernel32" stdcallcc fn VirtualAlloc(lpAddress: ?LPVOID, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD) ?LPVOID; +pub extern "kernel32" stdcallcc fn VirtualFree(lpAddress: ?LPVOID, dwSize: SIZE_T, dwFreeType: DWORD) BOOL; + pub extern "kernel32" stdcallcc fn MoveFileExW( lpExistingFileName: [*]const u16, lpNewFileName: [*]const u16, diff --git a/std/packed_int_array.zig b/std/packed_int_array.zig new file mode 100644 index 0000000000..d4ed68c6ee --- /dev/null +++ b/std/packed_int_array.zig @@ -0,0 +1,649 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const debug = std.debug; +const testing = std.testing; + +pub fn PackedIntIo(comptime Int: type, comptime endian: builtin.Endian) type { + //The general technique employed here is to cast bytes in the array to a container + // integer (having bits % 8 == 0) large enough to contain the number of bits we want, + // then we can retrieve or store the new value with a relative minimum of masking + // and shifting. In this worst case, this means that we'll need an integer that's + // actually 1 byte larger than the minimum required to store the bits, because it + // is possible that the bits start at the end of the first byte, continue through + // zero or more, then end in the beginning of the last. But, if we try to access + // a value in the very last byte of memory with that integer size, that extra byte + // will be out of bounds. Depending on the circumstances of the memory, that might + // mean the OS fatally kills the program. Thus, we use a larger container (MaxIo) + // most of the time, but a smaller container (MinIo) when touching the last byte + // of the memory. + const int_bits = comptime std.meta.bitCount(Int); + + //in the best case, this is the number of bytes we need to touch + // to read or write a value, as bits + const min_io_bits = ((int_bits + 7) / 8) * 8; + + //in the worst case, this is the number of bytes we need to touch + // to read or write a value, as bits + const max_io_bits = switch (int_bits) { + 0 => 0, + 1 => 8, + 2...9 => 16, + 10...65535 => ((int_bits / 8) + 2) * 8, + else => unreachable, + }; + + //we bitcast the desired Int type to an unsigned version of itself + // to avoid issues with shifting signed ints. + const UnInt = @IntType(false, int_bits); + + //The maximum container int type + const MinIo = @IntType(false, min_io_bits); + + //The minimum container int type + const MaxIo = @IntType(false, max_io_bits); + + return struct { + pub fn get(bytes: []const u8, index: usize, bit_offset: u7) Int { + if (int_bits == 0) return 0; + + const bit_index = (index * int_bits) + bit_offset; + const max_end_byte = (bit_index + max_io_bits) / 8; + + //Using the larger container size will potentially read out of bounds + if (max_end_byte > bytes.len) return getBits(bytes, MinIo, bit_index); + return getBits(bytes, MaxIo, bit_index); + } + + fn getBits(bytes: []const u8, comptime Container: type, bit_index: usize) Int { + const container_bits = comptime std.meta.bitCount(Container); + const Shift = std.math.Log2Int(Container); + + const start_byte = bit_index / 8; + const head_keep_bits = bit_index - (start_byte * 8); + const tail_keep_bits = container_bits - (int_bits + head_keep_bits); + + //read bytes as container + const value_ptr = @ptrCast(*align(1) const Container, &bytes[start_byte]); + var value = value_ptr.*; + + if (endian != builtin.endian) value = @bswap(Container, value); + + switch (endian) { + .Big => { + value <<= @intCast(Shift, head_keep_bits); + value >>= @intCast(Shift, head_keep_bits); + value >>= @intCast(Shift, tail_keep_bits); + }, + .Little => { + value <<= @intCast(Shift, tail_keep_bits); + value >>= @intCast(Shift, tail_keep_bits); + value >>= @intCast(Shift, head_keep_bits); + }, + } + + return @bitCast(Int, @truncate(UnInt, value)); + } + + pub fn set(bytes: []u8, index: usize, bit_offset: u3, int: Int) void { + if (int_bits == 0) return; + + const bit_index = (index * int_bits) + bit_offset; + const max_end_byte = (bit_index + max_io_bits) / 8; + + //Using the larger container size will potentially write out of bounds + if (max_end_byte > bytes.len) return setBits(bytes, MinIo, bit_index, int); + setBits(bytes, MaxIo, bit_index, int); + } + + fn setBits(bytes: []u8, comptime Container: type, bit_index: usize, int: Int) void { + const container_bits = comptime std.meta.bitCount(Container); + const Shift = std.math.Log2Int(Container); + + const start_byte = bit_index / 8; + const head_keep_bits = bit_index - (start_byte * 8); + const tail_keep_bits = container_bits - (int_bits + head_keep_bits); + const keep_shift = switch (endian) { + .Big => @intCast(Shift, tail_keep_bits), + .Little => @intCast(Shift, head_keep_bits), + }; + + //position the bits where they need to be in the container + const value = @intCast(Container, @bitCast(UnInt, int)) << keep_shift; + + //read existing bytes + const target_ptr = @ptrCast(*align(1) Container, &bytes[start_byte]); + var target = target_ptr.*; + + if (endian != builtin.endian) target = @bswap(Container, target); + + //zero the bits we want to replace in the existing bytes + const inv_mask = @intCast(Container, std.math.maxInt(UnInt)) << keep_shift; + const mask = ~inv_mask; + target &= mask; + + //merge the new value + target |= value; + + if (endian != builtin.endian) target = @bswap(Container, target); + + //save it back + target_ptr.* = target; + } + + fn slice(bytes: []u8, bit_offset: u3, start: usize, end: usize) PackedIntSliceEndian(Int, endian) { + debug.assert(end >= start); + + const length = end - start; + const bit_index = (start * int_bits) + bit_offset; + const start_byte = bit_index / 8; + const end_byte = (bit_index + (length * int_bits) + 7) / 8; + const new_bytes = bytes[start_byte..end_byte]; + + if (length == 0) return PackedIntSliceEndian(Int, endian).init(new_bytes[0..0], 0); + + var new_slice = PackedIntSliceEndian(Int, endian).init(new_bytes, length); + new_slice.bit_offset = @intCast(u3, (bit_index - (start_byte * 8))); + return new_slice; + } + + fn sliceCast(bytes: []u8, comptime NewInt: type, comptime new_endian: builtin.Endian, bit_offset: u3, old_len: usize) PackedIntSliceEndian(NewInt, new_endian) { + const new_int_bits = comptime std.meta.bitCount(NewInt); + const New = PackedIntSliceEndian(NewInt, new_endian); + + const total_bits = (old_len * int_bits); + const new_int_count = total_bits / new_int_bits; + + debug.assert(total_bits == new_int_count * new_int_bits); + + var new = New.init(bytes, new_int_count); + new.bit_offset = bit_offset; + + return new; + } + }; +} + +///Creates a bit-packed array of integers of type Int. Bits +/// are packed using native endianess and without storing any meta +/// data. PackedIntArray(i3, 8) will occupy exactly 3 bytes of memory. +pub fn PackedIntArray(comptime Int: type, comptime int_count: usize) type { + return PackedIntArrayEndian(Int, builtin.endian, int_count); +} + +///Creates a bit-packed array of integers of type Int. Bits +/// are packed using specified endianess and without storing any meta +/// data. +pub fn PackedIntArrayEndian(comptime Int: type, comptime endian: builtin.Endian, comptime int_count: usize) type { + const int_bits = comptime std.meta.bitCount(Int); + const total_bits = int_bits * int_count; + const total_bytes = (total_bits + 7) / 8; + + const Io = PackedIntIo(Int, endian); + + return struct { + const Self = @This(); + + bytes: [total_bytes]u8, + + ///Returns the number of elements in the packed array + pub fn len(self: Self) usize { + return int_count; + } + + ///Initialize a packed array using an unpacked array + /// or, more likely, an array literal. + pub fn init(ints: [int_count]Int) Self { + var self = Self(undefined); + for (ints) |int, i| self.set(i, int); + return self; + } + + ///Return the Int stored at index + pub fn get(self: Self, index: usize) Int { + debug.assert(index < int_count); + return Io.get(self.bytes, index, 0); + } + + ///Copy int into the array at index + pub fn set(self: *Self, index: usize, int: Int) void { + debug.assert(index < int_count); + return Io.set(&self.bytes, index, 0, int); + } + + ///Create a PackedIntSlice of the array from given start to given end + pub fn slice(self: *Self, start: usize, end: usize) PackedIntSliceEndian(Int, endian) { + debug.assert(start < int_count); + debug.assert(end <= int_count); + return Io.slice(&self.bytes, 0, start, end); + } + + ///Create a PackedIntSlice of the array using NewInt as the bit width integer. + /// NewInt's bit width must fit evenly within the array's Int's total bits. + pub fn sliceCast(self: *Self, comptime NewInt: type) PackedIntSlice(NewInt) { + return self.sliceCastEndian(NewInt, endian); + } + + ///Create a PackedIntSlice of the array using NewInt as the bit width integer + /// and new_endian as the new endianess. NewInt's bit width must fit evenly within + /// the array's Int's total bits. + pub fn sliceCastEndian(self: *Self, comptime NewInt: type, comptime new_endian: builtin.Endian) PackedIntSliceEndian(NewInt, new_endian) { + return Io.sliceCast(&self.bytes, NewInt, new_endian, 0, int_count); + } + }; +} + +///Uses a slice as a bit-packed block of int_count integers of type Int. +/// Bits are packed using native endianess and without storing any meta +/// data. +pub fn PackedIntSlice(comptime Int: type) type { + return PackedIntSliceEndian(Int, builtin.endian); +} + +///Uses a slice as a bit-packed block of int_count integers of type Int. +/// Bits are packed using specified endianess and without storing any meta +/// data. +pub fn PackedIntSliceEndian(comptime Int: type, comptime endian: builtin.Endian) type { + const int_bits = comptime std.meta.bitCount(Int); + const Io = PackedIntIo(Int, endian); + + return struct { + const Self = @This(); + + bytes: []u8, + int_count: usize, + bit_offset: u3, + + ///Returns the number of elements in the packed slice + pub fn len(self: Self) usize { + return self.int_count; + } + + ///Calculates the number of bytes required to store a desired count + /// of Ints + pub fn bytesRequired(int_count: usize) usize { + const total_bits = int_bits * int_count; + const total_bytes = (total_bits + 7) / 8; + return total_bytes; + } + + ///Initialize a packed slice using the memory at bytes, with int_count + /// elements. bytes must be large enough to accomodate the requested + /// count. + pub fn init(bytes: []u8, int_count: usize) Self { + debug.assert(bytes.len >= bytesRequired(int_count)); + + return Self{ + .bytes = bytes, + .int_count = int_count, + .bit_offset = 0, + }; + } + + ///Return the Int stored at index + pub fn get(self: Self, index: usize) Int { + debug.assert(index < self.int_count); + return Io.get(self.bytes, index, self.bit_offset); + } + + ///Copy int into the array at index + pub fn set(self: *Self, index: usize, int: Int) void { + debug.assert(index < self.int_count); + return Io.set(self.bytes, index, self.bit_offset, int); + } + + ///Create a PackedIntSlice of this slice from given start to given end + pub fn slice(self: Self, start: usize, end: usize) PackedIntSliceEndian(Int, endian) { + debug.assert(start < self.int_count); + debug.assert(end <= self.int_count); + return Io.slice(self.bytes, self.bit_offset, start, end); + } + + ///Create a PackedIntSlice of this slice using NewInt as the bit width integer. + /// NewInt's bit width must fit evenly within this slice's Int's total bits. + pub fn sliceCast(self: Self, comptime NewInt: type) PackedIntSliceEndian(NewInt, endian) { + return self.sliceCastEndian(NewInt, endian); + } + + ///Create a PackedIntSlice of this slice using NewInt as the bit width integer + /// and new_endian as the new endianess. NewInt's bit width must fit evenly within + /// this slice's Int's total bits. + pub fn sliceCastEndian(self: Self, comptime NewInt: type, comptime new_endian: builtin.Endian) PackedIntSliceEndian(NewInt, new_endian) { + return Io.sliceCast(self.bytes, NewInt, new_endian, self.bit_offset, self.int_count); + } + }; +} + +test "PackedIntArray" { + @setEvalBranchQuota(10000); + const max_bits = 256; + const int_count = 19; + + comptime var bits = 0; + inline while (bits <= 256) : (bits += 1) { + //alternate unsigned and signed + const even = bits % 2 == 0; + const I = @IntType(even, bits); + + const PackedArray = PackedIntArray(I, int_count); + const expected_bytes = ((bits * int_count) + 7) / 8; + testing.expect(@sizeOf(PackedArray) == expected_bytes); + + var data = PackedArray(undefined); + + //write values, counting up + var i = usize(0); + var count = I(0); + while (i < data.len()) : (i += 1) { + data.set(i, count); + if (bits > 0) count +%= 1; + } + + //read and verify values + i = 0; + count = 0; + while (i < data.len()) : (i += 1) { + const val = data.get(i); + testing.expect(val == count); + if (bits > 0) count +%= 1; + } + } +} + +test "PackedIntArray init" { + const PackedArray = PackedIntArray(u3, 8); + var packed_array = PackedArray.init([]u3{ 0, 1, 2, 3, 4, 5, 6, 7 }); + var i = usize(0); + while (i < packed_array.len()) : (i += 1) testing.expect(packed_array.get(i) == i); +} + +test "PackedIntSlice" { + @setEvalBranchQuota(10000); + const max_bits = 256; + const int_count = 19; + const total_bits = max_bits * int_count; + const total_bytes = (total_bits + 7) / 8; + + var buffer: [total_bytes]u8 = undefined; + + comptime var bits = 0; + inline while (bits <= 256) : (bits += 1) { + //alternate unsigned and signed + const even = bits % 2 == 0; + const I = @IntType(even, bits); + const P = PackedIntSlice(I); + + var data = P.init(&buffer, int_count); + + //write values, counting up + var i = usize(0); + var count = I(0); + while (i < data.len()) : (i += 1) { + data.set(i, count); + if (bits > 0) count +%= 1; + } + + //read and verify values + i = 0; + count = 0; + while (i < data.len()) : (i += 1) { + const val = data.get(i); + testing.expect(val == count); + if (bits > 0) count +%= 1; + } + } +} + +test "PackedIntSlice of PackedInt(Array/Slice)" { + const max_bits = 16; + const int_count = 19; + + comptime var bits = 0; + inline while (bits <= max_bits) : (bits += 1) { + const Int = @IntType(false, bits); + + const PackedArray = PackedIntArray(Int, int_count); + var packed_array = PackedArray(undefined); + + const limit = (1 << bits); + + var i = usize(0); + while (i < packed_array.len()) : (i += 1) { + packed_array.set(i, @intCast(Int, i % limit)); + } + + //slice of array + var packed_slice = packed_array.slice(2, 5); + testing.expect(packed_slice.len() == 3); + const ps_bit_count = (bits * packed_slice.len()) + packed_slice.bit_offset; + const ps_expected_bytes = (ps_bit_count + 7) / 8; + testing.expect(packed_slice.bytes.len == ps_expected_bytes); + testing.expect(packed_slice.get(0) == 2 % limit); + testing.expect(packed_slice.get(1) == 3 % limit); + testing.expect(packed_slice.get(2) == 4 % limit); + packed_slice.set(1, 7 % limit); + testing.expect(packed_slice.get(1) == 7 % limit); + + //write through slice + testing.expect(packed_array.get(3) == 7 % limit); + + //slice of a slice + const packed_slice_two = packed_slice.slice(0, 3); + testing.expect(packed_slice_two.len() == 3); + const ps2_bit_count = (bits * packed_slice_two.len()) + packed_slice_two.bit_offset; + const ps2_expected_bytes = (ps2_bit_count + 7) / 8; + testing.expect(packed_slice_two.bytes.len == ps2_expected_bytes); + testing.expect(packed_slice_two.get(1) == 7 % limit); + testing.expect(packed_slice_two.get(2) == 4 % limit); + + //size one case + const packed_slice_three = packed_slice_two.slice(1, 2); + testing.expect(packed_slice_three.len() == 1); + const ps3_bit_count = (bits * packed_slice_three.len()) + packed_slice_three.bit_offset; + const ps3_expected_bytes = (ps3_bit_count + 7) / 8; + testing.expect(packed_slice_three.bytes.len == ps3_expected_bytes); + testing.expect(packed_slice_three.get(0) == 7 % limit); + + //empty slice case + const packed_slice_empty = packed_slice.slice(0, 0); + testing.expect(packed_slice_empty.len() == 0); + testing.expect(packed_slice_empty.bytes.len == 0); + + //slicing at byte boundaries + const packed_slice_edge = packed_array.slice(8, 16); + testing.expect(packed_slice_edge.len() == 8); + const pse_bit_count = (bits * packed_slice_edge.len()) + packed_slice_edge.bit_offset; + const pse_expected_bytes = (pse_bit_count + 7) / 8; + testing.expect(packed_slice_edge.bytes.len == pse_expected_bytes); + testing.expect(packed_slice_edge.bit_offset == 0); + } +} + +test "PackedIntSlice accumulating bit offsets" { + //bit_offset is u3, so standard debugging asserts should catch + // anything + { + const PackedArray = PackedIntArray(u3, 16); + var packed_array = PackedArray(undefined); + + var packed_slice = packed_array.slice(0, packed_array.len()); + var i = usize(0); + while (i < packed_array.len() - 1) : (i += 1) { + packed_slice = packed_slice.slice(1, packed_slice.len()); + } + } + { + const PackedArray = PackedIntArray(u11, 88); + var packed_array = PackedArray(undefined); + + var packed_slice = packed_array.slice(0, packed_array.len()); + var i = usize(0); + while (i < packed_array.len() - 1) : (i += 1) { + packed_slice = packed_slice.slice(1, packed_slice.len()); + } + } +} + +//@NOTE: As I do not have a big endian system to test this on, +// big endian values were not tested +test "PackedInt(Array/Slice) sliceCast" { + const PackedArray = PackedIntArray(u1, 16); + var packed_array = PackedArray.init([]u1{ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 }); + const packed_slice_cast_2 = packed_array.sliceCast(u2); + const packed_slice_cast_4 = packed_slice_cast_2.sliceCast(u4); + var packed_slice_cast_9 = packed_array.slice(0, (packed_array.len() / 9) * 9).sliceCast(u9); + const packed_slice_cast_3 = packed_slice_cast_9.sliceCast(u3); + + var i = usize(0); + while (i < packed_slice_cast_2.len()) : (i += 1) { + const val = switch (builtin.endian) { + .Big => 0b01, + .Little => 0b10, + }; + testing.expect(packed_slice_cast_2.get(i) == val); + } + i = 0; + while (i < packed_slice_cast_4.len()) : (i += 1) { + const val = switch (builtin.endian) { + .Big => 0b0101, + .Little => 0b1010, + }; + testing.expect(packed_slice_cast_4.get(i) == val); + } + i = 0; + while (i < packed_slice_cast_9.len()) : (i += 1) { + const val = 0b010101010; + testing.expect(packed_slice_cast_9.get(i) == val); + packed_slice_cast_9.set(i, 0b111000111); + } + i = 0; + while (i < packed_slice_cast_3.len()) : (i += 1) { + const val = switch (builtin.endian) { + .Big => if (i % 2 == 0) u3(0b111) else u3(0b000), + .Little => if (i % 2 == 0) u3(0b111) else u3(0b000), + }; + testing.expect(packed_slice_cast_3.get(i) == val); + } +} + +test "PackedInt(Array/Slice)Endian" { + { + const PackedArrayBe = PackedIntArrayEndian(u4, .Big, 8); + var packed_array_be = PackedArrayBe.init([]u4{ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + }); + testing.expect(packed_array_be.bytes[0] == 0b00000001); + testing.expect(packed_array_be.bytes[1] == 0b00100011); + + var i = usize(0); + while (i < packed_array_be.len()) : (i += 1) { + testing.expect(packed_array_be.get(i) == i); + } + + var packed_slice_le = packed_array_be.sliceCastEndian(u4, .Little); + i = 0; + while (i < packed_slice_le.len()) : (i += 1) { + const val = if (i % 2 == 0) i + 1 else i - 1; + testing.expect(packed_slice_le.get(i) == val); + } + + var packed_slice_le_shift = packed_array_be.slice(1, 5).sliceCastEndian(u4, .Little); + i = 0; + while (i < packed_slice_le_shift.len()) : (i += 1) { + const val = if (i % 2 == 0) i else i + 2; + testing.expect(packed_slice_le_shift.get(i) == val); + } + } + + { + const PackedArrayBe = PackedIntArrayEndian(u11, .Big, 8); + var packed_array_be = PackedArrayBe.init([]u11{ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + }); + testing.expect(packed_array_be.bytes[0] == 0b00000000); + testing.expect(packed_array_be.bytes[1] == 0b00000000); + testing.expect(packed_array_be.bytes[2] == 0b00000100); + testing.expect(packed_array_be.bytes[3] == 0b00000001); + testing.expect(packed_array_be.bytes[4] == 0b00000000); + + var i = usize(0); + while (i < packed_array_be.len()) : (i += 1) { + testing.expect(packed_array_be.get(i) == i); + } + + var packed_slice_le = packed_array_be.sliceCastEndian(u11, .Little); + testing.expect(packed_slice_le.get(0) == 0b00000000000); + testing.expect(packed_slice_le.get(1) == 0b00010000000); + testing.expect(packed_slice_le.get(2) == 0b00000000100); + testing.expect(packed_slice_le.get(3) == 0b00000000000); + testing.expect(packed_slice_le.get(4) == 0b00010000011); + testing.expect(packed_slice_le.get(5) == 0b00000000010); + testing.expect(packed_slice_le.get(6) == 0b10000010000); + testing.expect(packed_slice_le.get(7) == 0b00000111001); + + var packed_slice_le_shift = packed_array_be.slice(1, 5).sliceCastEndian(u11, .Little); + testing.expect(packed_slice_le_shift.get(0) == 0b00010000000); + testing.expect(packed_slice_le_shift.get(1) == 0b00000000100); + testing.expect(packed_slice_le_shift.get(2) == 0b00000000000); + testing.expect(packed_slice_le_shift.get(3) == 0b00010000011); + } +} + +//@NOTE: Need to manually update this list as more posix os's get +// added to DirectAllocator. Windows can be added too when DirectAllocator +// switches to VirtualAlloc. + +//These tests prove we aren't accidentally accessing memory past +// the end of the array/slice by placing it at the end of a page +// and reading the last element. The assumption is that the page +// after this one is not mapped and will cause a segfault if we +// don't account for the bounds. +test "PackedIntArray at end of available memory" { + switch (builtin.os) { + .linux, .macosx, .ios, .freebsd, .netbsd => {}, + else => return, + } + const PackedArray = PackedIntArray(u3, 8); + + const Padded = struct { + _: [std.os.page_size - @sizeOf(PackedArray)]u8, + p: PackedArray, + }; + + var da = std.heap.DirectAllocator.init(); + const allocator = &da.allocator; + + var pad = try allocator.create(Padded); + defer allocator.destroy(pad); + pad.p.set(7, std.math.maxInt(u3)); +} + +test "PackedIntSlice at end of available memory" { + switch (builtin.os) { + .linux, .macosx, .ios, .freebsd, .netbsd => {}, + else => return, + } + const PackedSlice = PackedIntSlice(u11); + + var da = std.heap.DirectAllocator.init(); + const allocator = &da.allocator; + + var page = try allocator.alloc(u8, std.os.page_size); + defer allocator.free(page); + + var p = PackedSlice.init(page[std.os.page_size - 2 ..], 1); + p.set(0, std.math.maxInt(u11)); +} diff --git a/std/pdb.zig b/std/pdb.zig index 2b02a84871..043be2bcf4 100644 --- a/std/pdb.zig +++ b/std/pdb.zig @@ -588,7 +588,7 @@ const SuperBlock = packed struct { const MsfStream = struct { in_file: os.File, - pos: usize, + pos: u64, blocks: []u32, block_size: u32, @@ -598,7 +598,7 @@ const MsfStream = struct { pub const Error = @typeOf(read).ReturnType.ErrorSet; pub const Stream = io.InStream(Error); - fn init(block_size: u32, block_count: u32, pos: usize, file: os.File, allocator: *mem.Allocator) !MsfStream { + fn init(block_size: u32, block_count: u32, pos: u64, file: os.File, allocator: *mem.Allocator) !MsfStream { var stream = MsfStream{ .in_file = file, .pos = 0, @@ -660,23 +660,24 @@ const MsfStream = struct { return size; } - fn seekForward(self: *MsfStream, len: usize) !void { + // XXX: The `len` parameter should be signed + fn seekForward(self: *MsfStream, len: u64) !void { self.pos += len; if (self.pos >= self.blocks.len * self.block_size) return error.EOF; } - fn seekTo(self: *MsfStream, len: usize) !void { + fn seekTo(self: *MsfStream, len: u64) !void { self.pos = len; if (self.pos >= self.blocks.len * self.block_size) return error.EOF; } - fn getSize(self: *const MsfStream) usize { + fn getSize(self: *const MsfStream) u64 { return self.blocks.len * self.block_size; } - fn getFilePos(self: MsfStream) usize { + fn getFilePos(self: MsfStream) u64 { const block_id = self.pos / self.block_size; const block = self.blocks[block_id]; const offset = self.pos % self.block_size; diff --git a/std/rand.zig b/std/rand.zig index a2fdfed6fd..4a6563f65a 100644 --- a/std/rand.zig +++ b/std/rand.zig @@ -768,10 +768,10 @@ pub const Isaac64 = struct { const x = self.m[base + m1]; self.a = mix +% self.m[base + m2]; - const y = self.a +% self.b +% self.m[(x >> 3) % self.m.len]; + const y = self.a +% self.b +% self.m[@intCast(usize, (x >> 3) % self.m.len)]; self.m[base + m1] = y; - self.b = x +% self.m[(y >> 11) % self.m.len]; + self.b = x +% self.m[@intCast(usize, (y >> 11) % self.m.len)]; self.r[self.r.len - 1 - base - m1] = self.b; } diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig index 32f913a5b0..e6505c836b 100644 --- a/std/special/bootstrap.zig +++ b/std/special/bootstrap.zig @@ -20,6 +20,10 @@ comptime { } nakedcc fn _start() noreturn { + if (builtin.os == builtin.Os.wasi) { + std.os.wasi.proc_exit(callMain()); + } + switch (builtin.arch) { builtin.Arch.x86_64 => { argc_ptr = asm ("lea (%%rsp), %[argc]" @@ -63,24 +67,19 @@ fn posixCallMainAndExit() noreturn { var envp_count: usize = 0; while (envp_optional[envp_count]) |_| : (envp_count += 1) {} const envp = @ptrCast([*][*]u8, envp_optional)[0..envp_count]; + if (builtin.os == builtin.Os.linux) { - // Scan auxiliary vector. + // Find the beginning of the auxiliary vector const auxv = @ptrCast([*]std.elf.Auxv, envp.ptr + envp_count + 1); std.os.linux_elf_aux_maybe = auxv; - var i: usize = 0; - var at_phdr: usize = 0; - var at_phnum: usize = 0; - var at_phent: usize = 0; - while (auxv[i].a_un.a_val != 0) : (i += 1) { - switch (auxv[i].a_type) { - std.elf.AT_PAGESZ => assert(auxv[i].a_un.a_val == std.os.page_size), - std.elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val, - std.elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val, - std.elf.AT_PHENT => at_phent = auxv[i].a_un.a_val, - else => {}, - } + // Initialize the TLS area + std.os.linux.tls.initTLS(); + + if (std.os.linux.tls.tls_image) |tls_img| { + const tls_addr = std.os.linux.tls.allocateTLS(tls_img.alloc_size); + const tp = std.os.linux.tls.copyTLS(tls_addr); + std.os.linux.tls.setThreadPointer(tp); } - if (!builtin.single_threaded) linuxInitializeThreadLocalStorage(at_phdr, at_phnum, at_phent); } std.os.posix.exit(callMainWithArgs(argc, argv, envp)); @@ -136,50 +135,3 @@ inline fn callMain() u8 { const main_thread_tls_align = 32; var main_thread_tls_bytes: [64]u8 align(main_thread_tls_align) = [1]u8{0} ** 64; - -fn linuxInitializeThreadLocalStorage(at_phdr: usize, at_phnum: usize, at_phent: usize) void { - var phdr_addr = at_phdr; - var n = at_phnum; - var base: usize = 0; - while (n != 0) : ({ - n -= 1; - phdr_addr += at_phent; - }) { - const phdr = @intToPtr(*std.elf.Phdr, phdr_addr); - // TODO look for PT_DYNAMIC when we have https://github.com/ziglang/zig/issues/1917 - switch (phdr.p_type) { - std.elf.PT_PHDR => base = at_phdr - phdr.p_vaddr, - std.elf.PT_TLS => std.os.linux_tls_phdr = phdr, - else => continue, - } - } - const tls_phdr = std.os.linux_tls_phdr orelse return; - std.os.linux_tls_img_src = @intToPtr([*]const u8, base + tls_phdr.p_vaddr); - const end_addr = @ptrToInt(&main_thread_tls_bytes) + tls_phdr.p_memsz; - const max_end_addr = @ptrToInt(&main_thread_tls_bytes) + main_thread_tls_bytes.len; - assert(max_end_addr >= end_addr + @sizeOf(usize)); // not enough preallocated Thread Local Storage - assert(main_thread_tls_align >= tls_phdr.p_align); // preallocated Thread Local Storage not aligned enough - @memcpy(&main_thread_tls_bytes, std.os.linux_tls_img_src, tls_phdr.p_filesz); - const end_ptr = @intToPtr(*usize, end_addr); - end_ptr.* = end_addr; - linuxSetThreadArea(end_addr); -} - -fn linuxSetThreadArea(addr: usize) void { - switch (builtin.arch) { - builtin.Arch.x86_64 => { - const ARCH_SET_FS = 0x1002; - const rc = std.os.linux.syscall2(std.os.linux.SYS_arch_prctl, ARCH_SET_FS, addr); - // acrh_prctl is documented to never fail - assert(rc == 0); - }, - builtin.Arch.aarch64 => { - asm volatile ( - \\ msr tpidr_el0,x0 - \\ mov w0,#0 - \\ ret - ); - }, - else => @compileError("Unsupported architecture"), - } -} diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index 56cfe3bcb5..dfc3838577 100644 --- a/std/special/build_runner.zig +++ b/std/special/build_runner.zig @@ -94,6 +94,16 @@ pub fn main() !void { return usageAndErr(&builder, false, try stderr_stream); }); builder.addSearchPrefix(search_prefix); + } else if (mem.eql(u8, arg, "--override-std-dir")) { + builder.override_std_dir = try unwrapArg(arg_it.next(allocator) orelse { + warn("Expected argument after --override-std-dir\n\n"); + return usageAndErr(&builder, false, try stderr_stream); + }); + } else if (mem.eql(u8, arg, "--override-lib-dir")) { + builder.override_lib_dir = try unwrapArg(arg_it.next(allocator) orelse { + warn("Expected argument after --override-lib-dir\n\n"); + return usageAndErr(&builder, false, try stderr_stream); + }); } else if (mem.eql(u8, arg, "--verbose-tokenize")) { builder.verbose_tokenize = true; } else if (mem.eql(u8, arg, "--verbose-ast")) { @@ -187,15 +197,17 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void { try out_stream.write( \\ \\Advanced Options: - \\ --build-file [file] Override path to build.zig - \\ --cache-dir [path] Override path to zig cache directory - \\ --verbose-tokenize Enable compiler debug output for tokenization - \\ --verbose-ast Enable compiler debug output for parsing into an AST - \\ --verbose-link Enable compiler debug output for linking - \\ --verbose-ir Enable compiler debug output for Zig IR - \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR - \\ --verbose-cimport Enable compiler debug output for C imports - \\ --verbose-cc Enable compiler debug output for C compilation + \\ --build-file [file] Override path to build.zig + \\ --cache-dir [path] Override path to zig cache directory + \\ --override-std-dir [arg] Override path to Zig standard library + \\ --override-lib-dir [arg] Override path to Zig lib directory + \\ --verbose-tokenize Enable compiler debug output for tokenization + \\ --verbose-ast Enable compiler debug output for parsing into an AST + \\ --verbose-link Enable compiler debug output for linking + \\ --verbose-ir Enable compiler debug output for Zig IR + \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR + \\ --verbose-cimport Enable compiler debug output for C imports + \\ --verbose-cc Enable compiler debug output for C compilation \\ ); } diff --git a/std/special/compiler_rt.zig b/std/special/compiler_rt.zig index e8d522eea1..b4df7aea5f 100644 --- a/std/special/compiler_rt.zig +++ b/std/special/compiler_rt.zig @@ -5,20 +5,47 @@ 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 (builtin.arch) { + .i386, .x86_64 => @export("__zig_probe_stack", @import("compiler_rt/stack_probe.zig").zig_probe_stack, linkage), + else => {}, + } + + @export("__lesf2", @import("compiler_rt/comparesf2.zig").__lesf2, linkage); + @export("__ledf2", @import("compiler_rt/comparedf2.zig").__ledf2, linkage); @export("__letf2", @import("compiler_rt/comparetf2.zig").__letf2, linkage); + + @export("__gesf2", @import("compiler_rt/comparesf2.zig").__gesf2, linkage); + @export("__gedf2", @import("compiler_rt/comparedf2.zig").__gedf2, linkage); @export("__getf2", @import("compiler_rt/comparetf2.zig").__getf2, linkage); if (!is_test) { // only create these aliases when not testing + @export("__cmpsf2", @import("compiler_rt/comparesf2.zig").__lesf2, linkage); + @export("__cmpdf2", @import("compiler_rt/comparedf2.zig").__ledf2, linkage); @export("__cmptf2", @import("compiler_rt/comparetf2.zig").__letf2, linkage); + + @export("__eqsf2", @import("compiler_rt/comparesf2.zig").__eqsf2, linkage); + @export("__eqdf2", @import("compiler_rt/comparedf2.zig").__eqdf2, linkage); @export("__eqtf2", @import("compiler_rt/comparetf2.zig").__letf2, linkage); + + @export("__ltsf2", @import("compiler_rt/comparesf2.zig").__ltsf2, linkage); + @export("__ltdf2", @import("compiler_rt/comparedf2.zig").__ltdf2, linkage); @export("__lttf2", @import("compiler_rt/comparetf2.zig").__letf2, linkage); + + @export("__nesf2", @import("compiler_rt/comparesf2.zig").__nesf2, linkage); + @export("__nedf2", @import("compiler_rt/comparedf2.zig").__nedf2, linkage); @export("__netf2", @import("compiler_rt/comparetf2.zig").__letf2, linkage); + + @export("__gtsf2", @import("compiler_rt/comparesf2.zig").__gtsf2, linkage); + @export("__gtdf2", @import("compiler_rt/comparedf2.zig").__gtdf2, linkage); @export("__gttf2", @import("compiler_rt/comparetf2.zig").__getf2, linkage); + @export("__gnu_h2f_ieee", @import("compiler_rt/extendXfYf2.zig").__extendhfsf2, linkage); @export("__gnu_f2h_ieee", @import("compiler_rt/truncXfYf2.zig").__truncsfhf2, linkage); } + @export("__unordsf2", @import("compiler_rt/comparesf2.zig").__unordsf2, linkage); + @export("__unorddf2", @import("compiler_rt/comparedf2.zig").__unorddf2, linkage); @export("__unordtf2", @import("compiler_rt/comparetf2.zig").__unordtf2, linkage); @export("__addsf3", @import("compiler_rt/addXf3.zig").__addsf3, linkage); @@ -35,6 +62,17 @@ comptime { @export("__divsf3", @import("compiler_rt/divsf3.zig").__divsf3, linkage); @export("__divdf3", @import("compiler_rt/divdf3.zig").__divdf3, linkage); + @export("__ashlti3", @import("compiler_rt/ashlti3.zig").__ashlti3, linkage); + @export("__lshrti3", @import("compiler_rt/lshrti3.zig").__lshrti3, linkage); + @export("__ashrti3", @import("compiler_rt/ashrti3.zig").__ashrti3, linkage); + + @export("__floatsidf", @import("compiler_rt/floatsiXf.zig").__floatsidf, linkage); + @export("__floatsisf", @import("compiler_rt/floatsiXf.zig").__floatsisf, linkage); + @export("__floatdidf", @import("compiler_rt/floatdidf.zig").__floatdidf, linkage); + @export("__floatsitf", @import("compiler_rt/floatsiXf.zig").__floatsitf, linkage); + @export("__floatunsidf", @import("compiler_rt/floatunsidf.zig").__floatunsidf, linkage); + @export("__floatundidf", @import("compiler_rt/floatundidf.zig").__floatundidf, linkage); + @export("__floattitf", @import("compiler_rt/floattitf.zig").__floattitf, linkage); @export("__floattidf", @import("compiler_rt/floattidf.zig").__floattidf, linkage); @export("__floattisf", @import("compiler_rt/floattisf.zig").__floattisf, linkage); @@ -55,6 +93,10 @@ comptime { @export("__trunctfdf2", @import("compiler_rt/truncXfYf2.zig").__trunctfdf2, linkage); @export("__trunctfsf2", @import("compiler_rt/truncXfYf2.zig").__trunctfsf2, linkage); + @export("__truncdfsf2", @import("compiler_rt/truncXfYf2.zig").__truncdfsf2, linkage); + + @export("__extendsfdf2", @import("compiler_rt/extendXfYf2.zig").__extendsfdf2, linkage); + @export("__fixunssfsi", @import("compiler_rt/fixunssfsi.zig").__fixunssfsi, linkage); @export("__fixunssfdi", @import("compiler_rt/fixunssfdi.zig").__fixunssfdi, linkage); @export("__fixunssfti", @import("compiler_rt/fixunssfti.zig").__fixunssfti, linkage); @@ -80,18 +122,33 @@ comptime { @export("__udivmoddi4", @import("compiler_rt/udivmoddi4.zig").__udivmoddi4, linkage); @export("__popcountdi2", @import("compiler_rt/popcountdi2.zig").__popcountdi2, linkage); + @export("__divmoddi4", __divmoddi4, linkage); + @export("__divsi3", __divsi3, linkage); + @export("__divdi3", __divdi3, linkage); @export("__udivsi3", __udivsi3, linkage); @export("__udivdi3", __udivdi3, linkage); + @export("__modsi3", __modsi3, linkage); + @export("__moddi3", __moddi3, linkage); + @export("__umodsi3", __umodsi3, linkage); @export("__umoddi3", __umoddi3, linkage); + @export("__divmodsi4", __divmodsi4, linkage); @export("__udivmodsi4", __udivmodsi4, linkage); @export("__negsf2", @import("compiler_rt/negXf2.zig").__negsf2, linkage); @export("__negdf2", @import("compiler_rt/negXf2.zig").__negdf2, linkage); if (is_arm_arch and !is_arm_64) { + @export("__aeabi_unwind_cpp_pr0", __aeabi_unwind_cpp_pr0, strong_linkage); + @export("__aeabi_unwind_cpp_pr1", __aeabi_unwind_cpp_pr1, linkage); + @export("__aeabi_unwind_cpp_pr2", __aeabi_unwind_cpp_pr2, linkage); + + @export("__aeabi_ldivmod", __aeabi_ldivmod, linkage); @export("__aeabi_uldivmod", __aeabi_uldivmod, linkage); - @export("__aeabi_uidivmod", __aeabi_uidivmod, linkage); + + @export("__aeabi_idiv", __divsi3, linkage); + @export("__aeabi_idivmod", __aeabi_idivmod, linkage); @export("__aeabi_uidiv", __udivsi3, linkage); + @export("__aeabi_uidivmod", __aeabi_uidivmod, linkage); @export("__aeabi_memcpy", __aeabi_memcpy, linkage); @export("__aeabi_memcpy4", __aeabi_memcpy, linkage); @@ -113,6 +170,12 @@ comptime { @export("__aeabi_memcmp4", __aeabi_memcmp, linkage); @export("__aeabi_memcmp8", __aeabi_memcmp, linkage); + @export("__aeabi_f2d", @import("compiler_rt/extendXfYf2.zig").__extendsfdf2, linkage); + @export("__aeabi_i2d", @import("compiler_rt/floatsiXf.zig").__floatsidf, linkage); + @export("__aeabi_l2d", @import("compiler_rt/floatdidf.zig").__floatdidf, linkage); + @export("__aeabi_ui2d", @import("compiler_rt/floatunsidf.zig").__floatunsidf, linkage); + @export("__aeabi_ul2d", @import("compiler_rt/floatundidf.zig").__floatundidf, linkage); + @export("__aeabi_fneg", @import("compiler_rt/negXf2.zig").__negsf2, linkage); @export("__aeabi_dneg", @import("compiler_rt/negXf2.zig").__negdf2, linkage); @@ -132,6 +195,9 @@ comptime { @export("__aeabi_h2f", @import("compiler_rt/extendXfYf2.zig").__extendhfsf2, linkage); @export("__aeabi_f2h", @import("compiler_rt/truncXfYf2.zig").__truncsfhf2, linkage); + @export("__aeabi_i2f", @import("compiler_rt/floatsiXf.zig").__floatsisf, linkage); + @export("__aeabi_d2f", @import("compiler_rt/truncXfYf2.zig").__truncdfsf2, linkage); + @export("__aeabi_fadd", @import("compiler_rt/addXf3.zig").__addsf3, linkage); @export("__aeabi_dadd", @import("compiler_rt/addXf3.zig").__adddf3, linkage); @export("__aeabi_fsub", @import("compiler_rt/addXf3.zig").__subsf3, linkage); @@ -144,26 +210,41 @@ comptime { @export("__aeabi_fdiv", @import("compiler_rt/divsf3.zig").__divsf3, linkage); @export("__aeabi_ddiv", @import("compiler_rt/divdf3.zig").__divdf3, linkage); + + @export("__aeabi_fcmpeq", @import("compiler_rt/arm/aeabi_fcmp.zig").__aeabi_fcmpeq, linkage); + @export("__aeabi_fcmplt", @import("compiler_rt/arm/aeabi_fcmp.zig").__aeabi_fcmplt, linkage); + @export("__aeabi_fcmple", @import("compiler_rt/arm/aeabi_fcmp.zig").__aeabi_fcmple, linkage); + @export("__aeabi_fcmpge", @import("compiler_rt/arm/aeabi_fcmp.zig").__aeabi_fcmpge, linkage); + @export("__aeabi_fcmpgt", @import("compiler_rt/arm/aeabi_fcmp.zig").__aeabi_fcmpgt, linkage); + @export("__aeabi_fcmpun", @import("compiler_rt/comparesf2.zig").__unordsf2, linkage); + + @export("__aeabi_dcmpeq", @import("compiler_rt/arm/aeabi_dcmp.zig").__aeabi_dcmpeq, linkage); + @export("__aeabi_dcmplt", @import("compiler_rt/arm/aeabi_dcmp.zig").__aeabi_dcmplt, linkage); + @export("__aeabi_dcmple", @import("compiler_rt/arm/aeabi_dcmp.zig").__aeabi_dcmple, linkage); + @export("__aeabi_dcmpge", @import("compiler_rt/arm/aeabi_dcmp.zig").__aeabi_dcmpge, linkage); + @export("__aeabi_dcmpgt", @import("compiler_rt/arm/aeabi_dcmp.zig").__aeabi_dcmpgt, linkage); + @export("__aeabi_dcmpun", @import("compiler_rt/comparedf2.zig").__unorddf2, linkage); } if (builtin.os == builtin.Os.windows) { + if (!builtin.link_libc) { + @export("_chkstk", @import("compiler_rt/stack_probe.zig")._chkstk, strong_linkage); + @export("__chkstk", @import("compiler_rt/stack_probe.zig").__chkstk, strong_linkage); + @export("___chkstk", @import("compiler_rt/stack_probe.zig").___chkstk, strong_linkage); + @export("__chkstk_ms", @import("compiler_rt/stack_probe.zig").__chkstk_ms, strong_linkage); + @export("___chkstk_ms", @import("compiler_rt/stack_probe.zig").___chkstk_ms, strong_linkage); + } + switch (builtin.arch) { builtin.Arch.i386 => { - if (!builtin.link_libc) { - @export("_chkstk", _chkstk, strong_linkage); - @export("__chkstk_ms", __chkstk_ms, linkage); - } @export("_aulldiv", @import("compiler_rt/aulldiv.zig")._aulldiv, strong_linkage); @export("_aullrem", @import("compiler_rt/aullrem.zig")._aullrem, strong_linkage); }, builtin.Arch.x86_64 => { - if (!builtin.link_libc) { - @export("__chkstk", __chkstk, strong_linkage); - @export("___chkstk_ms", ___chkstk_ms, linkage); - } + // The "ti" functions must use @Vector(2, u64) parameter types to adhere to the ABI + // that LLVM expects compiler-rt to have. @export("__divti3", @import("compiler_rt/divti3.zig").__divti3_windows_x86_64, linkage); @export("__modti3", @import("compiler_rt/modti3.zig").__modti3_windows_x86_64, linkage); @export("__multi3", @import("compiler_rt/multi3.zig").__multi3_windows_x86_64, linkage); - @export("__muloti4", @import("compiler_rt/muloti4.zig").__muloti4_windows_x86_64, linkage); @export("__udivti3", @import("compiler_rt/udivti3.zig").__udivti3_windows_x86_64, linkage); @export("__udivmodti4", @import("compiler_rt/udivmodti4.zig").__udivmodti4_windows_x86_64, linkage); @export("__umodti3", @import("compiler_rt/umodti3.zig").__umodti3_windows_x86_64, linkage); @@ -174,11 +255,12 @@ comptime { @export("__divti3", @import("compiler_rt/divti3.zig").__divti3, linkage); @export("__modti3", @import("compiler_rt/modti3.zig").__modti3, linkage); @export("__multi3", @import("compiler_rt/multi3.zig").__multi3, linkage); - @export("__muloti4", @import("compiler_rt/muloti4.zig").__muloti4, linkage); @export("__udivti3", @import("compiler_rt/udivti3.zig").__udivti3, linkage); @export("__udivmodti4", @import("compiler_rt/udivmodti4.zig").__udivmodti4, linkage); @export("__umodti3", @import("compiler_rt/umodti3.zig").__umodti3, linkage); } + @export("__muloti4", @import("compiler_rt/muloti4.zig").__muloti4, linkage); + @export("__mulodi4", @import("compiler_rt/mulodi4.zig").__mulodi4, linkage); } const std = @import("std"); @@ -198,15 +280,49 @@ pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn } } -pub fn setXmm0(comptime T: type, value: T) void { - comptime assert(builtin.arch == builtin.Arch.x86_64); - const aligned_value: T align(16) = value; - asm volatile ( - \\movaps (%[ptr]), %%xmm0 - : - : [ptr] "r" (&aligned_value) - : "xmm0" - ); +extern fn __aeabi_unwind_cpp_pr0() void { + unreachable; +} +extern fn __aeabi_unwind_cpp_pr1() void { + unreachable; +} +extern fn __aeabi_unwind_cpp_pr2() void { + unreachable; +} + +extern fn __divmoddi4(a: i64, b: i64, rem: *i64) i64 { + @setRuntimeSafety(is_test); + + const d = __divdi3(a, b); + rem.* = a -% (d *% b); + return d; +} + +extern fn __divdi3(a: i64, b: i64) i64 { + @setRuntimeSafety(is_test); + + // Set aside the sign of the quotient. + const sign = @bitCast(u64, (a ^ b) >> 63); + // Take absolute value of a and b via abs(x) = (x^(x >> 63)) - (x >> 63). + const abs_a = (a ^ (a >> 63)) -% (a >> 63); + const abs_b = (b ^ (b >> 63)) -% (b >> 63); + // Unsigned division + const res = __udivmoddi4(@bitCast(u64, abs_a), @bitCast(u64, abs_b), null); + // Apply sign of quotient to result and return. + return @bitCast(i64, (res ^ sign) -% sign); +} + +extern fn __moddi3(a: i64, b: i64) i64 { + @setRuntimeSafety(is_test); + + // Take absolute value of a and b via abs(x) = (x^(x >> 63)) - (x >> 63). + const abs_a = (a ^ (a >> 63)) -% (a >> 63); + const abs_b = (b ^ (b >> 63)) -% (b >> 63); + // Unsigned division + var r: u64 = undefined; + _ = __udivmoddi4(@bitCast(u64, abs_a), @bitCast(u64, abs_b), &r); + // Apply the sign of the dividend and return. + return (@bitCast(i64, r) ^ (a >> 63)) -% (a >> 63); } extern fn __udivdi3(a: u64, b: u64) u64 { @@ -222,14 +338,35 @@ extern fn __umoddi3(a: u64, b: u64) u64 { return r; } -const AeabiUlDivModResult = extern struct { - quot: u64, - rem: u64, -}; -extern fn __aeabi_uldivmod(numerator: u64, denominator: u64) AeabiUlDivModResult { +extern fn __aeabi_uidivmod(n: u32, d: u32) extern struct{q: u32, r: u32} { + @setRuntimeSafety(is_test); + + var result: @typeOf(__aeabi_uidivmod).ReturnType = undefined; + result.q = __udivmodsi4(n, d, &result.r); + return result; +} + +extern fn __aeabi_uldivmod(n: u64, d: u64) extern struct{q: u64, r: u64} { + @setRuntimeSafety(is_test); + + var result: @typeOf(__aeabi_uldivmod).ReturnType = undefined; + result.q = __udivmoddi4(n, d, &result.r); + return result; +} + +extern fn __aeabi_idivmod(n: i32, d: i32) extern struct{q: i32, r: i32} { + @setRuntimeSafety(is_test); + + var result: @typeOf(__aeabi_idivmod).ReturnType = undefined; + result.q = __divmodsi4(n, d, &result.r); + return result; +} + +extern fn __aeabi_ldivmod(n: i64, d: i64) extern struct{q: i64, r:i64} { @setRuntimeSafety(is_test); - var result: AeabiUlDivModResult = undefined; - result.quot = __udivmoddi4(numerator, denominator, &result.rem); + + var result: @typeOf(__aeabi_ldivmod).ReturnType = undefined; + result.q = __divmoddi4(n, d, &result.r); return result; } @@ -253,29 +390,74 @@ const is_arm_arch = switch (builtin.arch) { const is_arm_32 = is_arm_arch and !is_arm_64; -const use_thumb_1 = is_arm_32 and switch (builtin.arch.arm) { - builtin.Arch.Arm32.v6, - builtin.Arch.Arm32.v6m, - builtin.Arch.Arm32.v6k, - builtin.Arch.Arm32.v6t2, - => true, - else => false, -}; +const use_thumb_1 = usesThumb1(builtin.arch); + +fn usesThumb1(arch: builtin.Arch) bool { + return switch (arch) { + .arm => switch (arch.arm) { + .v6m => true, + else => false, + }, + .armeb => switch (arch.armeb) { + .v6m => true, + else => false, + }, + .thumb => switch (arch.thumb) { + .v5, + .v5te, + .v4t, + .v6, + .v6m, + .v6k, + => true, + else => false, + }, + .thumbeb => switch (arch.thumbeb) { + .v5, + .v5te, + .v4t, + .v6, + .v6m, + .v6k, + => true, + else => false, + }, + else => false, + }; +} -nakedcc fn __aeabi_uidivmod() void { - @setRuntimeSafety(false); - asm volatile ( - \\ push { lr } - \\ sub sp, sp, #4 - \\ mov r2, sp - \\ bl __udivmodsi4 - \\ ldr r1, [sp] - \\ add sp, sp, #4 - \\ pop { pc } - : - : - : "r2", "r1" - ); +test "usesThumb1" { + testing.expect(usesThumb1(builtin.Arch{ .arm = .v6m })); + testing.expect(!usesThumb1(builtin.Arch{ .arm = .v5 })); + //etc. + + testing.expect(usesThumb1(builtin.Arch{ .armeb = .v6m })); + testing.expect(!usesThumb1(builtin.Arch{ .armeb = .v5 })); + //etc. + + testing.expect(usesThumb1(builtin.Arch{ .thumb = .v5 })); + testing.expect(usesThumb1(builtin.Arch{ .thumb = .v5te })); + testing.expect(usesThumb1(builtin.Arch{ .thumb = .v4t })); + testing.expect(usesThumb1(builtin.Arch{ .thumb = .v6 })); + testing.expect(usesThumb1(builtin.Arch{ .thumb = .v6k })); + testing.expect(usesThumb1(builtin.Arch{ .thumb = .v6m })); + testing.expect(!usesThumb1(builtin.Arch{ .thumb = .v6t2 })); + //etc. + + testing.expect(usesThumb1(builtin.Arch{ .thumbeb = .v5 })); + testing.expect(usesThumb1(builtin.Arch{ .thumbeb = .v5te })); + testing.expect(usesThumb1(builtin.Arch{ .thumbeb = .v4t })); + testing.expect(usesThumb1(builtin.Arch{ .thumbeb = .v6 })); + testing.expect(usesThumb1(builtin.Arch{ .thumbeb = .v6k })); + testing.expect(usesThumb1(builtin.Arch{ .thumbeb = .v6m })); + testing.expect(!usesThumb1(builtin.Arch{ .thumbeb = .v6t2 })); + //etc. + + testing.expect(!usesThumb1(builtin.Arch{ .aarch64 = .v8 })); + testing.expect(!usesThumb1(builtin.Arch{ .aarch64_be = .v8 })); + testing.expect(!usesThumb1(builtin.Arch.x86_64)); + testing.expect(!usesThumb1(builtin.Arch.riscv32)); + //etc. } nakedcc fn __aeabi_memcpy() noreturn { @@ -368,107 +550,12 @@ nakedcc fn __aeabi_memcmp() noreturn { unreachable; } -// _chkstk (_alloca) routine - probe stack between %esp and (%esp-%eax) in 4k increments, -// then decrement %esp by %eax. Preserves all registers except %esp and flags. -// This routine is windows specific -// http://msdn.microsoft.com/en-us/library/ms648426.aspx -nakedcc fn _chkstk() align(4) void { - @setRuntimeSafety(false); - - asm volatile ( - \\ push %%ecx - \\ push %%eax - \\ cmp $0x1000,%%eax - \\ lea 12(%%esp),%%ecx - \\ jb 1f - \\ 2: - \\ sub $0x1000,%%ecx - \\ test %%ecx,(%%ecx) - \\ sub $0x1000,%%eax - \\ cmp $0x1000,%%eax - \\ ja 2b - \\ 1: - \\ sub %%eax,%%ecx - \\ test %%ecx,(%%ecx) - \\ pop %%eax - \\ pop %%ecx - \\ ret - ); -} - -nakedcc fn __chkstk() align(4) void { - @setRuntimeSafety(false); - - asm volatile ( - \\ push %%rcx - \\ push %%rax - \\ cmp $0x1000,%%rax - \\ lea 24(%%rsp),%%rcx - \\ jb 1f - \\2: - \\ sub $0x1000,%%rcx - \\ test %%rcx,(%%rcx) - \\ sub $0x1000,%%rax - \\ cmp $0x1000,%%rax - \\ ja 2b - \\1: - \\ sub %%rax,%%rcx - \\ test %%rcx,(%%rcx) - \\ pop %%rax - \\ pop %%rcx - \\ ret - ); -} - -// _chkstk routine -// This routine is windows specific -// http://msdn.microsoft.com/en-us/library/ms648426.aspx -nakedcc fn __chkstk_ms() align(4) void { - @setRuntimeSafety(false); - - asm volatile ( - \\ push %%ecx - \\ push %%eax - \\ cmp $0x1000,%%eax - \\ lea 12(%%esp),%%ecx - \\ jb 1f - \\ 2: - \\ sub $0x1000,%%ecx - \\ test %%ecx,(%%ecx) - \\ sub $0x1000,%%eax - \\ cmp $0x1000,%%eax - \\ ja 2b - \\ 1: - \\ sub %%eax,%%ecx - \\ test %%ecx,(%%ecx) - \\ pop %%eax - \\ pop %%ecx - \\ ret - ); -} - -nakedcc fn ___chkstk_ms() align(4) void { - @setRuntimeSafety(false); +extern fn __divmodsi4(a: i32, b: i32, rem: *i32) i32 { + @setRuntimeSafety(is_test); - asm volatile ( - \\ push %%rcx - \\ push %%rax - \\ cmp $0x1000,%%rax - \\ lea 24(%%rsp),%%rcx - \\ jb 1f - \\2: - \\ sub $0x1000,%%rcx - \\ test %%rcx,(%%rcx) - \\ sub $0x1000,%%rax - \\ cmp $0x1000,%%rax - \\ ja 2b - \\1: - \\ sub %%rax,%%rcx - \\ test %%rcx,(%%rcx) - \\ pop %%rax - \\ pop %%rcx - \\ ret - ); + const d = __divsi3(a, b); + rem.* = a -% (d * b); + return d; } extern fn __udivmodsi4(a: u32, b: u32, rem: *u32) u32 { @@ -479,6 +566,20 @@ extern fn __udivmodsi4(a: u32, b: u32, rem: *u32) u32 { return d; } +extern fn __divsi3(n: i32, d: i32) i32 { + @setRuntimeSafety(is_test); + + // Set aside the sign of the quotient. + const sign = @bitCast(u32, (n ^ d) >> 31); + // Take absolute value of a and b via abs(x) = (x^(x >> 31)) - (x >> 31). + const abs_n = (n ^ (n >> 31)) -% (n >> 31); + const abs_d = (d ^ (d >> 31)) -% (d >> 31); + // abs(a) / abs(b) + const res = @bitCast(u32, abs_n) / @bitCast(u32, abs_d); + // Apply sign of quotient to result and return. + return @bitCast(i32, (res ^ sign) -% sign); +} + extern fn __udivsi3(n: u32, d: u32) u32 { @setRuntimeSafety(is_test); @@ -520,6 +621,18 @@ extern fn __udivsi3(n: u32, d: u32) u32 { return q; } +extern fn __modsi3(n: i32, d: i32) i32 { + @setRuntimeSafety(is_test); + + return n -% __divsi3(n, d) *% d; +} + +extern fn __umodsi3(n: u32, d: u32) u32 { + @setRuntimeSafety(is_test); + + return n -% __udivsi3(n, d) *% d; +} + test "test_umoddi3" { test_one_umoddi3(0, 1, 0); test_one_umoddi3(2, 1, 0); @@ -1206,3 +1319,279 @@ fn test_one_udivsi3(a: u32, b: u32, expected_q: u32) void { const q: u32 = __udivsi3(a, b); testing.expect(q == expected_q); } + +test "test_divsi3" { + const cases = [][3]i32{ + []i32{ 0, 1, 0 }, + []i32{ 0, -1, 0 }, + []i32{ 2, 1, 2 }, + []i32{ 2, -1, -2 }, + []i32{ -2, 1, -2 }, + []i32{ -2, -1, 2 }, + + []i32{ @bitCast(i32, u32(0x80000000)), 1, @bitCast(i32, u32(0x80000000)) }, + []i32{ @bitCast(i32, u32(0x80000000)), -1, @bitCast(i32, u32(0x80000000)) }, + []i32{ @bitCast(i32, u32(0x80000000)), -2, 0x40000000 }, + []i32{ @bitCast(i32, u32(0x80000000)), 2, @bitCast(i32, u32(0xC0000000)) }, + }; + + for (cases) |case| { + test_one_divsi3(case[0], case[1], case[2]); + } +} + +fn test_one_divsi3(a: i32, b: i32, expected_q: i32) void { + const q: i32 = __divsi3(a, b); + testing.expect(q == expected_q); +} + +test "test_divmodsi4" { + const cases = [][4]i32{ + []i32{ 0, 1, 0, 0 }, + []i32{ 0, -1, 0, 0 }, + []i32{ 2, 1, 2, 0 }, + []i32{ 2, -1, -2, 0 }, + []i32{ -2, 1, -2, 0 }, + []i32{ -2, -1, 2, 0 }, + []i32{ 7, 5, 1, 2 }, + []i32{ -7, 5, -1, -2 }, + []i32{ 19, 5, 3, 4 }, + []i32{ 19, -5, -3, 4 }, + + []i32{ @bitCast(i32, u32(0x80000000)), 8, @bitCast(i32, u32(0xf0000000)), 0 }, + []i32{ @bitCast(i32, u32(0x80000007)), 8, @bitCast(i32, u32(0xf0000001)), -1 }, + }; + + for (cases) |case| { + test_one_divmodsi4(case[0], case[1], case[2], case[3]); + } +} + +fn test_one_divmodsi4(a: i32, b: i32, expected_q: i32, expected_r: i32) void { + var r: i32 = undefined; + const q: i32 = __divmodsi4(a, b, &r); + testing.expect(q == expected_q and r == expected_r); +} + +test "test_divdi3" { + const cases = [][3]i64{ + []i64{ 0, 1, 0 }, + []i64{ 0, -1, 0 }, + []i64{ 2, 1, 2 }, + []i64{ 2, -1, -2 }, + []i64{ -2, 1, -2 }, + []i64{ -2, -1, 2 }, + + []i64{ @bitCast(i64, u64(0x8000000000000000)), 1, @bitCast(i64, u64(0x8000000000000000)) }, + []i64{ @bitCast(i64, u64(0x8000000000000000)), -1, @bitCast(i64, u64(0x8000000000000000)) }, + []i64{ @bitCast(i64, u64(0x8000000000000000)), -2, 0x4000000000000000 }, + []i64{ @bitCast(i64, u64(0x8000000000000000)), 2, @bitCast(i64, u64(0xC000000000000000)) }, + }; + + for (cases) |case| { + test_one_divdi3(case[0], case[1], case[2]); + } +} + +fn test_one_divdi3(a: i64, b: i64, expected_q: i64) void { + const q: i64 = __divdi3(a, b); + testing.expect(q == expected_q); +} + +test "test_moddi3" { + const cases = [][3]i64{ + []i64{ 0, 1, 0 }, + []i64{ 0, -1, 0 }, + []i64{ 5, 3, 2 }, + []i64{ 5, -3, 2 }, + []i64{ -5, 3, -2 }, + []i64{ -5, -3, -2 }, + + []i64{ @bitCast(i64, @intCast(u64, 0x8000000000000000)), 1, 0 }, + []i64{ @bitCast(i64, @intCast(u64, 0x8000000000000000)), -1, 0 }, + []i64{ @bitCast(i64, @intCast(u64, 0x8000000000000000)), 2, 0 }, + []i64{ @bitCast(i64, @intCast(u64, 0x8000000000000000)), -2, 0 }, + []i64{ @bitCast(i64, @intCast(u64, 0x8000000000000000)), 3, -2 }, + []i64{ @bitCast(i64, @intCast(u64, 0x8000000000000000)), -3, -2 }, + }; + + for (cases) |case| { + test_one_moddi3(case[0], case[1], case[2]); + } +} + +fn test_one_moddi3(a: i64, b: i64, expected_r: i64) void { + const r: i64 = __moddi3(a, b); + testing.expect(r == expected_r); +} + +test "test_modsi3" { + const cases = [][3]i32{ + []i32{ 0, 1, 0 }, + []i32{ 0, -1, 0 }, + []i32{ 5, 3, 2 }, + []i32{ 5, -3, 2 }, + []i32{ -5, 3, -2 }, + []i32{ -5, -3, -2 }, + []i32{ @bitCast(i32, @intCast(u32, 0x80000000)), 1, 0x0 }, + []i32{ @bitCast(i32, @intCast(u32, 0x80000000)), 2, 0x0 }, + []i32{ @bitCast(i32, @intCast(u32, 0x80000000)), -2, 0x0 }, + []i32{ @bitCast(i32, @intCast(u32, 0x80000000)), 3, -2 }, + []i32{ @bitCast(i32, @intCast(u32, 0x80000000)), -3, -2 }, + }; + + for (cases) |case| { + test_one_modsi3(case[0], case[1], case[2]); + } +} + +fn test_one_modsi3(a: i32, b: i32, expected_r: i32) void { + const r: i32 = __modsi3(a, b); + testing.expect(r == expected_r); +} + +test "test_umodsi3" { + const cases = [][3]u32{ + []u32{ 0x00000000, 0x00000001, 0x00000000 }, + []u32{ 0x00000000, 0x00000002, 0x00000000 }, + []u32{ 0x00000000, 0x00000003, 0x00000000 }, + []u32{ 0x00000000, 0x00000010, 0x00000000 }, + []u32{ 0x00000000, 0x078644FA, 0x00000000 }, + []u32{ 0x00000000, 0x0747AE14, 0x00000000 }, + []u32{ 0x00000000, 0x7FFFFFFF, 0x00000000 }, + []u32{ 0x00000000, 0x80000000, 0x00000000 }, + []u32{ 0x00000000, 0xFFFFFFFD, 0x00000000 }, + []u32{ 0x00000000, 0xFFFFFFFE, 0x00000000 }, + []u32{ 0x00000000, 0xFFFFFFFF, 0x00000000 }, + []u32{ 0x00000001, 0x00000001, 0x00000000 }, + []u32{ 0x00000001, 0x00000002, 0x00000001 }, + []u32{ 0x00000001, 0x00000003, 0x00000001 }, + []u32{ 0x00000001, 0x00000010, 0x00000001 }, + []u32{ 0x00000001, 0x078644FA, 0x00000001 }, + []u32{ 0x00000001, 0x0747AE14, 0x00000001 }, + []u32{ 0x00000001, 0x7FFFFFFF, 0x00000001 }, + []u32{ 0x00000001, 0x80000000, 0x00000001 }, + []u32{ 0x00000001, 0xFFFFFFFD, 0x00000001 }, + []u32{ 0x00000001, 0xFFFFFFFE, 0x00000001 }, + []u32{ 0x00000001, 0xFFFFFFFF, 0x00000001 }, + []u32{ 0x00000002, 0x00000001, 0x00000000 }, + []u32{ 0x00000002, 0x00000002, 0x00000000 }, + []u32{ 0x00000002, 0x00000003, 0x00000002 }, + []u32{ 0x00000002, 0x00000010, 0x00000002 }, + []u32{ 0x00000002, 0x078644FA, 0x00000002 }, + []u32{ 0x00000002, 0x0747AE14, 0x00000002 }, + []u32{ 0x00000002, 0x7FFFFFFF, 0x00000002 }, + []u32{ 0x00000002, 0x80000000, 0x00000002 }, + []u32{ 0x00000002, 0xFFFFFFFD, 0x00000002 }, + []u32{ 0x00000002, 0xFFFFFFFE, 0x00000002 }, + []u32{ 0x00000002, 0xFFFFFFFF, 0x00000002 }, + []u32{ 0x00000003, 0x00000001, 0x00000000 }, + []u32{ 0x00000003, 0x00000002, 0x00000001 }, + []u32{ 0x00000003, 0x00000003, 0x00000000 }, + []u32{ 0x00000003, 0x00000010, 0x00000003 }, + []u32{ 0x00000003, 0x078644FA, 0x00000003 }, + []u32{ 0x00000003, 0x0747AE14, 0x00000003 }, + []u32{ 0x00000003, 0x7FFFFFFF, 0x00000003 }, + []u32{ 0x00000003, 0x80000000, 0x00000003 }, + []u32{ 0x00000003, 0xFFFFFFFD, 0x00000003 }, + []u32{ 0x00000003, 0xFFFFFFFE, 0x00000003 }, + []u32{ 0x00000003, 0xFFFFFFFF, 0x00000003 }, + []u32{ 0x00000010, 0x00000001, 0x00000000 }, + []u32{ 0x00000010, 0x00000002, 0x00000000 }, + []u32{ 0x00000010, 0x00000003, 0x00000001 }, + []u32{ 0x00000010, 0x00000010, 0x00000000 }, + []u32{ 0x00000010, 0x078644FA, 0x00000010 }, + []u32{ 0x00000010, 0x0747AE14, 0x00000010 }, + []u32{ 0x00000010, 0x7FFFFFFF, 0x00000010 }, + []u32{ 0x00000010, 0x80000000, 0x00000010 }, + []u32{ 0x00000010, 0xFFFFFFFD, 0x00000010 }, + []u32{ 0x00000010, 0xFFFFFFFE, 0x00000010 }, + []u32{ 0x00000010, 0xFFFFFFFF, 0x00000010 }, + []u32{ 0x078644FA, 0x00000001, 0x00000000 }, + []u32{ 0x078644FA, 0x00000002, 0x00000000 }, + []u32{ 0x078644FA, 0x00000003, 0x00000000 }, + []u32{ 0x078644FA, 0x00000010, 0x0000000A }, + []u32{ 0x078644FA, 0x078644FA, 0x00000000 }, + []u32{ 0x078644FA, 0x0747AE14, 0x003E96E6 }, + []u32{ 0x078644FA, 0x7FFFFFFF, 0x078644FA }, + []u32{ 0x078644FA, 0x80000000, 0x078644FA }, + []u32{ 0x078644FA, 0xFFFFFFFD, 0x078644FA }, + []u32{ 0x078644FA, 0xFFFFFFFE, 0x078644FA }, + []u32{ 0x078644FA, 0xFFFFFFFF, 0x078644FA }, + []u32{ 0x0747AE14, 0x00000001, 0x00000000 }, + []u32{ 0x0747AE14, 0x00000002, 0x00000000 }, + []u32{ 0x0747AE14, 0x00000003, 0x00000002 }, + []u32{ 0x0747AE14, 0x00000010, 0x00000004 }, + []u32{ 0x0747AE14, 0x078644FA, 0x0747AE14 }, + []u32{ 0x0747AE14, 0x0747AE14, 0x00000000 }, + []u32{ 0x0747AE14, 0x7FFFFFFF, 0x0747AE14 }, + []u32{ 0x0747AE14, 0x80000000, 0x0747AE14 }, + []u32{ 0x0747AE14, 0xFFFFFFFD, 0x0747AE14 }, + []u32{ 0x0747AE14, 0xFFFFFFFE, 0x0747AE14 }, + []u32{ 0x0747AE14, 0xFFFFFFFF, 0x0747AE14 }, + []u32{ 0x7FFFFFFF, 0x00000001, 0x00000000 }, + []u32{ 0x7FFFFFFF, 0x00000002, 0x00000001 }, + []u32{ 0x7FFFFFFF, 0x00000003, 0x00000001 }, + []u32{ 0x7FFFFFFF, 0x00000010, 0x0000000F }, + []u32{ 0x7FFFFFFF, 0x078644FA, 0x00156B65 }, + []u32{ 0x7FFFFFFF, 0x0747AE14, 0x043D70AB }, + []u32{ 0x7FFFFFFF, 0x7FFFFFFF, 0x00000000 }, + []u32{ 0x7FFFFFFF, 0x80000000, 0x7FFFFFFF }, + []u32{ 0x7FFFFFFF, 0xFFFFFFFD, 0x7FFFFFFF }, + []u32{ 0x7FFFFFFF, 0xFFFFFFFE, 0x7FFFFFFF }, + []u32{ 0x7FFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF }, + []u32{ 0x80000000, 0x00000001, 0x00000000 }, + []u32{ 0x80000000, 0x00000002, 0x00000000 }, + []u32{ 0x80000000, 0x00000003, 0x00000002 }, + []u32{ 0x80000000, 0x00000010, 0x00000000 }, + []u32{ 0x80000000, 0x078644FA, 0x00156B66 }, + []u32{ 0x80000000, 0x0747AE14, 0x043D70AC }, + []u32{ 0x80000000, 0x7FFFFFFF, 0x00000001 }, + []u32{ 0x80000000, 0x80000000, 0x00000000 }, + []u32{ 0x80000000, 0xFFFFFFFD, 0x80000000 }, + []u32{ 0x80000000, 0xFFFFFFFE, 0x80000000 }, + []u32{ 0x80000000, 0xFFFFFFFF, 0x80000000 }, + []u32{ 0xFFFFFFFD, 0x00000001, 0x00000000 }, + []u32{ 0xFFFFFFFD, 0x00000002, 0x00000001 }, + []u32{ 0xFFFFFFFD, 0x00000003, 0x00000001 }, + []u32{ 0xFFFFFFFD, 0x00000010, 0x0000000D }, + []u32{ 0xFFFFFFFD, 0x078644FA, 0x002AD6C9 }, + []u32{ 0xFFFFFFFD, 0x0747AE14, 0x01333341 }, + []u32{ 0xFFFFFFFD, 0x7FFFFFFF, 0x7FFFFFFE }, + []u32{ 0xFFFFFFFD, 0x80000000, 0x7FFFFFFD }, + []u32{ 0xFFFFFFFD, 0xFFFFFFFD, 0x00000000 }, + []u32{ 0xFFFFFFFD, 0xFFFFFFFE, 0xFFFFFFFD }, + []u32{ 0xFFFFFFFD, 0xFFFFFFFF, 0xFFFFFFFD }, + []u32{ 0xFFFFFFFE, 0x00000001, 0x00000000 }, + []u32{ 0xFFFFFFFE, 0x00000002, 0x00000000 }, + []u32{ 0xFFFFFFFE, 0x00000003, 0x00000002 }, + []u32{ 0xFFFFFFFE, 0x00000010, 0x0000000E }, + []u32{ 0xFFFFFFFE, 0x078644FA, 0x002AD6CA }, + []u32{ 0xFFFFFFFE, 0x0747AE14, 0x01333342 }, + []u32{ 0xFFFFFFFE, 0x7FFFFFFF, 0x00000000 }, + []u32{ 0xFFFFFFFE, 0x80000000, 0x7FFFFFFE }, + []u32{ 0xFFFFFFFE, 0xFFFFFFFD, 0x00000001 }, + []u32{ 0xFFFFFFFE, 0xFFFFFFFE, 0x00000000 }, + []u32{ 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFE }, + []u32{ 0xFFFFFFFF, 0x00000001, 0x00000000 }, + []u32{ 0xFFFFFFFF, 0x00000002, 0x00000001 }, + []u32{ 0xFFFFFFFF, 0x00000003, 0x00000000 }, + []u32{ 0xFFFFFFFF, 0x00000010, 0x0000000F }, + []u32{ 0xFFFFFFFF, 0x078644FA, 0x002AD6CB }, + []u32{ 0xFFFFFFFF, 0x0747AE14, 0x01333343 }, + []u32{ 0xFFFFFFFF, 0x7FFFFFFF, 0x00000001 }, + []u32{ 0xFFFFFFFF, 0x80000000, 0x7FFFFFFF }, + []u32{ 0xFFFFFFFF, 0xFFFFFFFD, 0x00000002 }, + []u32{ 0xFFFFFFFF, 0xFFFFFFFE, 0x00000001 }, + []u32{ 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000 }, + }; + + for (cases) |case| { + test_one_umodsi3(case[0], case[1], case[2]); + } +} + +fn test_one_umodsi3(a: u32, b: u32, expected_r: u32) void { + const r: u32 = __umodsi3(a, b); + testing.expect(r == expected_r); +} diff --git a/std/special/compiler_rt/addXf3.zig b/std/special/compiler_rt/addXf3.zig index 3a70461eca..5852f3e50d 100644 --- a/std/special/compiler_rt/addXf3.zig +++ b/std/special/compiler_rt/addXf3.zig @@ -78,8 +78,8 @@ fn addXf3(comptime T: type, a: T, b: T) T { const infRep = @bitCast(Z, std.math.inf(T)); // Detect if a or b is zero, infinity, or NaN. - if (aAbs - Z(1) >= infRep - Z(1) or - bAbs - Z(1) >= infRep - Z(1)) + if (aAbs -% Z(1) >= infRep - Z(1) or + bAbs -% Z(1) >= infRep - Z(1)) { // NaN + anything = qNaN if (aAbs > infRep) return @bitCast(T, @bitCast(Z, a) | quietBit); diff --git a/std/special/compiler_rt/arm/aeabi_dcmp.zig b/std/special/compiler_rt/arm/aeabi_dcmp.zig new file mode 100644 index 0000000000..a51d9854ce --- /dev/null +++ b/std/special/compiler_rt/arm/aeabi_dcmp.zig @@ -0,0 +1,108 @@ +// Ported from: +// +// https://github.com/llvm/llvm-project/commit/d674d96bc56c0f377879d01c9d8dfdaaa7859cdb/compiler-rt/lib/builtins/arm/aeabi_dcmp.S + +const compiler_rt_armhf_target = false; // TODO + +const ConditionalOperator = enum { + Eq, + Lt, + Le, + Ge, + Gt, +}; + +pub nakedcc fn __aeabi_dcmpeq() noreturn { + @setRuntimeSafety(false); + aeabi_dcmp(.Eq); + unreachable; +} + +pub nakedcc fn __aeabi_dcmplt() noreturn { + @setRuntimeSafety(false); + aeabi_dcmp(.Lt); + unreachable; +} + +pub nakedcc fn __aeabi_dcmple() noreturn { + @setRuntimeSafety(false); + aeabi_dcmp(.Le); + unreachable; +} + +pub nakedcc fn __aeabi_dcmpge() noreturn { + @setRuntimeSafety(false); + aeabi_dcmp(.Ge); + unreachable; +} + +pub nakedcc fn __aeabi_dcmpgt() noreturn { + @setRuntimeSafety(false); + aeabi_dcmp(.Gt); + unreachable; +} + +inline fn convert_dcmp_args_to_df2_args() void { + asm volatile ( + \\ vmov d0, r0, r1 + \\ vmov d1, r2, r3 + ); +} + +inline fn aeabi_dcmp(comptime cond: ConditionalOperator) void { + @setRuntimeSafety(false); + asm volatile ( + \\ push { r4, lr } + ); + + if (compiler_rt_armhf_target) { + convert_dcmp_args_to_df2_args(); + } + + switch (cond) { + .Eq => asm volatile ( + \\ bl __eqdf2 + \\ cmp r0, #0 + \\ beq 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + .Lt => asm volatile ( + \\ bl __ltdf2 + \\ cmp r0, #0 + \\ blt 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + .Le => asm volatile ( + \\ bl __ledf2 + \\ cmp r0, #0 + \\ ble 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + .Ge => asm volatile ( + \\ bl __ltdf2 + \\ cmp r0, #0 + \\ bge 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + .Gt => asm volatile ( + \\ bl __gtdf2 + \\ cmp r0, #0 + \\ bgt 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + } + asm volatile ( + \\ movs r0, #1 + \\ pop { r4, pc } + ); +} diff --git a/std/special/compiler_rt/arm/aeabi_fcmp.zig b/std/special/compiler_rt/arm/aeabi_fcmp.zig new file mode 100644 index 0000000000..f82dd25270 --- /dev/null +++ b/std/special/compiler_rt/arm/aeabi_fcmp.zig @@ -0,0 +1,108 @@ +// Ported from: +// +// https://github.com/llvm/llvm-project/commit/d674d96bc56c0f377879d01c9d8dfdaaa7859cdb/compiler-rt/lib/builtins/arm/aeabi_fcmp.S + +const compiler_rt_armhf_target = false; // TODO + +const ConditionalOperator = enum { + Eq, + Lt, + Le, + Ge, + Gt, +}; + +pub nakedcc fn __aeabi_fcmpeq() noreturn { + @setRuntimeSafety(false); + aeabi_fcmp(.Eq); + unreachable; +} + +pub nakedcc fn __aeabi_fcmplt() noreturn { + @setRuntimeSafety(false); + aeabi_fcmp(.Lt); + unreachable; +} + +pub nakedcc fn __aeabi_fcmple() noreturn { + @setRuntimeSafety(false); + aeabi_fcmp(.Le); + unreachable; +} + +pub nakedcc fn __aeabi_fcmpge() noreturn { + @setRuntimeSafety(false); + aeabi_fcmp(.Ge); + unreachable; +} + +pub nakedcc fn __aeabi_fcmpgt() noreturn { + @setRuntimeSafety(false); + aeabi_fcmp(.Gt); + unreachable; +} + +inline fn convert_fcmp_args_to_sf2_args() void { + asm volatile ( + \\ vmov s0, r0 + \\ vmov s1, r1 + ); +} + +inline fn aeabi_fcmp(comptime cond: ConditionalOperator) void { + @setRuntimeSafety(false); + asm volatile ( + \\ push { r4, lr } + ); + + if (compiler_rt_armhf_target) { + convert_fcmp_args_to_sf2_args(); + } + + switch (cond) { + .Eq => asm volatile ( + \\ bl __eqsf2 + \\ cmp r0, #0 + \\ beq 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + .Lt => asm volatile ( + \\ bl __ltsf2 + \\ cmp r0, #0 + \\ blt 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + .Le => asm volatile ( + \\ bl __lesf2 + \\ cmp r0, #0 + \\ ble 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + .Ge => asm volatile ( + \\ bl __ltsf2 + \\ cmp r0, #0 + \\ bge 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + .Gt => asm volatile ( + \\ bl __gtsf2 + \\ cmp r0, #0 + \\ bgt 1f + \\ movs r0, #0 + \\ pop { r4, pc } + \\ 1: + ), + } + asm volatile ( + \\ movs r0, #1 + \\ pop { r4, pc } + ); +} diff --git a/std/special/compiler_rt/ashlti3.zig b/std/special/compiler_rt/ashlti3.zig new file mode 100644 index 0000000000..65b23f22e5 --- /dev/null +++ b/std/special/compiler_rt/ashlti3.zig @@ -0,0 +1,41 @@ +const builtin = @import("builtin"); +const compiler_rt = @import("../compiler_rt.zig"); + +pub extern fn __ashlti3(a: i128, b: i32) i128 { + var input = twords{ .all = a }; + var result: twords = undefined; + + if (b > 63) { + // 64 <= b < 128 + result.s.low = 0; + result.s.high = input.s.low << @intCast(u6, b - 64); + } else { + // 0 <= b < 64 + if (b == 0) return a; + result.s.low = input.s.low << @intCast(u6, b); + result.s.high = input.s.low >> @intCast(u6, 64 - b); + result.s.high |= input.s.high << @intCast(u6, b); + } + + return result.all; +} + +const twords = extern union { + all: i128, + s: S, + + const S = if (builtin.endian == builtin.Endian.Little) + struct { + low: u64, + high: u64, + } + else + struct { + high: u64, + low: u64, + }; +}; + +test "import ashlti3" { + _ = @import("ashlti3_test.zig"); +} diff --git a/std/special/compiler_rt/ashlti3_test.zig b/std/special/compiler_rt/ashlti3_test.zig new file mode 100644 index 0000000000..4ba21c138e --- /dev/null +++ b/std/special/compiler_rt/ashlti3_test.zig @@ -0,0 +1,46 @@ +const __ashlti3 = @import("ashlti3.zig").__ashlti3; +const testing = @import("std").testing; + +fn test__ashlti3(a: i128, b: i32, expected: i128) void { + const x = __ashlti3(a, b); + testing.expect(x == expected); +} + +test "ashlti3" { + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 0, @bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 1, @bitCast(i128, @intCast(u128, 0xFDB97530ECA8642BFDB97530ECA8642A))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 2, @bitCast(i128, @intCast(u128, 0xFB72EA61D950C857FB72EA61D950C854))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 3, @bitCast(i128, @intCast(u128, 0xF6E5D4C3B2A190AFF6E5D4C3B2A190A8))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 4, @bitCast(i128, @intCast(u128, 0xEDCBA9876543215FEDCBA98765432150))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 28, @bitCast(i128, @intCast(u128, 0x876543215FEDCBA98765432150000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 29, @bitCast(i128, @intCast(u128, 0x0ECA8642BFDB97530ECA8642A0000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 30, @bitCast(i128, @intCast(u128, 0x1D950C857FB72EA61D950C8540000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 31, @bitCast(i128, @intCast(u128, 0x3B2A190AFF6E5D4C3B2A190A80000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 32, @bitCast(i128, @intCast(u128, 0x76543215FEDCBA987654321500000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 33, @bitCast(i128, @intCast(u128, 0xECA8642BFDB97530ECA8642A00000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 34, @bitCast(i128, @intCast(u128, 0xD950C857FB72EA61D950C85400000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 35, @bitCast(i128, @intCast(u128, 0xB2A190AFF6E5D4C3B2A190A800000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 36, @bitCast(i128, @intCast(u128, 0x6543215FEDCBA9876543215000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 60, @bitCast(i128, @intCast(u128, 0x5FEDCBA9876543215000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 61, @bitCast(i128, @intCast(u128, 0xBFDB97530ECA8642A000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 62, @bitCast(i128, @intCast(u128, 0x7FB72EA61D950C854000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 63, @bitCast(i128, @intCast(u128, 0xFF6E5D4C3B2A190A8000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 64, @bitCast(i128, @intCast(u128, 0xFEDCBA98765432150000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 65, @bitCast(i128, @intCast(u128, 0xFDB97530ECA8642A0000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 66, @bitCast(i128, @intCast(u128, 0xFB72EA61D950C8540000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 67, @bitCast(i128, @intCast(u128, 0xF6E5D4C3B2A190A80000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 68, @bitCast(i128, @intCast(u128, 0xEDCBA987654321500000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 92, @bitCast(i128, @intCast(u128, 0x87654321500000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 93, @bitCast(i128, @intCast(u128, 0x0ECA8642A00000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 94, @bitCast(i128, @intCast(u128, 0x1D950C85400000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 95, @bitCast(i128, @intCast(u128, 0x3B2A190A800000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 96, @bitCast(i128, @intCast(u128, 0x76543215000000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 97, @bitCast(i128, @intCast(u128, 0xECA8642A000000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 98, @bitCast(i128, @intCast(u128, 0xD950C854000000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 99, @bitCast(i128, @intCast(u128, 0xB2A190A8000000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 100, @bitCast(i128, @intCast(u128, 0x65432150000000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 124, @bitCast(i128, @intCast(u128, 0x50000000000000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 125, @bitCast(i128, @intCast(u128, 0xA0000000000000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 126, @bitCast(i128, @intCast(u128, 0x40000000000000000000000000000000))); + test__ashlti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 127, @bitCast(i128, @intCast(u128, 0x80000000000000000000000000000000))); +} diff --git a/std/special/compiler_rt/ashrti3.zig b/std/special/compiler_rt/ashrti3.zig new file mode 100644 index 0000000000..40ee89c3c4 --- /dev/null +++ b/std/special/compiler_rt/ashrti3.zig @@ -0,0 +1,42 @@ +const builtin = @import("builtin"); +const compiler_rt = @import("../compiler_rt.zig"); + +pub extern fn __ashrti3(a: i128, b: i32) i128 { + var input = twords{ .all = a }; + var result: twords = undefined; + + if (b > 63) { + // 64 <= b < 128 + result.s.low = input.s.high >> @intCast(u6, b - 64); + result.s.high = input.s.high >> 63; + } else { + // 0 <= b < 64 + if (b == 0) return a; + result.s.low = input.s.high << @intCast(u6, 64 - b); + // Avoid sign-extension here + result.s.low |= @bitCast(i64, @bitCast(u64, input.s.low) >> @intCast(u6, b)); + result.s.high = input.s.high >> @intCast(u6, b); + } + + return result.all; +} + +const twords = extern union { + all: i128, + s: S, + + const S = if (builtin.endian == builtin.Endian.Little) + struct { + low: i64, + high: i64, + } + else + struct { + high: i64, + low: i64, + }; +}; + +test "import ashrti3" { + _ = @import("ashrti3_test.zig"); +} diff --git a/std/special/compiler_rt/ashrti3_test.zig b/std/special/compiler_rt/ashrti3_test.zig new file mode 100644 index 0000000000..04ae5abd82 --- /dev/null +++ b/std/special/compiler_rt/ashrti3_test.zig @@ -0,0 +1,58 @@ +const __ashrti3 = @import("ashrti3.zig").__ashrti3; +const testing = @import("std").testing; + +fn test__ashrti3(a: i128, b: i32, expected: i128) void { + const x = __ashrti3(a, b); + // @import("std").debug.warn("got 0x{x}\nexp 0x{x}\n", @truncate(u64, +// @bitCast(u128, x) >> 64), @truncate(u64, @bitCast(u128, expected)) >> 64); + testing.expect(x == expected); +} + +test "ashrti3" { + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 0, @bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 1, @bitCast(i128, @intCast(u128, 0xFF6E5D4C3B2A190AFF6E5D4C3B2A190A))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 2, @bitCast(i128, @intCast(u128, 0xFFB72EA61D950C857FB72EA61D950C85))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 3, @bitCast(i128, @intCast(u128, 0xFFDB97530ECA8642BFDB97530ECA8642))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 4, @bitCast(i128, @intCast(u128, 0xFFEDCBA9876543215FEDCBA987654321))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 28, @bitCast(i128, @intCast(u128, 0xFFFFFFFFEDCBA9876543215FEDCBA987))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 29, @bitCast(i128, @intCast(u128, 0xFFFFFFFFF6E5D4C3B2A190AFF6E5D4C3))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 30, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFB72EA61D950C857FB72EA61))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 31, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFDB97530ECA8642BFDB97530))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 32, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFEDCBA9876543215FEDCBA98))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 33, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFF6E5D4C3B2A190AFF6E5D4C))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 34, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFB72EA61D950C857FB72EA6))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 35, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFDB97530ECA8642BFDB9753))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 36, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFEDCBA9876543215FEDCBA9))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 60, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFEDCBA9876543215F))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 61, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFF6E5D4C3B2A190AF))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 62, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFB72EA61D950C857))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 63, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFDB97530ECA8642B))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 64, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFEDCBA9876543215))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 65, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFF6E5D4C3B2A190A))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 66, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFB72EA61D950C85))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 67, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFDB97530ECA8642))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 68, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFEDCBA987654321))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 92, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFEDCBA987))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 93, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFF6E5D4C3))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 94, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFB72EA61))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 95, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFDB97530))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 96, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFEDCBA98))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 97, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFF6E5D4C))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 98, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFB72EA6))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 99, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFDB9753))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 100, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFEDCBA9))); + + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 124, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 125, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 126, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))); + test__ashrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 127, @bitCast(i128, @intCast(u128, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))); +} diff --git a/std/special/compiler_rt/comparedf2.zig b/std/special/compiler_rt/comparedf2.zig new file mode 100644 index 0000000000..f97e2474be --- /dev/null +++ b/std/special/compiler_rt/comparedf2.zig @@ -0,0 +1,122 @@ +// Ported from: +// +// https://github.com/llvm/llvm-project/commit/d674d96bc56c0f377879d01c9d8dfdaaa7859cdb/compiler-rt/lib/builtins/comparedf2.c + +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; + +const fp_t = f64; +const rep_t = u64; +const srep_t = i64; + +const typeWidth = rep_t.bit_count; +const significandBits = std.math.floatMantissaBits(fp_t); +const exponentBits = std.math.floatExponentBits(fp_t); +const signBit = (rep_t(1) << (significandBits + exponentBits)); +const absMask = signBit - 1; +const implicitBit = rep_t(1) << significandBits; +const significandMask = implicitBit - 1; +const exponentMask = absMask ^ significandMask; +const infRep = @bitCast(rep_t, std.math.inf(fp_t)); + +// TODO https://github.com/ziglang/zig/issues/641 +// and then make the return types of some of these functions the enum instead of c_int +const LE_LESS = c_int(-1); +const LE_EQUAL = c_int(0); +const LE_GREATER = c_int(1); +const LE_UNORDERED = c_int(1); + +pub extern fn __ledf2(a: fp_t, b: fp_t) c_int { + @setRuntimeSafety(is_test); + const aInt: srep_t = @bitCast(srep_t, a); + const bInt: srep_t = @bitCast(srep_t, b); + const aAbs: rep_t = @bitCast(rep_t, aInt) & absMask; + const bAbs: rep_t = @bitCast(rep_t, bInt) & absMask; + + // If either a or b is NaN, they are unordered. + if (aAbs > infRep or bAbs > infRep) return LE_UNORDERED; + + // If a and b are both zeros, they are equal. + if ((aAbs | bAbs) == 0) return LE_EQUAL; + + // If at least one of a and b is positive, we get the same result comparing + // a and b as signed integers as we would with a fp_ting-point compare. + if ((aInt & bInt) >= 0) { + if (aInt < bInt) { + return LE_LESS; + } else if (aInt == bInt) { + return LE_EQUAL; + } else return LE_GREATER; + } + + // Otherwise, both are negative, so we need to flip the sense of the + // comparison to get the correct result. (This assumes a twos- or ones- + // complement integer representation; if integers are represented in a + // sign-magnitude representation, then this flip is incorrect). + else { + if (aInt > bInt) { + return LE_LESS; + } else if (aInt == bInt) { + return LE_EQUAL; + } else return LE_GREATER; + } +} + +// TODO https://github.com/ziglang/zig/issues/641 +// and then make the return types of some of these functions the enum instead of c_int +const GE_LESS = c_int(-1); +const GE_EQUAL = c_int(0); +const GE_GREATER = c_int(1); +const GE_UNORDERED = c_int(-1); // Note: different from LE_UNORDERED + +pub extern fn __gedf2(a: fp_t, b: fp_t) c_int { + @setRuntimeSafety(is_test); + const aInt: srep_t = @bitCast(srep_t, a); + const bInt: srep_t = @bitCast(srep_t, b); + const aAbs: rep_t = @bitCast(rep_t, aInt) & absMask; + const bAbs: rep_t = @bitCast(rep_t, bInt) & absMask; + + if (aAbs > infRep or bAbs > infRep) return GE_UNORDERED; + if ((aAbs | bAbs) == 0) return GE_EQUAL; + if ((aInt & bInt) >= 0) { + if (aInt < bInt) { + return GE_LESS; + } else if (aInt == bInt) { + return GE_EQUAL; + } else return GE_GREATER; + } else { + if (aInt > bInt) { + return GE_LESS; + } else if (aInt == bInt) { + return GE_EQUAL; + } else return GE_GREATER; + } +} + +pub extern fn __unorddf2(a: fp_t, b: fp_t) c_int { + @setRuntimeSafety(is_test); + const aAbs: rep_t = @bitCast(rep_t, a) & absMask; + const bAbs: rep_t = @bitCast(rep_t, b) & absMask; + return @boolToInt(aAbs > infRep or bAbs > infRep); +} + +pub extern fn __eqdf2(a: fp_t, b: fp_t) c_int { + return __ledf2(a, b); +} + +pub extern fn __ltdf2(a: fp_t, b: fp_t) c_int { + return __ledf2(a, b); +} + +pub extern fn __nedf2(a: fp_t, b: fp_t) c_int { + return __ledf2(a, b); +} + +pub extern fn __gtdf2(a: fp_t, b: fp_t) c_int { + return __gedf2(a, b); +} + +test "import comparedf2" { + _ = @import("comparedf2_test.zig"); +} diff --git a/std/special/compiler_rt/comparedf2_test.zig b/std/special/compiler_rt/comparedf2_test.zig new file mode 100644 index 0000000000..9c08389994 --- /dev/null +++ b/std/special/compiler_rt/comparedf2_test.zig @@ -0,0 +1,101 @@ +// Ported from: +// +// https://github.com/llvm/llvm-project/commit/d674d96bc56c0f377879d01c9d8dfdaaa7859cdb/compiler-rt/test/builtins/Unit/comparedf2_test.c + +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; + +const comparedf2 = @import("comparedf2.zig"); + +const TestVector = struct { + a: f64, + b: f64, + eqReference: c_int, + geReference: c_int, + gtReference: c_int, + leReference: c_int, + ltReference: c_int, + neReference: c_int, + unReference: c_int, +}; + +fn test__cmpdf2(vector: TestVector) bool { + if (comparedf2.__eqdf2(vector.a, vector.b) != vector.eqReference) { + return false; + } + if (comparedf2.__gedf2(vector.a, vector.b) != vector.geReference) { + return false; + } + if (comparedf2.__gtdf2(vector.a, vector.b) != vector.gtReference) { + return false; + } + if (comparedf2.__ledf2(vector.a, vector.b) != vector.leReference) { + return false; + } + if (comparedf2.__ltdf2(vector.a, vector.b) != vector.ltReference) { + return false; + } + if (comparedf2.__nedf2(vector.a, vector.b) != vector.neReference) { + return false; + } + if (comparedf2.__unorddf2(vector.a, vector.b) != vector.unReference) { + return false; + } + return true; +} + +const arguments = []f64{ + std.math.nan(f64), + -std.math.inf(f64), + -0x1.fffffffffffffp1023, + -0x1.0000000000001p0 - 0x1.0000000000000p0, + -0x1.fffffffffffffp-1, + -0x1.0000000000000p-1022, + -0x0.fffffffffffffp-1022, + -0x0.0000000000001p-1022, + -0.0, + 0.0, + 0x0.0000000000001p-1022, + 0x0.fffffffffffffp-1022, + 0x1.0000000000000p-1022, + 0x1.fffffffffffffp-1, + 0x1.0000000000000p0, + 0x1.0000000000001p0, + 0x1.fffffffffffffp1023, + std.math.inf(f64), +}; + +fn generateVector(comptime a: f64, comptime b: f64) TestVector { + const leResult = if (a < b) -1 else if (a == b) 0 else 1; + const geResult = if (a > b) 1 else if (a == b) 0 else -1; + const unResult = if (a != a or b != b) 1 else 0; + return TestVector{ + .a = a, + .b = b, + .eqReference = leResult, + .geReference = geResult, + .gtReference = geResult, + .leReference = leResult, + .ltReference = leResult, + .neReference = leResult, + .unReference = unResult, + }; +} + +const test_vectors = init: { + @setEvalBranchQuota(10000); + var vectors: [arguments.len * arguments.len]TestVector = undefined; + for (arguments[0..]) |arg_i, i| { + for (arguments[0..]) |arg_j, j| { + vectors[(i * arguments.len) + j] = generateVector(arg_i, arg_j); + } + } + break :init vectors; +}; + +test "compare f64" { + for (test_vectors) |vector, i| { + std.testing.expect(test__cmpdf2(vector)); + } +} diff --git a/std/special/compiler_rt/comparesf2.zig b/std/special/compiler_rt/comparesf2.zig new file mode 100644 index 0000000000..e99e0bb3dd --- /dev/null +++ b/std/special/compiler_rt/comparesf2.zig @@ -0,0 +1,122 @@ +// Ported from: +// +// https://github.com/llvm/llvm-project/commit/d674d96bc56c0f377879d01c9d8dfdaaa7859cdb/compiler-rt/lib/builtins/comparesf2.c + +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; + +const fp_t = f32; +const rep_t = u32; +const srep_t = i32; + +const typeWidth = rep_t.bit_count; +const significandBits = std.math.floatMantissaBits(fp_t); +const exponentBits = std.math.floatExponentBits(fp_t); +const signBit = (rep_t(1) << (significandBits + exponentBits)); +const absMask = signBit - 1; +const implicitBit = rep_t(1) << significandBits; +const significandMask = implicitBit - 1; +const exponentMask = absMask ^ significandMask; +const infRep = @bitCast(rep_t, std.math.inf(fp_t)); + +// TODO https://github.com/ziglang/zig/issues/641 +// and then make the return types of some of these functions the enum instead of c_int +const LE_LESS = c_int(-1); +const LE_EQUAL = c_int(0); +const LE_GREATER = c_int(1); +const LE_UNORDERED = c_int(1); + +pub extern fn __lesf2(a: fp_t, b: fp_t) c_int { + @setRuntimeSafety(is_test); + const aInt: srep_t = @bitCast(srep_t, a); + const bInt: srep_t = @bitCast(srep_t, b); + const aAbs: rep_t = @bitCast(rep_t, aInt) & absMask; + const bAbs: rep_t = @bitCast(rep_t, bInt) & absMask; + + // If either a or b is NaN, they are unordered. + if (aAbs > infRep or bAbs > infRep) return LE_UNORDERED; + + // If a and b are both zeros, they are equal. + if ((aAbs | bAbs) == 0) return LE_EQUAL; + + // If at least one of a and b is positive, we get the same result comparing + // a and b as signed integers as we would with a fp_ting-point compare. + if ((aInt & bInt) >= 0) { + if (aInt < bInt) { + return LE_LESS; + } else if (aInt == bInt) { + return LE_EQUAL; + } else return LE_GREATER; + } + + // Otherwise, both are negative, so we need to flip the sense of the + // comparison to get the correct result. (This assumes a twos- or ones- + // complement integer representation; if integers are represented in a + // sign-magnitude representation, then this flip is incorrect). + else { + if (aInt > bInt) { + return LE_LESS; + } else if (aInt == bInt) { + return LE_EQUAL; + } else return LE_GREATER; + } +} + +// TODO https://github.com/ziglang/zig/issues/641 +// and then make the return types of some of these functions the enum instead of c_int +const GE_LESS = c_int(-1); +const GE_EQUAL = c_int(0); +const GE_GREATER = c_int(1); +const GE_UNORDERED = c_int(-1); // Note: different from LE_UNORDERED + +pub extern fn __gesf2(a: fp_t, b: fp_t) c_int { + @setRuntimeSafety(is_test); + const aInt: srep_t = @bitCast(srep_t, a); + const bInt: srep_t = @bitCast(srep_t, b); + const aAbs: rep_t = @bitCast(rep_t, aInt) & absMask; + const bAbs: rep_t = @bitCast(rep_t, bInt) & absMask; + + if (aAbs > infRep or bAbs > infRep) return GE_UNORDERED; + if ((aAbs | bAbs) == 0) return GE_EQUAL; + if ((aInt & bInt) >= 0) { + if (aInt < bInt) { + return GE_LESS; + } else if (aInt == bInt) { + return GE_EQUAL; + } else return GE_GREATER; + } else { + if (aInt > bInt) { + return GE_LESS; + } else if (aInt == bInt) { + return GE_EQUAL; + } else return GE_GREATER; + } +} + +pub extern fn __unordsf2(a: fp_t, b: fp_t) c_int { + @setRuntimeSafety(is_test); + const aAbs: rep_t = @bitCast(rep_t, a) & absMask; + const bAbs: rep_t = @bitCast(rep_t, b) & absMask; + return @boolToInt(aAbs > infRep or bAbs > infRep); +} + +pub extern fn __eqsf2(a: fp_t, b: fp_t) c_int { + return __lesf2(a, b); +} + +pub extern fn __ltsf2(a: fp_t, b: fp_t) c_int { + return __lesf2(a, b); +} + +pub extern fn __nesf2(a: fp_t, b: fp_t) c_int { + return __lesf2(a, b); +} + +pub extern fn __gtsf2(a: fp_t, b: fp_t) c_int { + return __gesf2(a, b); +} + +test "import comparesf2" { + _ = @import("comparesf2_test.zig"); +} diff --git a/std/special/compiler_rt/comparesf2_test.zig b/std/special/compiler_rt/comparesf2_test.zig new file mode 100644 index 0000000000..e460634fad --- /dev/null +++ b/std/special/compiler_rt/comparesf2_test.zig @@ -0,0 +1,101 @@ +// Ported from: +// +// https://github.com/llvm/llvm-project/commit/d674d96bc56c0f377879d01c9d8dfdaaa7859cdb/compiler-rt/test/builtins/Unit/comparesf2_test.c + +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; + +const comparesf2 = @import("comparesf2.zig"); + +const TestVector = struct { + a: f32, + b: f32, + eqReference: c_int, + geReference: c_int, + gtReference: c_int, + leReference: c_int, + ltReference: c_int, + neReference: c_int, + unReference: c_int, +}; + +fn test__cmpsf2(vector: TestVector) bool { + if (comparesf2.__eqsf2(vector.a, vector.b) != vector.eqReference) { + return false; + } + if (comparesf2.__gesf2(vector.a, vector.b) != vector.geReference) { + return false; + } + if (comparesf2.__gtsf2(vector.a, vector.b) != vector.gtReference) { + return false; + } + if (comparesf2.__lesf2(vector.a, vector.b) != vector.leReference) { + return false; + } + if (comparesf2.__ltsf2(vector.a, vector.b) != vector.ltReference) { + return false; + } + if (comparesf2.__nesf2(vector.a, vector.b) != vector.neReference) { + return false; + } + if (comparesf2.__unordsf2(vector.a, vector.b) != vector.unReference) { + return false; + } + return true; +} + +const arguments = []f32{ + std.math.nan(f32), + -std.math.inf(f32), + -0x1.fffffep127, + -0x1.000002p0 - 0x1.000000p0, + -0x1.fffffep-1, + -0x1.000000p-126, + -0x0.fffffep-126, + -0x0.000002p-126, + -0.0, + 0.0, + 0x0.000002p-126, + 0x0.fffffep-126, + 0x1.000000p-126, + 0x1.fffffep-1, + 0x1.000000p0, + 0x1.000002p0, + 0x1.fffffep127, + std.math.inf(f32), +}; + +fn generateVector(comptime a: f32, comptime b: f32) TestVector { + const leResult = if (a < b) -1 else if (a == b) 0 else 1; + const geResult = if (a > b) 1 else if (a == b) 0 else -1; + const unResult = if (a != a or b != b) 1 else 0; + return TestVector{ + .a = a, + .b = b, + .eqReference = leResult, + .geReference = geResult, + .gtReference = geResult, + .leReference = leResult, + .ltReference = leResult, + .neReference = leResult, + .unReference = unResult, + }; +} + +const test_vectors = init: { + @setEvalBranchQuota(10000); + var vectors: [arguments.len * arguments.len]TestVector = undefined; + for (arguments[0..]) |arg_i, i| { + for (arguments[0..]) |arg_j, j| { + vectors[(i * arguments.len) + j] = generateVector(arg_i, arg_j); + } + } + break :init vectors; +}; + +test "compare f32" { + for (test_vectors) |vector, i| { + std.testing.expect(test__cmpsf2(vector)); + } +} diff --git a/std/special/compiler_rt/divti3.zig b/std/special/compiler_rt/divti3.zig index e89a1ada5c..d5b2778a34 100644 --- a/std/special/compiler_rt/divti3.zig +++ b/std/special/compiler_rt/divti3.zig @@ -16,9 +16,9 @@ pub extern fn __divti3(a: i128, b: i128) i128 { return (@bitCast(i128, r) ^ s) -% s; } -pub extern fn __divti3_windows_x86_64(a: *const i128, b: *const i128) void { - @setRuntimeSafety(builtin.is_test); - compiler_rt.setXmm0(i128, __divti3(a.*, b.*)); +const v128 = @Vector(2, u64); +pub extern fn __divti3_windows_x86_64(a: v128, b: v128) v128 { + return @bitCast(v128, @inlineCall(__divti3, @bitCast(i128, a), @bitCast(i128, b))); } test "import divti3" { diff --git a/std/special/compiler_rt/extendXfYf2.zig b/std/special/compiler_rt/extendXfYf2.zig index 099e27b74a..42559784bb 100644 --- a/std/special/compiler_rt/extendXfYf2.zig +++ b/std/special/compiler_rt/extendXfYf2.zig @@ -2,21 +2,27 @@ const std = @import("std"); const builtin = @import("builtin"); const is_test = builtin.is_test; +pub extern fn __extendsfdf2(a: f32) f64 { + return @inlineCall(extendXfYf2, f64, f32, @bitCast(u32, a)); +} + pub extern fn __extenddftf2(a: f64) f128 { - return extendXfYf2(f128, f64, a); + return @inlineCall(extendXfYf2, f128, f64, @bitCast(u64, a)); } pub extern fn __extendsftf2(a: f32) f128 { - return extendXfYf2(f128, f32, a); + return @inlineCall(extendXfYf2, f128, f32, @bitCast(u32, a)); } pub extern fn __extendhfsf2(a: u16) f32 { - return extendXfYf2(f32, f16, @bitCast(f16, a)); + return @inlineCall(extendXfYf2, f32, f16, a); } const CHAR_BIT = 8; -inline fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: src_t) dst_t { +fn extendXfYf2(comptime dst_t: type, comptime src_t: type, a: @IntType(false, @typeInfo(src_t).Float.bits)) dst_t { + @setRuntimeSafety(builtin.is_test); + const src_rep_t = @IntType(false, @typeInfo(src_t).Float.bits); const dst_rep_t = @IntType(false, @typeInfo(dst_t).Float.bits); const srcSigBits = std.math.floatMantissaBits(src_t); diff --git a/std/special/compiler_rt/floatdidf.zig b/std/special/compiler_rt/floatdidf.zig new file mode 100644 index 0000000000..1610136413 --- /dev/null +++ b/std/special/compiler_rt/floatdidf.zig @@ -0,0 +1,22 @@ +const builtin = @import("builtin"); +const std = @import("std"); + +const twop52: f64 = 0x1.0p52; +const twop32: f64 = 0x1.0p32; + +pub extern fn __floatdidf(a: i64) f64 { + @setRuntimeSafety(builtin.is_test); + + if (a == 0) return 0; + + var low = @bitCast(i64, twop52); + const high = @intToFloat(f64, @truncate(i32, a >> 32)) * twop32; + + low |= @bitCast(i64, a & 0xFFFFFFFF); + + return (high - twop52) + @bitCast(f64, low); +} + +test "import floatdidf" { + _ = @import("floatdidf_test.zig"); +} diff --git a/std/special/compiler_rt/floatdidf_test.zig b/std/special/compiler_rt/floatdidf_test.zig new file mode 100644 index 0000000000..c854183809 --- /dev/null +++ b/std/special/compiler_rt/floatdidf_test.zig @@ -0,0 +1,53 @@ +const __floatdidf = @import("floatdidf.zig").__floatdidf; +const testing = @import("std").testing; + +fn test__floatdidf(a: i64, expected: f64) void { + const r = __floatdidf(a); + testing.expect(r == expected); +} + +test "floatdidf" { + test__floatdidf(0, 0.0); + test__floatdidf(1, 1.0); + test__floatdidf(2, 2.0); + test__floatdidf(20, 20.0); + test__floatdidf(-1, -1.0); + test__floatdidf(-2, -2.0); + test__floatdidf(-20, -20.0); + test__floatdidf(0x7FFFFF8000000000, 0x1.FFFFFEp+62); + test__floatdidf(0x7FFFFFFFFFFFF800, 0x1.FFFFFFFFFFFFEp+62); + test__floatdidf(0x7FFFFF0000000000, 0x1.FFFFFCp+62); + test__floatdidf(0x7FFFFFFFFFFFF000, 0x1.FFFFFFFFFFFFCp+62); + test__floatdidf(@bitCast(i64, @intCast(u64, 0x8000008000000000)), -0x1.FFFFFEp+62); + test__floatdidf(@bitCast(i64, @intCast(u64, 0x8000000000000800)), -0x1.FFFFFFFFFFFFEp+62); + test__floatdidf(@bitCast(i64, @intCast(u64, 0x8000010000000000)), -0x1.FFFFFCp+62); + test__floatdidf(@bitCast(i64, @intCast(u64, 0x8000000000001000)), -0x1.FFFFFFFFFFFFCp+62); + test__floatdidf(@bitCast(i64, @intCast(u64, 0x8000000000000000)), -0x1.000000p+63); + test__floatdidf(@bitCast(i64, @intCast(u64, 0x8000000000000001)), -0x1.000000p+63); + test__floatdidf(0x0007FB72E8000000, 0x1.FEDCBAp+50); + test__floatdidf(0x0007FB72EA000000, 0x1.FEDCBA8p+50); + test__floatdidf(0x0007FB72EB000000, 0x1.FEDCBACp+50); + test__floatdidf(0x0007FB72EBFFFFFF, 0x1.FEDCBAFFFFFFCp+50); + test__floatdidf(0x0007FB72EC000000, 0x1.FEDCBBp+50); + test__floatdidf(0x0007FB72E8000001, 0x1.FEDCBA0000004p+50); + test__floatdidf(0x0007FB72E6000000, 0x1.FEDCB98p+50); + test__floatdidf(0x0007FB72E7000000, 0x1.FEDCB9Cp+50); + test__floatdidf(0x0007FB72E7FFFFFF, 0x1.FEDCB9FFFFFFCp+50); + test__floatdidf(0x0007FB72E4000001, 0x1.FEDCB90000004p+50); + test__floatdidf(0x0007FB72E4000000, 0x1.FEDCB9p+50); + test__floatdidf(0x023479FD0E092DC0, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DA1, 0x1.1A3CFE870496Dp+57); + test__floatdidf(0x023479FD0E092DB0, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DB8, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DB6, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DBF, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DC1, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DC7, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DC8, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DCF, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DD0, 0x1.1A3CFE870496Ep+57); + test__floatdidf(0x023479FD0E092DD1, 0x1.1A3CFE870496Fp+57); + test__floatdidf(0x023479FD0E092DD8, 0x1.1A3CFE870496Fp+57); + test__floatdidf(0x023479FD0E092DDF, 0x1.1A3CFE870496Fp+57); + test__floatdidf(0x023479FD0E092DE0, 0x1.1A3CFE870496Fp+57); +} diff --git a/std/special/compiler_rt/floatsiXf.zig b/std/special/compiler_rt/floatsiXf.zig new file mode 100644 index 0000000000..83b3940c1e --- /dev/null +++ b/std/special/compiler_rt/floatsiXf.zig @@ -0,0 +1,109 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const maxInt = std.math.maxInt; + +fn floatsiXf(comptime T: type, a: i32) T { + @setRuntimeSafety(builtin.is_test); + + const Z = @IntType(false, T.bit_count); + const S = @IntType(false, T.bit_count - @clz(Z(T.bit_count) - 1)); + + if (a == 0) { + return T(0.0); + } + + const significandBits = std.math.floatMantissaBits(T); + const exponentBits = std.math.floatExponentBits(T); + const exponentBias = ((1 << exponentBits - 1) - 1); + + const implicitBit = Z(1) << significandBits; + const signBit = Z(1 << Z.bit_count - 1); + + const sign = a >> 31; + // Take absolute value of a via abs(x) = (x^(x >> 31)) - (x >> 31). + const abs_a = (a ^ sign) -% sign; + // The exponent is the width of abs(a) + const exp = Z(31 - @clz(abs_a)); + + const sign_bit = if (sign < 0) signBit else 0; + + var mantissa: Z = undefined; + // Shift a into the significand field and clear the implicit bit. + if (exp <= significandBits) { + // No rounding needed + const shift = @intCast(S, significandBits - exp); + mantissa = @intCast(Z, @bitCast(u32, abs_a)) << shift ^ implicitBit; + } else { + const shift = @intCast(S, exp - significandBits); + // Round to the nearest number after truncation + mantissa = @intCast(Z, @bitCast(u32, abs_a)) >> shift ^ implicitBit; + // Align to the left and check if the truncated part is halfway over + const round = @bitCast(u32, abs_a) << @intCast(u5, 31 - shift); + mantissa += @boolToInt(round > 0x80000000); + // Tie to even + mantissa += mantissa & 1; + } + + // Use the addition instead of a or since we may have a carry from the + // mantissa to the exponent + var result = mantissa; + result += (exp + exponentBias) << significandBits; + result += sign_bit; + + return @bitCast(T, result); +} + +pub extern fn __floatsisf(arg: i32) f32 { + @setRuntimeSafety(builtin.is_test); + return @inlineCall(floatsiXf, f32, arg); +} + +pub extern fn __floatsidf(arg: i32) f64 { + @setRuntimeSafety(builtin.is_test); + return @inlineCall(floatsiXf, f64, arg); +} + +pub extern fn __floatsitf(arg: i32) f128 { + @setRuntimeSafety(builtin.is_test); + return @inlineCall(floatsiXf, f128, arg); +} + +fn test_one_floatsitf(a: i32, expected: u128) void { + const r = __floatsitf(a); + std.testing.expect(@bitCast(u128, r) == expected); +} + +fn test_one_floatsidf(a: i32, expected: u64) void { + const r = __floatsidf(a); + std.testing.expect(@bitCast(u64, r) == expected); +} + +fn test_one_floatsisf(a: i32, expected: u32) void { + const r = __floatsisf(a); + std.testing.expect(@bitCast(u32, r) == expected); +} + +test "floatsidf" { + test_one_floatsidf(0, 0x0000000000000000); + test_one_floatsidf(1, 0x3ff0000000000000); + test_one_floatsidf(-1, 0xbff0000000000000); + test_one_floatsidf(0x7FFFFFFF, 0x41dfffffffc00000); + test_one_floatsidf(@bitCast(i32, @intCast(u32, 0x80000000)), 0xc1e0000000000000); +} + +test "floatsisf" { + test_one_floatsisf(0, 0x00000000); + test_one_floatsisf(1, 0x3f800000); + test_one_floatsisf(-1, 0xbf800000); + test_one_floatsisf(0x7FFFFFFF, 0x4f000000); + test_one_floatsisf(@bitCast(i32, @intCast(u32, 0x80000000)), 0xcf000000); +} + +test "floatsitf" { + test_one_floatsitf(0, 0); + test_one_floatsitf(0x7FFFFFFF, 0x401dfffffffc00000000000000000000); + test_one_floatsitf(0x12345678, 0x401b2345678000000000000000000000); + test_one_floatsitf(-0x12345678, 0xc01b2345678000000000000000000000); + test_one_floatsitf(@bitCast(i32, @intCast(u32, 0xffffffff)), 0xbfff0000000000000000000000000000); + test_one_floatsitf(@bitCast(i32, @intCast(u32, 0x80000000)), 0xc01e0000000000000000000000000000); +} diff --git a/std/special/compiler_rt/floatundidf.zig b/std/special/compiler_rt/floatundidf.zig new file mode 100644 index 0000000000..68759a2acd --- /dev/null +++ b/std/special/compiler_rt/floatundidf.zig @@ -0,0 +1,24 @@ +const builtin = @import("builtin"); +const std = @import("std"); + +const twop52: f64 = 0x1.0p52; +const twop84: f64 = 0x1.0p84; +const twop84_plus_twop52: f64 = 0x1.00000001p84; + +pub extern fn __floatundidf(a: u64) f64 { + @setRuntimeSafety(builtin.is_test); + + if (a == 0) return 0; + + var high = @bitCast(u64, twop84); + var low = @bitCast(u64, twop52); + + high |= a >> 32; + low |= a & 0xFFFFFFFF; + + return (@bitCast(f64, high) - twop84_plus_twop52) + @bitCast(f64, low); +} + +test "import floatundidf" { + _ = @import("floatundidf_test.zig"); +} diff --git a/std/special/compiler_rt/floatundidf_test.zig b/std/special/compiler_rt/floatundidf_test.zig new file mode 100644 index 0000000000..084ada51c9 --- /dev/null +++ b/std/special/compiler_rt/floatundidf_test.zig @@ -0,0 +1,50 @@ +const __floatundidf = @import("floatundidf.zig").__floatundidf; +const testing = @import("std").testing; + +fn test__floatundidf(a: u64, expected: f64) void { + const r = __floatundidf(a); + testing.expect(r == expected); +} + +test "floatundidf" { + test__floatundidf(0, 0.0); + test__floatundidf(1, 1.0); + test__floatundidf(2, 2.0); + test__floatundidf(20, 20.0); + test__floatundidf(0x7FFFFF8000000000, 0x1.FFFFFEp+62); + test__floatundidf(0x7FFFFFFFFFFFF800, 0x1.FFFFFFFFFFFFEp+62); + test__floatundidf(0x7FFFFF0000000000, 0x1.FFFFFCp+62); + test__floatundidf(0x7FFFFFFFFFFFF000, 0x1.FFFFFFFFFFFFCp+62); + test__floatundidf(0x8000008000000000, 0x1.000001p+63); + test__floatundidf(0x8000000000000800, 0x1.0000000000001p+63); + test__floatundidf(0x8000010000000000, 0x1.000002p+63); + test__floatundidf(0x8000000000001000, 0x1.0000000000002p+63); + test__floatundidf(0x8000000000000000, 0x1p+63); + test__floatundidf(0x8000000000000001, 0x1p+63); + test__floatundidf(0x0007FB72E8000000, 0x1.FEDCBAp+50); + test__floatundidf(0x0007FB72EA000000, 0x1.FEDCBA8p+50); + test__floatundidf(0x0007FB72EB000000, 0x1.FEDCBACp+50); + test__floatundidf(0x0007FB72EBFFFFFF, 0x1.FEDCBAFFFFFFCp+50); + test__floatundidf(0x0007FB72EC000000, 0x1.FEDCBBp+50); + test__floatundidf(0x0007FB72E8000001, 0x1.FEDCBA0000004p+50); + test__floatundidf(0x0007FB72E6000000, 0x1.FEDCB98p+50); + test__floatundidf(0x0007FB72E7000000, 0x1.FEDCB9Cp+50); + test__floatundidf(0x0007FB72E7FFFFFF, 0x1.FEDCB9FFFFFFCp+50); + test__floatundidf(0x0007FB72E4000001, 0x1.FEDCB90000004p+50); + test__floatundidf(0x0007FB72E4000000, 0x1.FEDCB9p+50); + test__floatundidf(0x023479FD0E092DC0, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DA1, 0x1.1A3CFE870496Dp+57); + test__floatundidf(0x023479FD0E092DB0, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DB8, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DB6, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DBF, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DC1, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DC7, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DC8, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DCF, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DD0, 0x1.1A3CFE870496Ep+57); + test__floatundidf(0x023479FD0E092DD1, 0x1.1A3CFE870496Fp+57); + test__floatundidf(0x023479FD0E092DD8, 0x1.1A3CFE870496Fp+57); + test__floatundidf(0x023479FD0E092DDF, 0x1.1A3CFE870496Fp+57); + test__floatundidf(0x023479FD0E092DE0, 0x1.1A3CFE870496Fp+57); +} diff --git a/std/special/compiler_rt/floatunsidf.zig b/std/special/compiler_rt/floatunsidf.zig new file mode 100644 index 0000000000..db02894448 --- /dev/null +++ b/std/special/compiler_rt/floatunsidf.zig @@ -0,0 +1,33 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const maxInt = std.math.maxInt; + +const implicitBit = u64(1) << 52; + +pub extern fn __floatunsidf(arg: u32) f64 { + @setRuntimeSafety(builtin.is_test); + + if (arg == 0) return 0.0; + + // The exponent is the width of abs(a) + const exp = u64(31) - @clz(arg); + // Shift a into the significand field and clear the implicit bit + const shift = @intCast(u6, 52 - exp); + const mant = u64(arg) << shift ^ implicitBit; + + return @bitCast(f64, mant | (exp + 1023) << 52); +} + +fn test_one_floatunsidf(a: u32, expected: u64) void { + const r = __floatunsidf(a); + std.testing.expect(@bitCast(u64, r) == expected); +} + +test "floatsidf" { + // Test the produced bit pattern + test_one_floatunsidf(0, 0x0000000000000000); + test_one_floatunsidf(1, 0x3ff0000000000000); + test_one_floatunsidf(0x7FFFFFFF, 0x41dfffffffc00000); + test_one_floatunsidf(@intCast(u32, 0x80000000), 0x41e0000000000000); + test_one_floatunsidf(@intCast(u32, 0xFFFFFFFF), 0x41efffffffe00000); +} diff --git a/std/special/compiler_rt/lshrti3.zig b/std/special/compiler_rt/lshrti3.zig new file mode 100644 index 0000000000..329968ae40 --- /dev/null +++ b/std/special/compiler_rt/lshrti3.zig @@ -0,0 +1,41 @@ +const builtin = @import("builtin"); +const compiler_rt = @import("../compiler_rt.zig"); + +pub extern fn __lshrti3(a: i128, b: i32) i128 { + var input = twords{ .all = a }; + var result: twords = undefined; + + if (b > 63) { + // 64 <= b < 128 + result.s.low = input.s.high >> @intCast(u6, b - 64); + result.s.high = 0; + } else { + // 0 <= b < 64 + if (b == 0) return a; + result.s.low = input.s.high << @intCast(u6, 64 - b); + result.s.low |= input.s.low >> @intCast(u6, b); + result.s.high = input.s.high >> @intCast(u6, b); + } + + return result.all; +} + +const twords = extern union { + all: i128, + s: S, + + const S = if (builtin.endian == builtin.Endian.Little) + struct { + low: u64, + high: u64, + } + else + struct { + high: u64, + low: u64, + }; +}; + +test "import lshrti3" { + _ = @import("lshrti3_test.zig"); +} diff --git a/std/special/compiler_rt/lshrti3_test.zig b/std/special/compiler_rt/lshrti3_test.zig new file mode 100644 index 0000000000..60f83d816e --- /dev/null +++ b/std/special/compiler_rt/lshrti3_test.zig @@ -0,0 +1,46 @@ +const __lshrti3 = @import("lshrti3.zig").__lshrti3; +const testing = @import("std").testing; + +fn test__lshrti3(a: i128, b: i32, expected: i128) void { + const x = __lshrti3(a, b); + testing.expect(x == expected); +} + +test "lshrti3" { + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 0, @bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 1, @bitCast(i128, @intCast(u128, 0x7F6E5D4C3B2A190AFF6E5D4C3B2A190A))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 2, @bitCast(i128, @intCast(u128, 0x3FB72EA61D950C857FB72EA61D950C85))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 3, @bitCast(i128, @intCast(u128, 0x1FDB97530ECA8642BFDB97530ECA8642))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 4, @bitCast(i128, @intCast(u128, 0x0FEDCBA9876543215FEDCBA987654321))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 28, @bitCast(i128, @intCast(u128, 0x0000000FEDCBA9876543215FEDCBA987))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 29, @bitCast(i128, @intCast(u128, 0x00000007F6E5D4C3B2A190AFF6E5D4C3))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 30, @bitCast(i128, @intCast(u128, 0x00000003FB72EA61D950C857FB72EA61))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 31, @bitCast(i128, @intCast(u128, 0x00000001FDB97530ECA8642BFDB97530))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 32, @bitCast(i128, @intCast(u128, 0x00000000FEDCBA9876543215FEDCBA98))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 33, @bitCast(i128, @intCast(u128, 0x000000007F6E5D4C3B2A190AFF6E5D4C))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 34, @bitCast(i128, @intCast(u128, 0x000000003FB72EA61D950C857FB72EA6))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 35, @bitCast(i128, @intCast(u128, 0x000000001FDB97530ECA8642BFDB9753))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 36, @bitCast(i128, @intCast(u128, 0x000000000FEDCBA9876543215FEDCBA9))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 60, @bitCast(i128, @intCast(u128, 0x000000000000000FEDCBA9876543215F))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 61, @bitCast(i128, @intCast(u128, 0x0000000000000007F6E5D4C3B2A190AF))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 62, @bitCast(i128, @intCast(u128, 0x0000000000000003FB72EA61D950C857))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 63, @bitCast(i128, @intCast(u128, 0x0000000000000001FDB97530ECA8642B))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 64, @bitCast(i128, @intCast(u128, 0x0000000000000000FEDCBA9876543215))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 65, @bitCast(i128, @intCast(u128, 0x00000000000000007F6E5D4C3B2A190A))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 66, @bitCast(i128, @intCast(u128, 0x00000000000000003FB72EA61D950C85))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 67, @bitCast(i128, @intCast(u128, 0x00000000000000001FDB97530ECA8642))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 68, @bitCast(i128, @intCast(u128, 0x00000000000000000FEDCBA987654321))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 92, @bitCast(i128, @intCast(u128, 0x00000000000000000000000FEDCBA987))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 93, @bitCast(i128, @intCast(u128, 0x000000000000000000000007F6E5D4C3))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 94, @bitCast(i128, @intCast(u128, 0x000000000000000000000003FB72EA61))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 95, @bitCast(i128, @intCast(u128, 0x000000000000000000000001FDB97530))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 96, @bitCast(i128, @intCast(u128, 0x000000000000000000000000FEDCBA98))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 97, @bitCast(i128, @intCast(u128, 0x0000000000000000000000007F6E5D4C))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 98, @bitCast(i128, @intCast(u128, 0x0000000000000000000000003FB72EA6))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 99, @bitCast(i128, @intCast(u128, 0x0000000000000000000000001FDB9753))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 100, @bitCast(i128, @intCast(u128, 0x0000000000000000000000000FEDCBA9))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 124, @bitCast(i128, @intCast(u128, 0x0000000000000000000000000000000F))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 125, @bitCast(i128, @intCast(u128, 0x00000000000000000000000000000007))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 126, @bitCast(i128, @intCast(u128, 0x00000000000000000000000000000003))); + test__lshrti3(@bitCast(i128, @intCast(u128, 0xFEDCBA9876543215FEDCBA9876543215)), 127, @bitCast(i128, @intCast(u128, 0x00000000000000000000000000000001))); +} diff --git a/std/special/compiler_rt/modti3.zig b/std/special/compiler_rt/modti3.zig index 03222cadf5..16f2f38ba3 100644 --- a/std/special/compiler_rt/modti3.zig +++ b/std/special/compiler_rt/modti3.zig @@ -20,9 +20,9 @@ pub extern fn __modti3(a: i128, b: i128) i128 { return (@bitCast(i128, r) ^ s_a) -% s_a; // negate if s == -1 } -pub extern fn __modti3_windows_x86_64(a: *const i128, b: *const i128) void { - @setRuntimeSafety(builtin.is_test); - compiler_rt.setXmm0(i128, __modti3(a.*, b.*)); +const v128 = @Vector(2, u64); +pub extern fn __modti3_windows_x86_64(a: v128, b: v128) v128 { + return @bitCast(v128, @inlineCall(__modti3, @bitCast(i128, a), @bitCast(i128, b))); } test "import modti3" { diff --git a/std/special/compiler_rt/mulodi4.zig b/std/special/compiler_rt/mulodi4.zig new file mode 100644 index 0000000000..82e9ef3253 --- /dev/null +++ b/std/special/compiler_rt/mulodi4.zig @@ -0,0 +1,44 @@ +const builtin = @import("builtin"); +const compiler_rt = @import("../compiler_rt.zig"); +const maxInt = std.math.maxInt; +const minInt = std.math.minInt; + +pub extern fn __mulodi4(a: i64, b: i64, overflow: *c_int) i64 { + @setRuntimeSafety(builtin.is_test); + + const min = @bitCast(i64, u64(1 << (i64.bit_count - 1))); + const max = ~min; + + overflow.* = 0; + const result = a *% b; + + // Edge cases + if (a == min) { + if (b != 0 and b != 1) overflow.* = 1; + return result; + } + if (b == min) { + if (a != 0 and a != 1) overflow.* = 1; + return result; + } + + // Take absolute value of a and b via abs(x) = (x^(x >> 63)) - (x >> 63). + const abs_a = (a ^ (a >> 63)) -% (a >> 63); + const abs_b = (b ^ (b >> 63)) -% (b >> 63); + + // Unitary magnitude, cannot have overflow + if (abs_a < 2 or abs_b < 2) return result; + + // Compare the signs of the operands + if ((a ^ b) >> 63 != 0) { + if (abs_a > @divTrunc(max, abs_b)) overflow.* = 1; + } else { + if (abs_a > @divTrunc(min, -abs_b)) overflow.* = 1; + } + + return result; +} + +test "import mulodi4" { + _ = @import("mulodi4_test.zig"); +} diff --git a/std/special/compiler_rt/mulodi4_test.zig b/std/special/compiler_rt/mulodi4_test.zig new file mode 100644 index 0000000000..7575c77044 --- /dev/null +++ b/std/special/compiler_rt/mulodi4_test.zig @@ -0,0 +1,85 @@ +const __mulodi4 = @import("mulodi4.zig").__mulodi4; +const testing = @import("std").testing; + +fn test__mulodi4(a: i64, b: i64, expected: i64, expected_overflow: c_int) void { + var overflow: c_int = undefined; + const x = __mulodi4(a, b, &overflow); + testing.expect(overflow == expected_overflow and (expected_overflow != 0 or x == expected)); +} + +test "mulodi4" { + test__mulodi4(0, 0, 0, 0); + test__mulodi4(0, 1, 0, 0); + test__mulodi4(1, 0, 0, 0); + test__mulodi4(0, 10, 0, 0); + test__mulodi4(10, 0, 0, 0); + test__mulodi4(0, 81985529216486895, 0, 0); + test__mulodi4(81985529216486895, 0, 0, 0); + + test__mulodi4(0, -1, 0, 0); + test__mulodi4(-1, 0, 0, 0); + test__mulodi4(0, -10, 0, 0); + test__mulodi4(-10, 0, 0, 0); + test__mulodi4(0, -81985529216486895, 0, 0); + test__mulodi4(-81985529216486895, 0, 0, 0); + + test__mulodi4(1, 1, 1, 0); + test__mulodi4(1, 10, 10, 0); + test__mulodi4(10, 1, 10, 0); + test__mulodi4(1, 81985529216486895, 81985529216486895, 0); + test__mulodi4(81985529216486895, 1, 81985529216486895, 0); + + test__mulodi4(1, -1, -1, 0); + test__mulodi4(1, -10, -10, 0); + test__mulodi4(-10, 1, -10, 0); + test__mulodi4(1, -81985529216486895, -81985529216486895, 0); + test__mulodi4(-81985529216486895, 1, -81985529216486895, 0); + + test__mulodi4(3037000499, 3037000499, 9223372030926249001, 0); + test__mulodi4(-3037000499, 3037000499, -9223372030926249001, 0); + test__mulodi4(3037000499, -3037000499, -9223372030926249001, 0); + test__mulodi4(-3037000499, -3037000499, 9223372030926249001, 0); + + test__mulodi4(4398046511103, 2097152, 9223372036852678656, 0); + test__mulodi4(-4398046511103, 2097152, -9223372036852678656, 0); + test__mulodi4(4398046511103, -2097152, -9223372036852678656, 0); + test__mulodi4(-4398046511103, -2097152, 9223372036852678656, 0); + + test__mulodi4(2097152, 4398046511103, 9223372036852678656, 0); + test__mulodi4(-2097152, 4398046511103, -9223372036852678656, 0); + test__mulodi4(2097152, -4398046511103, -9223372036852678656, 0); + test__mulodi4(-2097152, -4398046511103, 9223372036852678656, 0); + + test__mulodi4(0x7FFFFFFFFFFFFFFF, -2, 2, 1); + test__mulodi4(-2, 0x7FFFFFFFFFFFFFFF, 2, 1); + test__mulodi4(0x7FFFFFFFFFFFFFFF, -1, @bitCast(i64, u64(0x8000000000000001)), 0); + test__mulodi4(-1, 0x7FFFFFFFFFFFFFFF, @bitCast(i64, u64(0x8000000000000001)), 0); + test__mulodi4(0x7FFFFFFFFFFFFFFF, 0, 0, 0); + test__mulodi4(0, 0x7FFFFFFFFFFFFFFF, 0, 0); + test__mulodi4(0x7FFFFFFFFFFFFFFF, 1, 0x7FFFFFFFFFFFFFFF, 0); + test__mulodi4(1, 0x7FFFFFFFFFFFFFFF, 0x7FFFFFFFFFFFFFFF, 0); + test__mulodi4(0x7FFFFFFFFFFFFFFF, 2, @bitCast(i64, u64(0x8000000000000001)), 1); + test__mulodi4(2, 0x7FFFFFFFFFFFFFFF, @bitCast(i64, u64(0x8000000000000001)), 1); + + test__mulodi4(@bitCast(i64, u64(0x8000000000000000)), -2, @bitCast(i64, u64(0x8000000000000000)), 1); + test__mulodi4(-2, @bitCast(i64, u64(0x8000000000000000)), @bitCast(i64, u64(0x8000000000000000)), 1); + test__mulodi4(@bitCast(i64, u64(0x8000000000000000)), -1, @bitCast(i64, u64(0x8000000000000000)), 1); + test__mulodi4(-1, @bitCast(i64, u64(0x8000000000000000)), @bitCast(i64, u64(0x8000000000000000)), 1); + test__mulodi4(@bitCast(i64, u64(0x8000000000000000)), 0, 0, 0); + test__mulodi4(0, @bitCast(i64, u64(0x8000000000000000)), 0, 0); + test__mulodi4(@bitCast(i64, u64(0x8000000000000000)), 1, @bitCast(i64, u64(0x8000000000000000)), 0); + test__mulodi4(1, @bitCast(i64, u64(0x8000000000000000)), @bitCast(i64, u64(0x8000000000000000)), 0); + test__mulodi4(@bitCast(i64, u64(0x8000000000000000)), 2, @bitCast(i64, u64(0x8000000000000000)), 1); + test__mulodi4(2, @bitCast(i64, u64(0x8000000000000000)), @bitCast(i64, u64(0x8000000000000000)), 1); + + test__mulodi4(@bitCast(i64, u64(0x8000000000000001)), -2, @bitCast(i64, u64(0x8000000000000001)), 1); + test__mulodi4(-2, @bitCast(i64, u64(0x8000000000000001)), @bitCast(i64, u64(0x8000000000000001)), 1); + test__mulodi4(@bitCast(i64, u64(0x8000000000000001)), -1, 0x7FFFFFFFFFFFFFFF, 0); + test__mulodi4(-1, @bitCast(i64, u64(0x8000000000000001)), 0x7FFFFFFFFFFFFFFF, 0); + test__mulodi4(@bitCast(i64, u64(0x8000000000000001)), 0, 0, 0); + test__mulodi4(0, @bitCast(i64, u64(0x8000000000000001)), 0, 0); + test__mulodi4(@bitCast(i64, u64(0x8000000000000001)), 1, @bitCast(i64, u64(0x8000000000000001)), 0); + test__mulodi4(1, @bitCast(i64, u64(0x8000000000000001)), @bitCast(i64, u64(0x8000000000000001)), 0); + test__mulodi4(@bitCast(i64, u64(0x8000000000000001)), 2, @bitCast(i64, u64(0x8000000000000000)), 1); + test__mulodi4(2, @bitCast(i64, u64(0x8000000000000001)), @bitCast(i64, u64(0x8000000000000000)), 1); +} diff --git a/std/special/compiler_rt/muloti4.zig b/std/special/compiler_rt/muloti4.zig index fd6855072b..ccde8e3e6c 100644 --- a/std/special/compiler_rt/muloti4.zig +++ b/std/special/compiler_rt/muloti4.zig @@ -1,4 +1,3 @@ -const udivmod = @import("udivmod.zig").udivmod; const builtin = @import("builtin"); const compiler_rt = @import("../compiler_rt.zig"); @@ -33,11 +32,11 @@ pub extern fn __muloti4(a: i128, b: i128, overflow: *c_int) i128 { } if (sa == sb) { - if (abs_a > @divFloor(max, abs_b)) { + if (abs_a > @divTrunc(max, abs_b)) { overflow.* = 1; } } else { - if (abs_a > @divFloor(min, -abs_b)) { + if (abs_a > @divTrunc(min, -abs_b)) { overflow.* = 1; } } @@ -45,11 +44,6 @@ pub extern fn __muloti4(a: i128, b: i128, overflow: *c_int) i128 { return r; } -pub extern fn __muloti4_windows_x86_64(a: *const i128, b: *const i128, overflow: *c_int) void { - @setRuntimeSafety(builtin.is_test); - compiler_rt.setXmm0(i128, __muloti4(a.*, b.*, overflow)); -} - test "import muloti4" { _ = @import("muloti4_test.zig"); } diff --git a/std/special/compiler_rt/multi3.zig b/std/special/compiler_rt/multi3.zig index a0c84adaf4..799b1f575d 100644 --- a/std/special/compiler_rt/multi3.zig +++ b/std/special/compiler_rt/multi3.zig @@ -14,9 +14,9 @@ pub extern fn __multi3(a: i128, b: i128) i128 { return r.all; } -pub extern fn __multi3_windows_x86_64(a: *const i128, b: *const i128) void { - @setRuntimeSafety(builtin.is_test); - compiler_rt.setXmm0(i128, __multi3(a.*, b.*)); +const v128 = @Vector(2, u64); +pub extern fn __multi3_windows_x86_64(a: v128, b: v128) v128 { + return @bitCast(v128, @inlineCall(__multi3, @bitCast(i128, a), @bitCast(i128, b))); } fn __mulddi3(a: u64, b: u64) i128 { diff --git a/std/special/compiler_rt/stack_probe.zig b/std/special/compiler_rt/stack_probe.zig new file mode 100644 index 0000000000..71a349d2a8 --- /dev/null +++ b/std/special/compiler_rt/stack_probe.zig @@ -0,0 +1,206 @@ +const builtin = @import("builtin"); + +// Zig's own stack-probe routine (available only on x86 and x86_64) +pub nakedcc fn zig_probe_stack() void { + @setRuntimeSafety(false); + + // Versions of the Linux kernel before 5.1 treat any access below SP as + // invalid so let's update it on the go, otherwise we'll get a segfault + // instead of triggering the stack growth. + + switch (builtin.arch) { + .x86_64 => { + // %rax = probe length, %rsp = stack pointer + asm volatile ( + \\ push %%rcx + \\ mov %%rax, %%rcx + \\ cmp $0x1000,%%rcx + \\ jb 2f + \\ 1: + \\ sub $0x1000,%%rsp + \\ orl $0,16(%%rsp) + \\ sub $0x1000,%%rcx + \\ cmp $0x1000,%%rcx + \\ ja 1b + \\ 2: + \\ sub %%rcx, %%rsp + \\ orl $0,16(%%rsp) + \\ add %%rax,%%rsp + \\ pop %%rcx + \\ ret + ); + }, + .i386 => { + // %eax = probe length, %esp = stack pointer + asm volatile ( + \\ push %%ecx + \\ mov %%eax, %%ecx + \\ cmp $0x1000,%%ecx + \\ jb 2f + \\ 1: + \\ sub $0x1000,%%esp + \\ orl $0,8(%%esp) + \\ sub $0x1000,%%ecx + \\ cmp $0x1000,%%ecx + \\ ja 1b + \\ 2: + \\ sub %%ecx, %%esp + \\ orl $0,8(%%esp) + \\ add %%eax,%%esp + \\ pop %%ecx + \\ ret + ); + }, + else => { } + } + + unreachable; +} + +fn win_probe_stack_only() void { + @setRuntimeSafety(false); + + switch (builtin.arch) { + .x86_64 => { + asm volatile ( + \\ push %%rcx + \\ push %%rax + \\ cmp $0x1000,%%rax + \\ lea 24(%%rsp),%%rcx + \\ jb 1f + \\ 2: + \\ sub $0x1000,%%rcx + \\ test %%rcx,(%%rcx) + \\ sub $0x1000,%%rax + \\ cmp $0x1000,%%rax + \\ ja 2b + \\ 1: + \\ sub %%rax,%%rcx + \\ test %%rcx,(%%rcx) + \\ pop %%rax + \\ pop %%rcx + \\ ret + ); + }, + .i386 => { + asm volatile ( + \\ push %%ecx + \\ push %%eax + \\ cmp $0x1000,%%eax + \\ lea 12(%%esp),%%ecx + \\ jb 1f + \\ 2: + \\ sub $0x1000,%%ecx + \\ test %%ecx,(%%ecx) + \\ sub $0x1000,%%eax + \\ cmp $0x1000,%%eax + \\ ja 2b + \\ 1: + \\ sub %%eax,%%ecx + \\ test %%ecx,(%%ecx) + \\ pop %%eax + \\ pop %%ecx + \\ ret + ); + }, + else => { } + } + + unreachable; +} + +fn win_probe_stack_adjust_sp() void { + @setRuntimeSafety(false); + + switch (builtin.arch) { + .x86_64 => { + asm volatile ( + \\ push %%rcx + \\ cmp $0x1000,%%rax + \\ lea 16(%%rsp),%%rcx + \\ jb 1f + \\ 2: + \\ sub $0x1000,%%rcx + \\ test %%rcx,(%%rcx) + \\ sub $0x1000,%%rax + \\ cmp $0x1000,%%rax + \\ ja 2b + \\ 1: + \\ sub %%rax,%%rcx + \\ test %%rcx,(%%rcx) + \\ + \\ lea 8(%%rsp),%%rax + \\ mov %%rcx,%%rsp + \\ mov -8(%%rax),%%rcx + \\ push (%%rax) + \\ sub %%rsp,%%rax + \\ ret + ); + }, + .i386 => { + asm volatile ( + \\ push %%ecx + \\ cmp $0x1000,%%eax + \\ lea 8(%%esp),%%ecx + \\ jb 1f + \\ 2: + \\ sub $0x1000,%%ecx + \\ test %%ecx,(%%ecx) + \\ sub $0x1000,%%eax + \\ cmp $0x1000,%%eax + \\ ja 2b + \\ 1: + \\ sub %%eax,%%ecx + \\ test %%ecx,(%%ecx) + \\ + \\ lea 4(%%esp),%%eax + \\ mov %%ecx,%%esp + \\ mov -4(%%eax),%%ecx + \\ push (%%eax) + \\ sub %%esp,%%eax + \\ ret + ); + }, + else => { }, + } + + unreachable; +} + +// Windows has a multitude of stack-probing functions with similar names and +// slightly different behaviours: some behave as alloca() and update the stack +// pointer after probing the stack, other do not. +// +// Function name | Adjusts the SP? | +// | x86 | x86_64 | +// ---------------------------------------- +// _chkstk (_alloca) | yes | yes | +// __chkstk | yes | no | +// __chkstk_ms | no | no | +// ___chkstk (__alloca) | yes | yes | +// ___chkstk_ms | no | no | + +pub nakedcc fn _chkstk() void { + @setRuntimeSafety(false); + @inlineCall(win_probe_stack_adjust_sp); +} +pub nakedcc fn __chkstk() void { + @setRuntimeSafety(false); + switch (builtin.arch) { + .i386 => @inlineCall(win_probe_stack_adjust_sp), + .x86_64 => @inlineCall(win_probe_stack_only), + else => unreachable + } +} +pub nakedcc fn ___chkstk() void { + @setRuntimeSafety(false); + @inlineCall(win_probe_stack_adjust_sp); +} +pub nakedcc fn __chkstk_ms() void { + @setRuntimeSafety(false); + @inlineCall(win_probe_stack_only); +} +pub nakedcc fn ___chkstk_ms() void { + @setRuntimeSafety(false); + @inlineCall(win_probe_stack_only); +} diff --git a/std/special/compiler_rt/truncXfYf2.zig b/std/special/compiler_rt/truncXfYf2.zig index b385090a93..e4c4aa38a7 100644 --- a/std/special/compiler_rt/truncXfYf2.zig +++ b/std/special/compiler_rt/truncXfYf2.zig @@ -16,6 +16,10 @@ pub extern fn __trunctfdf2(a: f128) f64 { return truncXfYf2(f64, f128, a); } +pub extern fn __truncdfsf2(a: f64) f32 { + return truncXfYf2(f32, f64, a); +} + inline fn truncXfYf2(comptime dst_t: type, comptime src_t: type, a: src_t) dst_t { const src_rep_t = @IntType(false, @typeInfo(src_t).Float.bits); const dst_rep_t = @IntType(false, @typeInfo(dst_t).Float.bits); diff --git a/std/special/compiler_rt/truncXfYf2_test.zig b/std/special/compiler_rt/truncXfYf2_test.zig index 429372c3f1..eccf7efb7e 100644 --- a/std/special/compiler_rt/truncXfYf2_test.zig +++ b/std/special/compiler_rt/truncXfYf2_test.zig @@ -200,3 +200,40 @@ test "trunctfdf2" { test__trunctfdf2(0x1.2f34dd5f437e849b4baab754cdefp+4534, 0x7ff0000000000000); test__trunctfdf2(0x1.edcbff8ad76ab5bf46463233214fp-435, 0x24cedcbff8ad76ab); } + +const __truncdfsf2 = @import("truncXfYf2.zig").__truncdfsf2; + +fn test__truncdfsf2(a: f64, expected: u32) void { + const x = __truncdfsf2(a); + + const rep = @bitCast(u32, x); + if (rep == expected) { + return; + } + // test other possible NaN representation(signal NaN) + else if (expected == 0x7fc00000) { + if ((rep & 0x7f800000) == 0x7f800000 and (rep & 0x7fffff) > 0) { + return; + } + } + + @import("std").debug.warn("got 0x{x} wanted 0x{x}\n", rep, expected); + + @panic("__trunctfsf2 test failure"); +} + +test "truncdfsf2" { + // nan & qnan + test__truncdfsf2(@bitCast(f64, u64(0x7ff8000000000000)), 0x7fc00000); + test__truncdfsf2(@bitCast(f64, u64(0x7ff0000000000001)), 0x7fc00000); + // inf + test__truncdfsf2(@bitCast(f64, u64(0x7ff0000000000000)), 0x7f800000); + test__truncdfsf2(@bitCast(f64, u64(0xfff0000000000000)), 0xff800000); + + test__truncdfsf2(0.0, 0x0); + test__truncdfsf2(1.0, 0x3f800000); + test__truncdfsf2(-1.0, 0xbf800000); + + // huge number becomes inf + test__truncdfsf2(340282366920938463463374607431768211456.0, 0x7f800000); +} diff --git a/std/special/compiler_rt/udivmodti4.zig b/std/special/compiler_rt/udivmodti4.zig index 6a037d3bae..c74dff512d 100644 --- a/std/special/compiler_rt/udivmodti4.zig +++ b/std/special/compiler_rt/udivmodti4.zig @@ -7,9 +7,10 @@ pub extern fn __udivmodti4(a: u128, b: u128, maybe_rem: ?*u128) u128 { return udivmod(u128, a, b, maybe_rem); } -pub extern fn __udivmodti4_windows_x86_64(a: *const u128, b: *const u128, maybe_rem: ?*u128) void { +const v128 = @Vector(2, u64); +pub extern fn __udivmodti4_windows_x86_64(a: v128, b: v128, maybe_rem: ?*u128) v128 { @setRuntimeSafety(builtin.is_test); - compiler_rt.setXmm0(u128, udivmod(u128, a.*, b.*, maybe_rem)); + return @bitCast(v128, udivmod(u128, @bitCast(u128, a), @bitCast(u128, b), maybe_rem)); } test "import udivmodti4" { diff --git a/std/special/compiler_rt/udivti3.zig b/std/special/compiler_rt/udivti3.zig index 510e21ac1d..ab451859bf 100644 --- a/std/special/compiler_rt/udivti3.zig +++ b/std/special/compiler_rt/udivti3.zig @@ -6,7 +6,8 @@ pub extern fn __udivti3(a: u128, b: u128) u128 { return udivmodti4.__udivmodti4(a, b, null); } -pub extern fn __udivti3_windows_x86_64(a: *const u128, b: *const u128) void { +const v128 = @Vector(2, u64); +pub extern fn __udivti3_windows_x86_64(a: v128, b: v128) v128 { @setRuntimeSafety(builtin.is_test); - udivmodti4.__udivmodti4_windows_x86_64(a, b, null); + return udivmodti4.__udivmodti4_windows_x86_64(a, b, null); } diff --git a/std/special/compiler_rt/umodti3.zig b/std/special/compiler_rt/umodti3.zig index 12aca8b036..7add0b2ffe 100644 --- a/std/special/compiler_rt/umodti3.zig +++ b/std/special/compiler_rt/umodti3.zig @@ -9,7 +9,7 @@ pub extern fn __umodti3(a: u128, b: u128) u128 { return r; } -pub extern fn __umodti3_windows_x86_64(a: *const u128, b: *const u128) void { - @setRuntimeSafety(builtin.is_test); - compiler_rt.setXmm0(u128, __umodti3(a.*, b.*)); +const v128 = @Vector(2, u64); +pub extern fn __umodti3_windows_x86_64(a: v128, b: v128) v128 { + return @bitCast(v128, @inlineCall(__umodti3, @bitCast(u128, a), @bitCast(u128, b))); } diff --git a/std/special/fmt_runner.zig b/std/special/fmt_runner.zig deleted file mode 100644 index f0ed6704ed..0000000000 --- a/std/special/fmt_runner.zig +++ /dev/null @@ -1,260 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const os = std.os; -const io = std.io; -const mem = std.mem; -const Allocator = mem.Allocator; -const ArrayList = std.ArrayList; -const Buffer = std.Buffer; -const ast = std.zig.ast; - -const arg = @import("fmt/arg.zig"); -const self_hosted_main = @import("fmt/main.zig"); -const Args = arg.Args; -const Flag = arg.Flag; -const errmsg = @import("fmt/errmsg.zig"); - -var stderr_file: os.File = undefined; -var stderr: *io.OutStream(os.File.WriteError) = undefined; -var stdout: *io.OutStream(os.File.WriteError) = undefined; - -// This brings `zig fmt` to stage 1. -pub fn main() !void { - // Here we use an ArenaAllocator backed by a DirectAllocator because `zig fmt` is a short-lived, - // one shot program. We don't need to waste time freeing memory and finding places to squish - // bytes into. So we free everything all at once at the very end. - var direct_allocator = std.heap.DirectAllocator.init(); - var arena = std.heap.ArenaAllocator.init(&direct_allocator.allocator); - const allocator = &arena.allocator; - - var stdout_file = try std.io.getStdOut(); - var stdout_out_stream = stdout_file.outStream(); - stdout = &stdout_out_stream.stream; - - stderr_file = try std.io.getStdErr(); - var stderr_out_stream = stderr_file.outStream(); - stderr = &stderr_out_stream.stream; - const args = try std.os.argsAlloc(allocator); - - var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args[1..]); - defer flags.deinit(); - - if (flags.present("help")) { - try stdout.write(self_hosted_main.usage_fmt); - os.exit(0); - } - - const color = blk: { - if (flags.single("color")) |color_flag| { - if (mem.eql(u8, color_flag, "auto")) { - break :blk errmsg.Color.Auto; - } else if (mem.eql(u8, color_flag, "on")) { - break :blk errmsg.Color.On; - } else if (mem.eql(u8, color_flag, "off")) { - break :blk errmsg.Color.Off; - } else unreachable; - } else { - break :blk errmsg.Color.Auto; - } - }; - - if (flags.present("stdin")) { - if (flags.positionals.len != 0) { - try stderr.write("cannot use --stdin with positional arguments\n"); - os.exit(1); - } - - var stdin_file = try io.getStdIn(); - var stdin = stdin_file.inStream(); - - const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size); - defer allocator.free(source_code); - - var tree = std.zig.parse(allocator, source_code) catch |err| { - try stderr.print("error parsing stdin: {}\n", err); - os.exit(1); - }; - defer tree.deinit(); - - var error_it = tree.errors.iterator(0); - while (error_it.next()) |parse_error| { - try printErrMsgToFile(allocator, parse_error, &tree, "<stdin>", stderr_file, color); - } - if (tree.errors.len != 0) { - os.exit(1); - } - if (flags.present("check")) { - const anything_changed = try std.zig.render(allocator, io.null_out_stream, &tree); - const code = if (anything_changed) u8(1) else u8(0); - os.exit(code); - } - - _ = try std.zig.render(allocator, stdout, &tree); - return; - } - - if (flags.positionals.len == 0) { - try stderr.write("expected at least one source file argument\n"); - os.exit(1); - } - - var fmt = Fmt{ - .seen = Fmt.SeenMap.init(allocator), - .any_error = false, - .color = color, - .allocator = allocator, - }; - - const check_mode = flags.present("check"); - - for (flags.positionals.toSliceConst()) |file_path| { - try fmtPath(&fmt, file_path, check_mode); - } - if (fmt.any_error) { - os.exit(1); - } -} - -const FmtError = error{ - SystemResources, - OperationAborted, - IoPending, - BrokenPipe, - Unexpected, - WouldBlock, - FileClosed, - DestinationAddressRequired, - DiskQuota, - FileTooBig, - InputOutput, - NoSpaceLeft, - AccessDenied, - OutOfMemory, - RenameAcrossMountPoints, - ReadOnlyFileSystem, - LinkQuotaExceeded, - FileBusy, -} || os.File.OpenError; - -fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void { - const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref); - defer fmt.allocator.free(file_path); - - if (try fmt.seen.put(file_path, {})) |_| return; - - const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) { - error.IsDir, error.AccessDenied => { - // TODO make event based (and dir.next()) - var dir = try std.os.Dir.open(fmt.allocator, file_path); - defer dir.close(); - - while (try dir.next()) |entry| { - if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) { - const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name }); - try fmtPath(fmt, full_path, check_mode); - } - } - return; - }, - else => { - // TODO lock stderr printing - try stderr.print("unable to open '{}': {}\n", file_path, err); - fmt.any_error = true; - return; - }, - }; - defer fmt.allocator.free(source_code); - - var tree = std.zig.parse(fmt.allocator, source_code) catch |err| { - try stderr.print("error parsing file '{}': {}\n", file_path, err); - fmt.any_error = true; - return; - }; - defer tree.deinit(); - - var error_it = tree.errors.iterator(0); - while (error_it.next()) |parse_error| { - try printErrMsgToFile(fmt.allocator, parse_error, &tree, file_path, stderr_file, fmt.color); - } - if (tree.errors.len != 0) { - fmt.any_error = true; - return; - } - - if (check_mode) { - const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, &tree); - if (anything_changed) { - try stderr.print("{}\n", file_path); - fmt.any_error = true; - } - } else { - // TODO make this evented - const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path); - defer baf.destroy(); - - const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), &tree); - if (anything_changed) { - try stderr.print("{}\n", file_path); - try baf.finish(); - } - } -} - -const Fmt = struct { - seen: SeenMap, - any_error: bool, - color: errmsg.Color, - allocator: *mem.Allocator, - - const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8); -}; - -fn printErrMsgToFile(allocator: *mem.Allocator, parse_error: *const ast.Error, tree: *ast.Tree, - path: []const u8, file: os.File, color: errmsg.Color,) !void -{ - const color_on = switch (color) { - errmsg.Color.Auto => file.isTty(), - errmsg.Color.On => true, - errmsg.Color.Off => false, - }; - const lok_token = parse_error.loc(); - const span = errmsg.Span{ - .first = lok_token, - .last = lok_token, - }; - - const first_token = tree.tokens.at(span.first); - const last_token = tree.tokens.at(span.last); - const start_loc = tree.tokenLocationPtr(0, first_token); - const end_loc = tree.tokenLocationPtr(first_token.end, last_token); - - var text_buf = try std.Buffer.initSize(allocator, 0); - var out_stream = &std.io.BufferOutStream.init(&text_buf).stream; - try parse_error.render(&tree.tokens, out_stream); - const text = text_buf.toOwnedSlice(); - - const stream = &file.outStream().stream; - if (!color_on) { - try stream.print( - "{}:{}:{}: error: {}\n", - path, - start_loc.line + 1, - start_loc.column + 1, - text, - ); - return; - } - - try stream.print( - "{}:{}:{}: error: {}\n{}\n", - path, - start_loc.line + 1, - start_loc.column + 1, - text, - tree.source[start_loc.line_start..start_loc.line_end], - ); - try stream.writeByteNTimes(' ', start_loc.column); - try stream.writeByteNTimes('~', last_token.end - first_token.start); - try stream.write("\n"); -} diff --git a/std/special/panic.zig b/std/special/panic.zig index 7cb7143955..40b1d5e7fe 100644 --- a/std/special/panic.zig +++ b/std/special/panic.zig @@ -9,10 +9,15 @@ const std = @import("std"); pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { @setCold(true); switch (builtin.os) { - // TODO: fix panic in zen. + // TODO: fix panic in zen builtin.Os.freestanding, builtin.Os.zen => { while (true) {} }, + builtin.Os.wasi => { + std.debug.warn("{}", msg); + _ = std.os.wasi.proc_raise(std.os.wasi.SIGABRT); + unreachable; + }, builtin.Os.uefi => { // TODO look into using the debug info and logging helpful messages std.os.abort(); diff --git a/std/special/test_runner.zig b/std/special/test_runner.zig index 36b098bd61..db01293059 100644 --- a/std/special/test_runner.zig +++ b/std/special/test_runner.zig @@ -8,7 +8,7 @@ pub fn main() !void { var ok_count: usize = 0; var skip_count: usize = 0; for (test_fn_list) |test_fn, i| { - warn("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name); + warn("{}/{} {}...", i + 1, test_fn_list.len, test_fn.name); if (test_fn.func()) |_| { ok_count += 1; diff --git a/std/std.zig b/std/std.zig index f68edf6435..8ec042fdb8 100644 --- a/std/std.zig +++ b/std/std.zig @@ -9,6 +9,10 @@ pub const DynLib = @import("dynamic_library.zig").DynLib; pub const HashMap = @import("hash_map.zig").HashMap; pub const LinkedList = @import("linked_list.zig").LinkedList; pub const Mutex = @import("mutex.zig").Mutex; +pub const PackedIntArrayEndian = @import("packed_int_array.zig").PackedIntArrayEndian; +pub const PackedIntArray = @import("packed_int_array.zig").PackedIntArray; +pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceEndian; +pub const PackedIntSlice = @import("packed_int_array.zig").PackedIntSlice; pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue; pub const StaticallyInitializedMutex = @import("statically_initialized_mutex.zig").StaticallyInitializedMutex; pub const SegmentedList = @import("segmented_list.zig").SegmentedList; @@ -87,6 +91,7 @@ test "std" { _ = @import("net.zig"); _ = @import("os.zig"); _ = @import("pdb.zig"); + _ = @import("packed_int_array.zig"); _ = @import("priority_queue.zig"); _ = @import("rand.zig"); _ = @import("sort.zig"); @@ -94,4 +99,6 @@ test "std" { _ = @import("unicode.zig"); _ = @import("valgrind.zig"); _ = @import("zig.zig"); + + _ = @import("debug/leb128.zig"); } diff --git a/std/zig/ast.zig b/std/zig/ast.zig index 77e487f1ef..75a811220f 100644 --- a/std/zig/ast.zig +++ b/std/zig/ast.zig @@ -18,7 +18,11 @@ pub const Tree = struct { pub const ErrorList = SegmentedList(Error, 0); pub fn deinit(self: *Tree) void { - self.arena_allocator.deinit(); + // Here we copy the arena allocator into stack memory, because + // otherwise it would destroy itself while it was still working. + var arena_allocator = self.arena_allocator; + arena_allocator.deinit(); + // self is destroyed } pub fn renderError(self: *Tree, parse_error: *Error, stream: var) !void { @@ -551,7 +555,6 @@ pub const Node = struct { doc_comments: ?*DocComment, decls: DeclList, eof_token: TokenIndex, - shebang: ?TokenIndex, pub const DeclList = SegmentedList(*Node, 4); @@ -563,7 +566,6 @@ pub const Node = struct { } pub fn firstToken(self: *const Root) TokenIndex { - if (self.shebang) |shebang| return shebang; return if (self.decls.len == 0) self.eof_token else (self.decls.at(0).*).firstToken(); } @@ -2307,7 +2309,6 @@ test "iterate" { .doc_comments = null, .decls = Node.Root.DeclList.init(std.debug.global_allocator), .eof_token = 0, - .shebang = null, }; var base = &root.base; testing.expect(base.iterate(0) == null); diff --git a/std/zig/parse.zig b/std/zig/parse.zig index 96aec714ab..efe83a7dac 100644 --- a/std/zig/parse.zig +++ b/std/zig/parse.zig @@ -9,7 +9,7 @@ const Error = ast.Error; /// Result should be freed with tree.deinit() when there are /// no more references to any of the tokens or nodes. -pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { +pub fn parse(allocator: *mem.Allocator, source: []const u8) !*ast.Tree { var tree_arena = std.heap.ArenaAllocator.init(allocator); errdefer tree_arena.deinit(); @@ -22,12 +22,12 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { .base = ast.Node{ .id = ast.Node.Id.Root }, .decls = ast.Node.Root.DeclList.init(arena), .doc_comments = null, - .shebang = null, // initialized when we get the eof token .eof_token = undefined, }; - var tree = ast.Tree{ + const tree = try arena.create(ast.Tree); + tree.* = ast.Tree{ .source = source, .root_node = root_node, .arena_allocator = tree_arena, @@ -43,15 +43,6 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } var tok_it = tree.tokens.iterator(0); - // skip over shebang line - shebang: { - const shebang_tok_index = tok_it.index; - const shebang_tok_ptr = tok_it.peek() orelse break :shebang; - if (shebang_tok_ptr.id != Token.Id.ShebangLine) break :shebang; - root_node.shebang = shebang_tok_index; - _ = tok_it.next(); - } - // skip over line comments at the top of the file while (true) { const next_tok = tok_it.peek() orelse break; @@ -67,9 +58,9 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { switch (state) { State.TopLevel => { - const comments = try eatDocComments(arena, &tok_it, &tree); + const comments = try eatDocComments(arena, &tok_it, tree); - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -150,7 +141,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, else => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); stack.append(State.TopLevel) catch unreachable; try stack.append(State{ .TopLevelExtern = TopLevelDeclCtx{ @@ -166,7 +157,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.TopLevelExtern => |ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -201,7 +192,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, else => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); stack.append(State{ .TopLevelDecl = ctx }) catch unreachable; continue; }, @@ -209,11 +200,11 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.TopLevelLibname => |ctx| { const lib_name = blk: { - const lib_name_token = nextToken(&tok_it, &tree); + const lib_name_token = nextToken(&tok_it, tree); const lib_name_token_index = lib_name_token.index; const lib_name_token_ptr = lib_name_token.ptr; - break :blk (try parseStringLiteral(arena, &tok_it, lib_name_token_ptr, lib_name_token_index, &tree)) orelse { - prevToken(&tok_it, &tree); + break :blk (try parseStringLiteral(arena, &tok_it, lib_name_token_ptr, lib_name_token_index, tree)) orelse { + prevToken(&tok_it, tree); break :blk null; }; }; @@ -230,7 +221,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.ThreadLocal => |ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -256,7 +247,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.TopLevelDecl => |ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -397,7 +388,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.TopLevelExternOrField => |ctx| { - if (eatToken(&tok_it, &tree, Token.Id.Identifier)) |identifier| { + if (eatToken(&tok_it, tree, Token.Id.Identifier)) |identifier| { const node = try arena.create(ast.Node.StructField); node.* = ast.Node.StructField{ .base = ast.Node{ .id = ast.Node.Id.StructField }, @@ -434,11 +425,11 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.FieldInitValue => |ctx| { - const eq_tok = nextToken(&tok_it, &tree); + const eq_tok = nextToken(&tok_it, tree); const eq_tok_index = eq_tok.index; const eq_tok_ptr = eq_tok.ptr; if (eq_tok_ptr.id != Token.Id.Equal) { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } stack.append(State{ .Expression = ctx }) catch unreachable; @@ -446,7 +437,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.ContainerKind => |ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; const node = try arena.create(ast.Node.ContainerDecl); @@ -479,7 +470,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.ContainerInitArgStart => |container_decl| { - if (eatToken(&tok_it, &tree, Token.Id.LParen) == null) { + if (eatToken(&tok_it, tree, Token.Id.LParen) == null) { continue; } @@ -489,24 +480,24 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.ContainerInitArg => |container_decl| { - const init_arg_token = nextToken(&tok_it, &tree); + const init_arg_token = nextToken(&tok_it, tree); const init_arg_token_index = init_arg_token.index; const init_arg_token_ptr = init_arg_token.ptr; switch (init_arg_token_ptr.id) { Token.Id.Keyword_enum => { container_decl.init_arg_expr = ast.Node.ContainerDecl.InitArg{ .Enum = null }; - const lparen_tok = nextToken(&tok_it, &tree); + const lparen_tok = nextToken(&tok_it, tree); const lparen_tok_index = lparen_tok.index; const lparen_tok_ptr = lparen_tok.ptr; if (lparen_tok_ptr.id == Token.Id.LParen) { try stack.append(State{ .ExpectToken = Token.Id.RParen }); try stack.append(State{ .Expression = OptionalCtx{ .RequiredNull = &container_decl.init_arg_expr.Enum } }); } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); } }, else => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); container_decl.init_arg_expr = ast.Node.ContainerDecl.InitArg{ .Type = undefined }; stack.append(State{ .Expression = OptionalCtx{ .Required = &container_decl.init_arg_expr.Type } }) catch unreachable; }, @@ -515,8 +506,8 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.ContainerDecl => |container_decl| { - const comments = try eatDocComments(arena, &tok_it, &tree); - const token = nextToken(&tok_it, &tree); + const comments = try eatDocComments(arena, &tok_it, tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -629,6 +620,35 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }); continue; }, + Token.Id.Keyword_comptime => { + const block = try arena.create(ast.Node.Block); + block.* = ast.Node.Block{ + .base = ast.Node{ .id = ast.Node.Id.Block }, + .label = null, + .lbrace = undefined, + .statements = ast.Node.Block.StatementList.init(arena), + .rbrace = undefined, + }; + + const node = try arena.create(ast.Node.Comptime); + node.* = ast.Node.Comptime{ + .base = ast.Node{ .id = ast.Node.Id.Comptime }, + .comptime_token = token_index, + .expr = &block.base, + .doc_comments = comments, + }; + try container_decl.fields_and_decls.push(&node.base); + + stack.append(State{ .ContainerDecl = container_decl }) catch unreachable; + try stack.append(State{ .Block = block }); + try stack.append(State{ + .ExpectTokenSave = ExpectTokenSave{ + .id = Token.Id.LBrace, + .ptr = &block.lbrace, + }, + }); + continue; + }, Token.Id.RBrace => { if (comments != null) { ((try tree.errors.addOne())).* = Error{ .UnattachedDocComment = Error.UnattachedDocComment{ .token = token_index } }; @@ -638,7 +658,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, else => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); stack.append(State{ .ContainerDecl = container_decl }) catch unreachable; try stack.append(State{ .TopLevelExtern = TopLevelDeclCtx{ @@ -690,7 +710,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.VarDeclAlign => |var_decl| { try stack.append(State{ .VarDeclSection = var_decl }); - const next_token = nextToken(&tok_it, &tree); + const next_token = nextToken(&tok_it, tree); const next_token_index = next_token.index; const next_token_ptr = next_token.ptr; if (next_token_ptr.id == Token.Id.Keyword_align) { @@ -700,13 +720,13 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; } - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; }, State.VarDeclSection => |var_decl| { try stack.append(State{ .VarDeclEq = var_decl }); - const next_token = nextToken(&tok_it, &tree); + const next_token = nextToken(&tok_it, tree); const next_token_index = next_token.index; const next_token_ptr = next_token.ptr; if (next_token_ptr.id == Token.Id.Keyword_linksection) { @@ -716,11 +736,11 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; } - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; }, State.VarDeclEq => |var_decl| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -742,7 +762,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.VarDeclSemiColon => |var_decl| { - const semicolon_token = nextToken(&tok_it, &tree); + const semicolon_token = nextToken(&tok_it, tree); if (semicolon_token.ptr.id != Token.Id.Semicolon) { ((try tree.errors.addOne())).* = Error{ @@ -756,18 +776,18 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { var_decl.semicolon_token = semicolon_token.index; - if (eatToken(&tok_it, &tree, Token.Id.DocComment)) |doc_comment_token| { + if (eatToken(&tok_it, tree, Token.Id.DocComment)) |doc_comment_token| { const loc = tree.tokenLocation(semicolon_token.ptr.end, doc_comment_token); if (loc.line == 0) { try pushDocComment(arena, doc_comment_token, &var_decl.doc_comments); } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); } } }, State.FnDef => |fn_proto| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -796,7 +816,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { try stack.append(State{ .ParamDecl = fn_proto }); try stack.append(State{ .ExpectToken = Token.Id.LParen }); - if (eatToken(&tok_it, &tree, Token.Id.Identifier)) |name_token| { + if (eatToken(&tok_it, tree, Token.Id.Identifier)) |name_token| { fn_proto.name_token = name_token; } continue; @@ -804,7 +824,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.FnProtoAlign => |fn_proto| { stack.append(State{ .FnProtoSection = fn_proto }) catch unreachable; - if (eatToken(&tok_it, &tree, Token.Id.Keyword_align)) |align_token| { + if (eatToken(&tok_it, tree, Token.Id.Keyword_align)) |align_token| { try stack.append(State{ .ExpectToken = Token.Id.RParen }); try stack.append(State{ .Expression = OptionalCtx{ .RequiredNull = &fn_proto.align_expr } }); try stack.append(State{ .ExpectToken = Token.Id.LParen }); @@ -814,7 +834,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.FnProtoSection => |fn_proto| { stack.append(State{ .FnProtoReturnType = fn_proto }) catch unreachable; - if (eatToken(&tok_it, &tree, Token.Id.Keyword_linksection)) |align_token| { + if (eatToken(&tok_it, tree, Token.Id.Keyword_linksection)) |align_token| { try stack.append(State{ .ExpectToken = Token.Id.RParen }); try stack.append(State{ .Expression = OptionalCtx{ .RequiredNull = &fn_proto.section_expr } }); try stack.append(State{ .ExpectToken = Token.Id.LParen }); @@ -822,7 +842,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.FnProtoReturnType => |fn_proto| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -845,7 +865,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } } - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); fn_proto.return_type = ast.Node.FnProto.ReturnType{ .Explicit = undefined }; stack.append(State{ .TypeExprBegin = OptionalCtx{ .Required = &fn_proto.return_type.Explicit } }) catch unreachable; continue; @@ -854,8 +874,8 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.ParamDecl => |fn_proto| { - const comments = try eatDocComments(arena, &tok_it, &tree); - if (eatToken(&tok_it, &tree, Token.Id.RParen)) |_| { + const comments = try eatDocComments(arena, &tok_it, tree); + if (eatToken(&tok_it, tree, Token.Id.RParen)) |_| { continue; } const param_decl = try arena.create(ast.Node.ParamDecl); @@ -881,9 +901,9 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.ParamDeclAliasOrComptime => |param_decl| { - if (eatToken(&tok_it, &tree, Token.Id.Keyword_comptime)) |comptime_token| { + if (eatToken(&tok_it, tree, Token.Id.Keyword_comptime)) |comptime_token| { param_decl.comptime_token = comptime_token; - } else if (eatToken(&tok_it, &tree, Token.Id.Keyword_noalias)) |noalias_token| { + } else if (eatToken(&tok_it, tree, Token.Id.Keyword_noalias)) |noalias_token| { param_decl.noalias_token = noalias_token; } continue; @@ -891,20 +911,20 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.ParamDeclName => |param_decl| { // TODO: Here, we eat two tokens in one state. This means that we can't have // comments between these two tokens. - if (eatToken(&tok_it, &tree, Token.Id.Identifier)) |ident_token| { - if (eatToken(&tok_it, &tree, Token.Id.Colon)) |_| { + if (eatToken(&tok_it, tree, Token.Id.Identifier)) |ident_token| { + if (eatToken(&tok_it, tree, Token.Id.Colon)) |_| { param_decl.name_token = ident_token; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); } } continue; }, State.ParamDeclEnd => |ctx| { - if (eatToken(&tok_it, &tree, Token.Id.Ellipsis3)) |ellipsis3| { + if (eatToken(&tok_it, tree, Token.Id.Ellipsis3)) |ellipsis3| { ctx.param_decl.var_args_token = ellipsis3; - switch (expectCommaOrEnd(&tok_it, &tree, Token.Id.RParen)) { + switch (expectCommaOrEnd(&tok_it, tree, Token.Id.RParen)) { ExpectCommaOrEndResult.end_token => |t| { if (t == null) { stack.append(State{ .ExpectToken = Token.Id.RParen }) catch unreachable; @@ -924,7 +944,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.ParamDeclComma => |fn_proto| { - switch (expectCommaOrEnd(&tok_it, &tree, Token.Id.RParen)) { + switch (expectCommaOrEnd(&tok_it, tree, Token.Id.RParen)) { ExpectCommaOrEndResult.end_token => |t| { if (t == null) { stack.append(State{ .ParamDecl = fn_proto }) catch unreachable; @@ -939,7 +959,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.MaybeLabeledExpression => |ctx| { - if (eatToken(&tok_it, &tree, Token.Id.Colon)) |_| { + if (eatToken(&tok_it, tree, Token.Id.Colon)) |_| { stack.append(State{ .LabeledExpression = LabelCtx{ .label = ctx.label, @@ -953,7 +973,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.LabeledExpression => |ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -1008,13 +1028,13 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { return tree; } - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; }, } }, State.Inline => |ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -1046,7 +1066,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { return tree; } - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; }, } @@ -1103,7 +1123,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.Else => |dest| { - if (eatToken(&tok_it, &tree, Token.Id.Keyword_else)) |else_token| { + if (eatToken(&tok_it, tree, Token.Id.Keyword_else)) |else_token| { const node = try arena.create(ast.Node.Else); node.* = ast.Node.Else{ .base = ast.Node{ .id = ast.Node.Id.Else }, @@ -1122,7 +1142,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.Block => |block| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -1131,7 +1151,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, else => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); stack.append(State{ .Block = block }) catch unreachable; try stack.append(State{ .Statement = block }); @@ -1140,7 +1160,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.Statement => |block| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -1197,7 +1217,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, else => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); const statement = try block.statements.addOne(); try stack.append(State{ .Semicolon = statement }); try stack.append(State{ .AssignmentExpressionBegin = OptionalCtx{ .Required = statement } }); @@ -1206,7 +1226,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.ComptimeStatement => |ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -1226,8 +1246,8 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, else => { - prevToken(&tok_it, &tree); - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); + prevToken(&tok_it, tree); const statement = try ctx.block.statements.addOne(); try stack.append(State{ .Semicolon = statement }); try stack.append(State{ .Expression = OptionalCtx{ .Required = statement } }); @@ -1245,11 +1265,11 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.AsmOutputItems => |items| { - const lbracket = nextToken(&tok_it, &tree); + const lbracket = nextToken(&tok_it, tree); const lbracket_index = lbracket.index; const lbracket_ptr = lbracket.ptr; if (lbracket_ptr.id != Token.Id.LBracket) { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } @@ -1280,7 +1300,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.AsmOutputReturnOrType => |node| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -1300,11 +1320,11 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.AsmInputItems => |items| { - const lbracket = nextToken(&tok_it, &tree); + const lbracket = nextToken(&tok_it, tree); const lbracket_index = lbracket.index; const lbracket_ptr = lbracket.ptr; if (lbracket_ptr.id != Token.Id.LBracket) { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } @@ -1335,16 +1355,16 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.AsmClobberItems => |items| { - while (eatToken(&tok_it, &tree, Token.Id.StringLiteral)) |strlit| { + while (eatToken(&tok_it, tree, Token.Id.StringLiteral)) |strlit| { try items.push(strlit); - if (eatToken(&tok_it, &tree, Token.Id.Comma) == null) + if (eatToken(&tok_it, tree, Token.Id.Comma) == null) break; } continue; }, State.ExprListItemOrEnd => |list_state| { - if (eatToken(&tok_it, &tree, list_state.end)) |token_index| { + if (eatToken(&tok_it, tree, list_state.end)) |token_index| { (list_state.ptr).* = token_index; continue; } @@ -1354,7 +1374,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.ExprListCommaOrEnd => |list_state| { - switch (expectCommaOrEnd(&tok_it, &tree, list_state.end)) { + switch (expectCommaOrEnd(&tok_it, tree, list_state.end)) { ExpectCommaOrEndResult.end_token => |maybe_end| if (maybe_end) |end| { (list_state.ptr).* = end; continue; @@ -1369,7 +1389,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.FieldInitListItemOrEnd => |list_state| { - if (eatToken(&tok_it, &tree, Token.Id.RBrace)) |rbrace| { + if (eatToken(&tok_it, tree, Token.Id.RBrace)) |rbrace| { (list_state.ptr).* = rbrace; continue; } @@ -1401,7 +1421,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.FieldInitListCommaOrEnd => |list_state| { - switch (expectCommaOrEnd(&tok_it, &tree, Token.Id.RBrace)) { + switch (expectCommaOrEnd(&tok_it, tree, Token.Id.RBrace)) { ExpectCommaOrEndResult.end_token => |maybe_end| if (maybe_end) |end| { (list_state.ptr).* = end; continue; @@ -1416,17 +1436,17 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.FieldListCommaOrEnd => |field_ctx| { - const end_token = nextToken(&tok_it, &tree); + const end_token = nextToken(&tok_it, tree); const end_token_index = end_token.index; const end_token_ptr = end_token.ptr; switch (end_token_ptr.id) { Token.Id.Comma => { - if (eatToken(&tok_it, &tree, Token.Id.DocComment)) |doc_comment_token| { + if (eatToken(&tok_it, tree, Token.Id.DocComment)) |doc_comment_token| { const loc = tree.tokenLocation(end_token_ptr.end, doc_comment_token); if (loc.line == 0) { try pushDocComment(arena, doc_comment_token, field_ctx.doc_comments); } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); } } @@ -1449,7 +1469,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.ErrorTagListItemOrEnd => |list_state| { - if (eatToken(&tok_it, &tree, Token.Id.RBrace)) |rbrace| { + if (eatToken(&tok_it, tree, Token.Id.RBrace)) |rbrace| { (list_state.ptr).* = rbrace; continue; } @@ -1461,7 +1481,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.ErrorTagListCommaOrEnd => |list_state| { - switch (expectCommaOrEnd(&tok_it, &tree, Token.Id.RBrace)) { + switch (expectCommaOrEnd(&tok_it, tree, Token.Id.RBrace)) { ExpectCommaOrEndResult.end_token => |maybe_end| if (maybe_end) |end| { (list_state.ptr).* = end; continue; @@ -1476,12 +1496,12 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.SwitchCaseOrEnd => |list_state| { - if (eatToken(&tok_it, &tree, Token.Id.RBrace)) |rbrace| { + if (eatToken(&tok_it, tree, Token.Id.RBrace)) |rbrace| { (list_state.ptr).* = rbrace; continue; } - const comments = try eatDocComments(arena, &tok_it, &tree); + const comments = try eatDocComments(arena, &tok_it, tree); const node = try arena.create(ast.Node.SwitchCase); node.* = ast.Node.SwitchCase{ .base = ast.Node{ .id = ast.Node.Id.SwitchCase }, @@ -1500,7 +1520,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.SwitchCaseCommaOrEnd => |list_state| { - switch (expectCommaOrEnd(&tok_it, &tree, Token.Id.RBrace)) { + switch (expectCommaOrEnd(&tok_it, tree, Token.Id.RBrace)) { ExpectCommaOrEndResult.end_token => |maybe_end| if (maybe_end) |end| { (list_state.ptr).* = end; continue; @@ -1516,7 +1536,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.SwitchCaseFirstItem => |switch_case| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (token_ptr.id == Token.Id.Keyword_else) { @@ -1535,26 +1555,26 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }); continue; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); stack.append(State{ .SwitchCaseItemCommaOrEnd = switch_case }) catch unreachable; try stack.append(State{ .RangeExpressionBegin = OptionalCtx{ .Required = try switch_case.items.addOne() } }); continue; } }, State.SwitchCaseItemOrEnd => |switch_case| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); if (token.ptr.id == Token.Id.EqualAngleBracketRight) { switch_case.arrow_token = token.index; continue; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); stack.append(State{ .SwitchCaseItemCommaOrEnd = switch_case }) catch unreachable; try stack.append(State{ .RangeExpressionBegin = OptionalCtx{ .Required = try switch_case.items.addOne() } }); continue; } }, State.SwitchCaseItemCommaOrEnd => |switch_case| { - switch (expectCommaOrEnd(&tok_it, &tree, Token.Id.EqualAngleBracketRight)) { + switch (expectCommaOrEnd(&tok_it, tree, Token.Id.EqualAngleBracketRight)) { ExpectCommaOrEndResult.end_token => |end_token| { if (end_token) |t| { switch_case.arrow_token = t; @@ -1572,14 +1592,14 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.SuspendBody => |suspend_node| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); switch (token.ptr.id) { Token.Id.Semicolon => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; }, Token.Id.LBrace => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); try stack.append(State{ .AssignmentExpressionBegin = OptionalCtx{ .RequiredNull = &suspend_node.body } }); continue; }, @@ -1589,7 +1609,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.AsyncAllocator => |async_node| { - if (eatToken(&tok_it, &tree, Token.Id.AngleBracketLeft) == null) { + if (eatToken(&tok_it, tree, Token.Id.AngleBracketLeft) == null) { continue; } @@ -1630,7 +1650,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.ExternType => |ctx| { - if (eatToken(&tok_it, &tree, Token.Id.Keyword_fn)) |fn_token| { + if (eatToken(&tok_it, tree, Token.Id.Keyword_fn)) |fn_token| { const fn_proto = try arena.create(ast.Node.FnProto); fn_proto.* = ast.Node.FnProto{ .base = ast.Node{ .id = ast.Node.Id.FnProto }, @@ -1663,7 +1683,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.SliceOrArrayAccess => |node| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -1696,7 +1716,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } }, State.SliceOrArrayType => |node| { - if (eatToken(&tok_it, &tree, Token.Id.RBracket)) |_| { + if (eatToken(&tok_it, tree, Token.Id.RBracket)) |_| { node.op = ast.Node.PrefixOp.Op{ .SliceType = ast.Node.PrefixOp.PtrInfo{ .align_info = null, @@ -1718,7 +1738,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.PtrTypeModifiers => |addr_of_info| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -1768,14 +1788,14 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, else => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; }, } }, State.AlignBitRange => |align_info| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); switch (token.ptr.id) { Token.Id.Colon => { align_info.bit_range = ast.Node.PrefixOp.PtrInfo.Align.BitRange(undefined); @@ -1798,7 +1818,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.Payload => |opt_ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (token_ptr.id != Token.Id.Pipe) { @@ -1812,7 +1832,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { return tree; } - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } @@ -1835,7 +1855,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.PointerPayload => |opt_ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (token_ptr.id != Token.Id.Pipe) { @@ -1849,7 +1869,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { return tree; } - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } @@ -1879,7 +1899,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.PointerIndexPayload => |opt_ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (token_ptr.id != Token.Id.Pipe) { @@ -1893,7 +1913,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { return tree; } - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } @@ -1927,7 +1947,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.Expression => |opt_ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -1981,7 +2001,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, else => { if (!try parseBlockExpr(&stack, arena, opt_ctx, token_ptr.*, token_index)) { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); stack.append(State{ .UnwrapExpressionBegin = opt_ctx }) catch unreachable; } continue; @@ -1996,7 +2016,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.RangeExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - if (eatToken(&tok_it, &tree, Token.Id.Ellipsis3)) |ellipsis3| { + if (eatToken(&tok_it, tree, Token.Id.Ellipsis3)) |ellipsis3| { const node = try arena.create(ast.Node.InfixOp); node.* = ast.Node.InfixOp{ .base = ast.Node{ .id = ast.Node.Id.InfixOp }, @@ -2019,7 +2039,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.AssignmentExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (tokenIdToAssignment(token_ptr.id)) |ass_id| { @@ -2036,7 +2056,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { try stack.append(State{ .Expression = OptionalCtx{ .Required = &node.rhs } }); continue; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } }, @@ -2050,7 +2070,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.UnwrapExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (tokenIdToUnwrapExpr(token_ptr.id)) |unwrap_id| { @@ -2072,7 +2092,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } continue; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } }, @@ -2086,7 +2106,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.BoolOrExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - if (eatToken(&tok_it, &tree, Token.Id.Keyword_or)) |or_token| { + if (eatToken(&tok_it, tree, Token.Id.Keyword_or)) |or_token| { const node = try arena.create(ast.Node.InfixOp); node.* = ast.Node.InfixOp{ .base = ast.Node{ .id = ast.Node.Id.InfixOp }, @@ -2111,7 +2131,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.BoolAndExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - if (eatToken(&tok_it, &tree, Token.Id.Keyword_and)) |and_token| { + if (eatToken(&tok_it, tree, Token.Id.Keyword_and)) |and_token| { const node = try arena.create(ast.Node.InfixOp); node.* = ast.Node.InfixOp{ .base = ast.Node{ .id = ast.Node.Id.InfixOp }, @@ -2136,7 +2156,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.ComparisonExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (tokenIdToComparison(token_ptr.id)) |comp_id| { @@ -2153,7 +2173,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { try stack.append(State{ .BinaryOrExpressionBegin = OptionalCtx{ .Required = &node.rhs } }); continue; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } }, @@ -2167,7 +2187,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.BinaryOrExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - if (eatToken(&tok_it, &tree, Token.Id.Pipe)) |pipe| { + if (eatToken(&tok_it, tree, Token.Id.Pipe)) |pipe| { const node = try arena.create(ast.Node.InfixOp); node.* = ast.Node.InfixOp{ .base = ast.Node{ .id = ast.Node.Id.InfixOp }, @@ -2192,7 +2212,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.BinaryXorExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - if (eatToken(&tok_it, &tree, Token.Id.Caret)) |caret| { + if (eatToken(&tok_it, tree, Token.Id.Caret)) |caret| { const node = try arena.create(ast.Node.InfixOp); node.* = ast.Node.InfixOp{ .base = ast.Node{ .id = ast.Node.Id.InfixOp }, @@ -2217,7 +2237,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.BinaryAndExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - if (eatToken(&tok_it, &tree, Token.Id.Ampersand)) |ampersand| { + if (eatToken(&tok_it, tree, Token.Id.Ampersand)) |ampersand| { const node = try arena.create(ast.Node.InfixOp); node.* = ast.Node.InfixOp{ .base = ast.Node{ .id = ast.Node.Id.InfixOp }, @@ -2242,7 +2262,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.BitShiftExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (tokenIdToBitShift(token_ptr.id)) |bitshift_id| { @@ -2259,7 +2279,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { try stack.append(State{ .AdditionExpressionBegin = OptionalCtx{ .Required = &node.rhs } }); continue; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } }, @@ -2273,7 +2293,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.AdditionExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (tokenIdToAddition(token_ptr.id)) |add_id| { @@ -2290,7 +2310,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { try stack.append(State{ .MultiplyExpressionBegin = OptionalCtx{ .Required = &node.rhs } }); continue; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } }, @@ -2304,7 +2324,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.MultiplyExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (tokenIdToMultiply(token_ptr.id)) |mult_id| { @@ -2321,7 +2341,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { try stack.append(State{ .CurlySuffixExpressionBegin = OptionalCtx{ .Required = &node.rhs } }); continue; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } }, @@ -2386,7 +2406,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.TypeExprEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - if (eatToken(&tok_it, &tree, Token.Id.Bang)) |bang| { + if (eatToken(&tok_it, tree, Token.Id.Bang)) |bang| { const node = try arena.create(ast.Node.InfixOp); node.* = ast.Node.InfixOp{ .base = ast.Node{ .id = ast.Node.Id.InfixOp }, @@ -2403,7 +2423,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.PrefixOpExpression => |opt_ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (tokenIdToPrefixOp(token_ptr.id)) |prefix_id| { @@ -2435,14 +2455,14 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { } continue; } else { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); stack.append(State{ .SuffixOpExpressionBegin = opt_ctx }) catch unreachable; continue; } }, State.SuffixOpExpressionBegin => |opt_ctx| { - if (eatToken(&tok_it, &tree, Token.Id.Keyword_async)) |async_token| { + if (eatToken(&tok_it, tree, Token.Id.Keyword_async)) |async_token| { const async_node = try arena.create(ast.Node.AsyncAttribute); async_node.* = ast.Node.AsyncAttribute{ .base = ast.Node{ .id = ast.Node.Id.AsyncAttribute }, @@ -2470,7 +2490,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { State.SuffixOpExpressionEnd => |opt_ctx| { const lhs = opt_ctx.get() orelse continue; - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; switch (token_ptr.id) { @@ -2515,7 +2535,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, Token.Id.Period => { - if (eatToken(&tok_it, &tree, Token.Id.Asterisk)) |asterisk_token| { + if (eatToken(&tok_it, tree, Token.Id.Asterisk)) |asterisk_token| { const node = try arena.create(ast.Node.SuffixOp); node.* = ast.Node.SuffixOp{ .base = ast.Node{ .id = ast.Node.Id.SuffixOp }, @@ -2527,7 +2547,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { stack.append(State{ .SuffixOpExpressionEnd = opt_ctx.toRequired() }) catch unreachable; continue; } - if (eatToken(&tok_it, &tree, Token.Id.QuestionMark)) |question_token| { + if (eatToken(&tok_it, tree, Token.Id.QuestionMark)) |question_token| { const node = try arena.create(ast.Node.SuffixOp); node.* = ast.Node.SuffixOp{ .base = ast.Node{ .id = ast.Node.Id.SuffixOp }, @@ -2554,21 +2574,21 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, else => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; }, } }, State.PrimaryExpression => |opt_ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); switch (token.ptr.id) { Token.Id.IntegerLiteral => { _ = try createToCtxLiteral(arena, opt_ctx, ast.Node.IntegerLiteral, token.index); continue; }, Token.Id.Period => { - const name_token = nextToken(&tok_it, &tree); + const name_token = nextToken(&tok_it, tree); if (name_token.ptr.id != Token.Id.Identifier) { ((try tree.errors.addOne())).* = Error{ .ExpectedToken = Error.ExpectedToken{ @@ -2624,11 +2644,11 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { .result = null, }; opt_ctx.store(&node.base); - const next_token = nextToken(&tok_it, &tree); + const next_token = nextToken(&tok_it, tree); const next_token_index = next_token.index; const next_token_ptr = next_token.ptr; if (next_token_ptr.id != Token.Id.Arrow) { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); continue; } node.result = ast.Node.PromiseType.Result{ @@ -2640,7 +2660,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, Token.Id.StringLiteral, Token.Id.MultilineStringLiteralLine => { - opt_ctx.store((try parseStringLiteral(arena, &tok_it, token.ptr, token.index, &tree)) orelse unreachable); + opt_ctx.store((try parseStringLiteral(arena, &tok_it, token.ptr, token.index, tree)) orelse unreachable); continue; }, Token.Id.LParen => { @@ -2728,7 +2748,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, Token.Id.Keyword_struct, Token.Id.Keyword_union, Token.Id.Keyword_enum => { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); stack.append(State{ .ContainerKind = ContainerKindCtx{ .opt_ctx = opt_ctx, @@ -2845,7 +2865,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, else => { if (!try parseBlockExpr(&stack, arena, opt_ctx, token.ptr.*, token.index)) { - prevToken(&tok_it, &tree); + prevToken(&tok_it, tree); if (opt_ctx != OptionalCtx.Optional) { ((try tree.errors.addOne())).* = Error{ .ExpectedPrimaryExpr = Error.ExpectedPrimaryExpr{ .token = token.index } }; return tree; @@ -2857,7 +2877,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.ErrorTypeOrSetDecl => |ctx| { - if (eatToken(&tok_it, &tree, Token.Id.LBrace) == null) { + if (eatToken(&tok_it, tree, Token.Id.LBrace) == null) { const node = try arena.create(ast.Node.InfixOp); node.* = ast.Node.InfixOp{ .base = ast.Node{ .id = ast.Node.Id.InfixOp }, @@ -2895,11 +2915,11 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.StringLiteral => |opt_ctx| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; - opt_ctx.store((try parseStringLiteral(arena, &tok_it, token_ptr, token_index, &tree)) orelse { - prevToken(&tok_it, &tree); + opt_ctx.store((try parseStringLiteral(arena, &tok_it, token_ptr, token_index, tree)) orelse { + prevToken(&tok_it, tree); if (opt_ctx != OptionalCtx.Optional) { ((try tree.errors.addOne())).* = Error{ .ExpectedPrimaryExpr = Error.ExpectedPrimaryExpr{ .token = token_index } }; return tree; @@ -2910,13 +2930,13 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.Identifier => |opt_ctx| { - if (eatToken(&tok_it, &tree, Token.Id.Identifier)) |ident_token| { + if (eatToken(&tok_it, tree, Token.Id.Identifier)) |ident_token| { _ = try createToCtxLiteral(arena, opt_ctx, ast.Node.Identifier, ident_token); continue; } if (opt_ctx != OptionalCtx.Optional) { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; ((try tree.errors.addOne())).* = Error{ @@ -2930,8 +2950,8 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.ErrorTag => |node_ptr| { - const comments = try eatDocComments(arena, &tok_it, &tree); - const ident_token = nextToken(&tok_it, &tree); + const comments = try eatDocComments(arena, &tok_it, tree); + const ident_token = nextToken(&tok_it, tree); const ident_token_index = ident_token.index; const ident_token_ptr = ident_token.ptr; if (ident_token_ptr.id != Token.Id.Identifier) { @@ -2955,7 +2975,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { }, State.ExpectToken => |token_id| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (token_ptr.id != token_id) { @@ -2970,7 +2990,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.ExpectTokenSave => |expect_token_save| { - const token = nextToken(&tok_it, &tree); + const token = nextToken(&tok_it, tree); const token_index = token.index; const token_ptr = token.ptr; if (token_ptr.id != expect_token_save.id) { @@ -2986,7 +3006,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.IfToken => |token_id| { - if (eatToken(&tok_it, &tree, token_id)) |_| { + if (eatToken(&tok_it, tree, token_id)) |_| { continue; } @@ -2994,7 +3014,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.IfTokenSave => |if_token_save| { - if (eatToken(&tok_it, &tree, if_token_save.id)) |token_index| { + if (eatToken(&tok_it, tree, if_token_save.id)) |token_index| { (if_token_save.ptr).* = token_index; continue; } @@ -3003,7 +3023,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree { continue; }, State.OptionalTokenSave => |optional_token_save| { - if (eatToken(&tok_it, &tree, optional_token_save.id)) |token_index| { + if (eatToken(&tok_it, tree, optional_token_save.id)) |token_index| { (optional_token_save.ptr).* = token_index; continue; } diff --git a/std/zig/parser_test.zig b/std/zig/parser_test.zig index 955e65eeed..6977e4e6b1 100644 --- a/std/zig/parser_test.zig +++ b/std/zig/parser_test.zig @@ -1,10 +1,3 @@ -test "temporary trivial example" { - try testCanonical( - \\const x = true; - \\ - ); -} - test "zig fmt: allowzero pointer" { try testCanonical( \\const T = [*]allowzero const u8; @@ -57,14 +50,6 @@ test "zig fmt: linksection" { ); } -test "zig fmt: shebang line" { - try testCanonical( - \\#!/usr/bin/env zig - \\pub fn main() void {} - \\ - ); -} - test "zig fmt: correctly move doc comments on struct fields" { try testTransform( \\pub const section_64 = extern struct { @@ -2125,6 +2110,21 @@ test "zig fmt: error return" { ); } +test "zig fmt: comptime block in container" { + try testCanonical( + \\pub fn container() type { + \\ return struct { + \\ comptime { + \\ if (false) { + \\ unreachable; + \\ } + \\ } + \\ }; + \\} + \\ + ); +} + const std = @import("std"); const mem = std.mem; const warn = std.debug.warn; @@ -2137,7 +2137,7 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b var stderr_file = try io.getStdErr(); var stderr = &stderr_file.outStream().stream; - var tree = try std.zig.parse2(allocator, source); + const tree = try std.zig.parse(allocator, source); defer tree.deinit(); var error_it = tree.errors.iterator(0); @@ -2170,7 +2170,7 @@ fn testParse(source: []const u8, allocator: *mem.Allocator, anything_changed: *b errdefer buffer.deinit(); var buffer_out_stream = io.BufferOutStream.init(&buffer); - anything_changed.* = try std.zig.render(allocator, &buffer_out_stream.stream, &tree); + anything_changed.* = try std.zig.render(allocator, &buffer_out_stream.stream, tree); return buffer.toOwnedSlice(); } @@ -2215,7 +2215,7 @@ fn testTransform(source: []const u8, expected_source: []const u8) !void { needed_alloc_count, failing_allocator.allocated_bytes, failing_allocator.freed_bytes, - failing_allocator.index, + failing_allocator.allocations, failing_allocator.deallocations, ); return error.MemoryLeakDetected; diff --git a/std/zig/render.zig b/std/zig/render.zig index f1fe23c2a8..cabc4ea9ef 100644 --- a/std/zig/render.zig +++ b/std/zig/render.zig @@ -73,11 +73,6 @@ fn renderRoot( ) (@typeOf(stream).Child.Error || Error)!void { var tok_it = tree.tokens.iterator(0); - // render the shebang line - if (tree.root_node.shebang) |shebang| { - try stream.write(tree.tokenSlice(shebang)); - } - // render all the line comments at the beginning of the file while (tok_it.next()) |token| { if (token.id != Token.Id.LineComment) break; @@ -753,7 +748,7 @@ fn renderExpression( counting_stream.bytes_written = 0; var dummy_col: usize = 0; try renderExpression(allocator, &counting_stream.stream, tree, 0, &dummy_col, expr.*, Space.None); - const width = counting_stream.bytes_written; + const width = @intCast(usize, counting_stream.bytes_written); const col = i % row_size; column_widths[col] = std.math.max(column_widths[col], width); expr_widths[i] = width; |
