diff --git a/build.zig b/build.zig index d924719d92..3e63325303 100644 --- a/build.zig +++ b/build.zig @@ -44,6 +44,7 @@ pub fn build(b: *Builder) !void { const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"}); + const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false; const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release; const skip_release_fast = b.option(bool, "skip-release-fast", "Main test suite skips release-fast builds") orelse skip_release; @@ -240,8 +241,10 @@ pub fn build(b: *Builder) !void { var chosen_modes: [4]builtin.Mode = undefined; var chosen_mode_index: usize = 0; - chosen_modes[chosen_mode_index] = builtin.Mode.Debug; - chosen_mode_index += 1; + if (!skip_debug) { + chosen_modes[chosen_mode_index] = builtin.Mode.Debug; + chosen_mode_index += 1; + } if (!skip_release_safe) { chosen_modes[chosen_mode_index] = builtin.Mode.ReleaseSafe; chosen_mode_index += 1; diff --git a/ci/drone/drone.yml b/ci/drone/drone.yml index 5af5fe0350..f0c4ab66d4 100644 --- a/ci/drone/drone.yml +++ b/ci/drone/drone.yml @@ -6,7 +6,38 @@ platform: arch: arm64 steps: -- name: build-and-test +- name: build + image: ziglang/static-base:llvm12-aarch64-1 + commands: + - ./ci/drone/linux_script_build + +- name: test-1 + depends_on: + - build + image: ziglang/static-base:llvm12-aarch64-1 + commands: + - ./ci/drone/linux_script_test 1 + +- name: test-2 + depends_on: + - build + image: ziglang/static-base:llvm12-aarch64-1 + commands: + - ./ci/drone/linux_script_test 2 + +- name: test-3 + depends_on: + - build + image: ziglang/static-base:llvm12-aarch64-1 + commands: + - ./ci/drone/linux_script_test 3 + +- name: finalize + depends_on: + - build + - test-1 + - test-2 + - test-3 image: ziglang/static-base:llvm12-aarch64-1 environment: SRHT_OAUTH_TOKEN: @@ -16,4 +47,4 @@ steps: AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY commands: - - ./ci/drone/linux_script + - ./ci/drone/linux_script_finalize diff --git a/ci/drone/linux_script b/ci/drone/linux_script deleted file mode 100755 index 0994604f80..0000000000 --- a/ci/drone/linux_script +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/sh - -set -x -set -e - -TRIPLEARCH="$(uname -m)" -BUILDDIR="$(pwd)" -DISTDIR="$(pwd)/dist" - -apk update -apk add py3-pip xz perl-utils jq curl samurai -pip3 install s3cmd - -# Make the `zig version` number consistent. -# This will affect the cmake command below. -git config core.abbrev 9 -git fetch --unshallow || true -git fetch --tags - -mkdir build -cd build -cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_INSTALL_PREFIX=$DISTDIR" -DZIG_STATIC=ON -DCMAKE_PREFIX_PATH=/deps/local -GNinja - -samu install -# run-translated-c tests are skipped due to: https://github.com/ziglang/zig/issues/8537 -./zig build test \ - -Dskip-release \ - -Dskip-non-native \ - -Dskip-compile-errors \ - -Dskip-run-translated-c - -if [ -z "$DRONE_PULL_REQUEST" ]; then - mv ../LICENSE "$DISTDIR/" - mv ../zig-cache/langref.html "$DISTDIR/" - mv "$DISTDIR/bin/zig" "$DISTDIR/" - rmdir "$DISTDIR/bin" - - GITBRANCH="$DRONE_BRANCH" - VERSION="$("$DISTDIR/zig" version)" - DIRNAME="zig-linux-$TRIPLEARCH-$VERSION" - TARBALL="$DIRNAME.tar.xz" - mv "$DISTDIR" "$DIRNAME" - tar cfJ "$TARBALL" "$DIRNAME" - - s3cmd put -P --add-header="cache-control: public, max-age=31536000, immutable" "$TARBALL" s3://ziglang.org/builds/ - - SHASUM=$(shasum -a 256 $TARBALL | cut '-d ' -f1) - BYTESIZE=$(wc -c < $TARBALL) - - JSONFILE="$TRIPLEARCH-linux-$GITBRANCH.json" - touch $JSONFILE - echo "{\"tarball\": \"$TARBALL\"," >>$JSONFILE - echo "\"shasum\": \"$SHASUM\"," >>$JSONFILE - echo "\"size\": \"$BYTESIZE\"}" >>$JSONFILE - - s3cmd put -P --add-header="Cache-Control: max-age=0, must-revalidate" "$JSONFILE" "s3://ziglang.org/builds/$JSONFILE" - s3cmd put -P "$JSONFILE" "s3://ziglang.org/builds/$TRIPLEARCH-linux-$VERSION.json" - if [ "$GITBRANCH" = "master" ]; then - # avoid leaking oauth token - set +x - - cd "$BUILDDIR" - ./ci/srht/on_master_success "$VERSION" "$SRHT_OAUTH_TOKEN" - fi -fi diff --git a/ci/drone/linux_script_base b/ci/drone/linux_script_base new file mode 100755 index 0000000000..2788eb2df6 --- /dev/null +++ b/ci/drone/linux_script_base @@ -0,0 +1,22 @@ +#!/bin/sh + +# https://docs.drone.io/pipeline/docker/syntax/workspace/ +# +# Drone automatically creates a temporary volume, known as your workspace, +# where it clones your repository. The workspace is the current working +# directory for each step in your pipeline. +# +# Because the workspace is a volume, filesystem changes are persisted between +# pipeline steps. In other words, individual steps can communicate and share +# state using the filesystem. +# +# Workspace volumes are ephemeral. They are created when the pipeline starts +# and destroyed after the pipeline completes. + +set -x +set -e + +TRIPLEARCH="$(uname -m)" +DISTDIR="$DRONE_WORKSPACE/dist" + +export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" diff --git a/ci/drone/linux_script_build b/ci/drone/linux_script_build new file mode 100755 index 0000000000..3aedafbb89 --- /dev/null +++ b/ci/drone/linux_script_build @@ -0,0 +1,18 @@ +#!/bin/sh + +. ./ci/drone/linux_script_base + +apk update +apk add samurai + +# Make the `zig version` number consistent. +# This will affect the cmake command below. +git config core.abbrev 9 +git fetch --unshallow || true +git fetch --tags + +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_INSTALL_PREFIX=$DISTDIR" -DZIG_STATIC=ON -DCMAKE_PREFIX_PATH=/deps/local -GNinja + +samu install diff --git a/ci/drone/linux_script_finalize b/ci/drone/linux_script_finalize new file mode 100755 index 0000000000..da754a6b54 --- /dev/null +++ b/ci/drone/linux_script_finalize @@ -0,0 +1,46 @@ +#!/bin/sh + +. ./ci/drone/linux_script_base + +if [ -n "$DRONE_PULL_REQUEST" ]; then + exit 0 +fi + +apk update +apk add py3-pip xz perl-utils jq curl samurai +pip3 install s3cmd + +cd build + +mv ../LICENSE "$DISTDIR/" +# docs are disabled due to: https://github.com/ziglang/zig/issues/8597 +#mv ../zig-cache/langref.html "$DISTDIR/" +mv "$DISTDIR/bin/zig" "$DISTDIR/" +rmdir "$DISTDIR/bin" + +GITBRANCH="$DRONE_BRANCH" +VERSION="$("$DISTDIR/zig" version)" +DIRNAME="zig-linux-$TRIPLEARCH-$VERSION" +TARBALL="$DIRNAME.tar.xz" +mv "$DISTDIR" "$DIRNAME" +tar cfJ "$TARBALL" "$DIRNAME" + +s3cmd put -P --add-header="cache-control: public, max-age=31536000, immutable" "$TARBALL" s3://ziglang.org/builds/ + +SHASUM=$(shasum -a 256 $TARBALL | cut '-d ' -f1) +BYTESIZE=$(wc -c < $TARBALL) + +JSONFILE="tarball.json" +touch $JSONFILE +echo "{\"tarball\": \"$TARBALL\"," >>$JSONFILE +echo "\"shasum\": \"$SHASUM\"," >>$JSONFILE +echo "\"size\": \"$BYTESIZE\"}" >>$JSONFILE + +s3cmd put -P "$JSONFILE" "s3://ziglang.org/builds/$TRIPLEARCH-linux-$VERSION.json" +if [ "$GITBRANCH" = "master" ]; then + # avoid leaking oauth token + set +x + + cd "$DRONE_WORKSPACE" + ./ci/srht/on_master_success "$VERSION" "$SRHT_OAUTH_TOKEN" +fi diff --git a/ci/drone/linux_script_test b/ci/drone/linux_script_test new file mode 100755 index 0000000000..a8e497a619 --- /dev/null +++ b/ci/drone/linux_script_test @@ -0,0 +1,46 @@ +#!/bin/sh + +. ./ci/drone/linux_script_base + +# only release-fast builds of test suite due to: https://github.com/ziglang/zig/issues/8597 +# +# Some test suite components will be missing because they do not support +# forcing -OReleaseFast +# +# see `zig build --help` for the full list of test-* components +case "$1" in + 1) + steps="\ + test-stage2 \ + test-fmt \ + test-behavior" + ;; + 2) + steps="test-std" + ;; + 3) + steps="\ + test-compiler-rt \ + test-minilibc \ + test-compare-output \ + test-translate-c \ + test-run-translated-c" + ;; + '') + echo "error: expecting test group argument" + exit 1 + ;; + *) + echo "error: unknown test group: $1" + exit 1 + ;; +esac + +# only release-fast builds of test suite due to: https://github.com/ziglang/zig/issues/8597 +./build/zig build \ + -Drelease \ + -Dskip-debug \ + -Dskip-release-small \ + -Dskip-release-safe \ + -Dskip-non-native \ + $steps diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 7e8a6226e6..01405b104f 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -199,7 +199,8 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF inner: Context, }; fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD { - const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; + const arg = if (@sizeOf(Context) == 0) undefined // + else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { .NoReturn => { @@ -260,7 +261,8 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF const MainFuncs = struct { fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 { - const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*; + const arg = if (@sizeOf(Context) == 0) undefined // + else @intToPtr(*Context, ctx_addr).*; switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { .NoReturn => { @@ -292,7 +294,8 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF } } fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void { - const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*; + const arg = if (@sizeOf(Context) == 0) undefined // + else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*; switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { .NoReturn => { diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index a6bab9b94a..ae960618ef 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -166,6 +166,7 @@ pub const CallingConvention = enum { APCS, AAPCS, AAPCSVFP, + SysV }; /// This data structure is used by the Zig language code generation and diff --git a/lib/std/c/haiku.zig b/lib/std/c/haiku.zig index e361a7520e..1aa3ac31e2 100644 --- a/lib/std/c/haiku.zig +++ b/lib/std/c/haiku.zig @@ -69,3 +69,51 @@ pub const pthread_rwlock_t = extern struct { writer_count: i32 = 0, waiters: [2]?*c_void = [_]?*c_void{ null, null }, }; + +pub const EAI = extern enum(c_int) { + /// address family for hostname not supported + ADDRFAMILY = 1, + + /// name could not be resolved at this time + AGAIN = 2, + + /// flags parameter had an invalid value + BADFLAGS = 3, + + /// non-recoverable failure in name resolution + FAIL = 4, + + /// address family not recognized + FAMILY = 5, + + /// memory allocation failure + MEMORY = 6, + + /// no address associated with hostname + NODATA = 7, + + /// name does not resolve + NONAME = 8, + + /// service not recognized for socket type + SERVICE = 9, + + /// intended socket type was not recognized + SOCKTYPE = 10, + + /// system error returned in errno + SYSTEM = 11, + + /// invalid value for hints + BADHINTS = 12, + + /// resolved protocol is unknown + PROTOCOL = 13, + + /// argument buffer overflow + OVERFLOW = 14, + + _, +}; + +pub const EAI_MAX = 15; diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 18f3951abb..56e3ec19a2 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -264,7 +264,7 @@ pub const ChildProcess = struct { // TODO collect output in a deadlock-avoiding way on Windows. // https://github.com/ziglang/zig/issues/6343 - if (builtin.os.tag == .windows) { + if (builtin.os.tag == .windows or builtin.os.tag == .haiku) { const stdout_in = child.stdout.?.reader(); const stderr_in = child.stderr.?.reader(); diff --git a/lib/std/crypto/25519/edwards25519.zig b/lib/std/crypto/25519/edwards25519.zig index 13dacc77f9..7b95fa9315 100644 --- a/lib/std/crypto/25519/edwards25519.zig +++ b/lib/std/crypto/25519/edwards25519.zig @@ -75,16 +75,8 @@ pub const Edwards25519 = struct { .is_base = true, }; - /// The edwards25519 neutral element. - pub const neutralElement = Edwards25519{ - .x = Fe{ .limbs = .{ 2251799813685229, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247 } }, - .y = Fe{ .limbs = .{ 1507481815385608, 2223447444246085, 1083941587175919, 2059929906842505, 1581435440146976 } }, - .z = Fe{ .limbs = .{ 1507481815385608, 2223447444246085, 1083941587175919, 2059929906842505, 1581435440146976 } }, - .t = Fe{ .limbs = .{ 2251799813685229, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247 } }, - .is_base = false, - }; - - const identityElement = Edwards25519{ .x = Fe.zero, .y = Fe.one, .z = Fe.one, .t = Fe.zero }; + pub const neutralElement = @compileError("deprecated: use identityElement instead"); + pub const identityElement = Edwards25519{ .x = Fe.zero, .y = Fe.one, .z = Fe.one, .t = Fe.zero }; /// Reject the neutral element. pub fn rejectIdentity(p: Edwards25519) IdentityElementError!void { @@ -160,9 +152,10 @@ pub const Edwards25519 = struct { return t; } - fn nonAdjacentForm(s: [32]u8) [2 * 32]i8 { + fn slide(s: [32]u8) [2 * 32]i8 { + const reduced = if ((s[s.len - 1] & 0x80) != 0) s else scalar.reduce(s); var e: [2 * 32]i8 = undefined; - for (s) |x, i| { + for (reduced) |x, i| { e[i * 2 + 0] = @as(i8, @truncate(u4, x)); e[i * 2 + 1] = @as(i8, @truncate(u4, x >> 4)); } @@ -185,7 +178,7 @@ pub const Edwards25519 = struct { // avoid these to keep the standard library lightweight. fn pcMul(pc: [9]Edwards25519, s: [32]u8, comptime vartime: bool) IdentityElementError!Edwards25519 { std.debug.assert(vartime); - const e = nonAdjacentForm(s); + const e = slide(s); var q = Edwards25519.identityElement; var pos: usize = 2 * 32 - 1; while (true) : (pos -= 1) { @@ -280,8 +273,8 @@ pub const Edwards25519 = struct { xpc[4].rejectIdentity() catch return error.WeakPublicKey; break :pc xpc; }; - const e1 = nonAdjacentForm(s1); - const e2 = nonAdjacentForm(s2); + const e1 = slide(s1); + const e2 = slide(s2); var q = Edwards25519.identityElement; var pos: usize = 2 * 32 - 1; while (true) : (pos -= 1) { @@ -318,7 +311,7 @@ pub const Edwards25519 = struct { } var es: [count][2 * 32]i8 = undefined; for (ss) |s, i| { - es[i] = nonAdjacentForm(s); + es[i] = slide(s); } var q = Edwards25519.identityElement; var pos: usize = 2 * 32 - 1; diff --git a/lib/std/crypto/25519/field.zig b/lib/std/crypto/25519/field.zig index 5ac184080c..aae53e9081 100644 --- a/lib/std/crypto/25519/field.zig +++ b/lib/std/crypto/25519/field.zig @@ -355,7 +355,7 @@ pub const Fe = struct { return fe; } - /// Compute the inverse of a field element + /// Return the inverse of a field element, or 0 if a=0. pub fn invert(a: Fe) Fe { var t0 = a.sq(); var t1 = t0.sqn(2).mul(a); diff --git a/lib/std/crypto/25519/scalar.zig b/lib/std/crypto/25519/scalar.zig index 21578486e8..e8564ff577 100644 --- a/lib/std/crypto/25519/scalar.zig +++ b/lib/std/crypto/25519/scalar.zig @@ -98,7 +98,7 @@ pub fn sub(a: [32]u8, b: [32]u8) [32]u8 { return add(a, neg(b)); } -/// A scalar in unpacked reprentation +/// A scalar in unpacked representation pub const Scalar = struct { const Limbs = [5]u64; limbs: Limbs = undefined, diff --git a/lib/std/crypto/utils.zig b/lib/std/crypto/utils.zig index 4e02092ed8..d20c0d3ff7 100644 --- a/lib/std/crypto/utils.zig +++ b/lib/std/crypto/utils.zig @@ -1,7 +1,11 @@ const std = @import("../std.zig"); +const debug = std.debug; const mem = std.mem; const testing = std.testing; +const Endian = std.builtin.Endian; +const Order = std.math.Order; + /// Compares two arrays in constant time (for a given length) and returns whether they are equal. /// This function was designed to compare short cryptographic secrets (MACs, signatures). /// For all other applications, use mem.eql() instead. @@ -38,6 +42,41 @@ pub fn timingSafeEql(comptime T: type, a: T, b: T) bool { } } +/// Compare two integers serialized as arrays of the same size, in constant time. +/// Returns .lt if ab and .eq if a=b +pub fn timingSafeCompare(comptime T: type, a: []const T, b: []const T, endian: Endian) Order { + debug.assert(a.len == b.len); + const bits = switch (@typeInfo(T)) { + .Int => |cinfo| if (cinfo.signedness != .unsigned) @compileError("Elements to be compared must be unsigned") else cinfo.bits, + else => @compileError("Elements to be compared must be integers"), + }; + comptime const Cext = std.meta.Int(.unsigned, bits + 1); + var gt: T = 0; + var eq: T = 1; + if (endian == .Little) { + var i = a.len; + while (i != 0) { + i -= 1; + const x1 = a[i]; + const x2 = b[i]; + gt |= @truncate(T, (@as(Cext, x2) -% @as(Cext, x1)) >> bits) & eq; + eq &= @truncate(T, (@as(Cext, (x2 ^ x1)) -% 1) >> bits); + } + } else { + for (a) |x1, i| { + const x2 = b[i]; + gt |= @truncate(T, (@as(Cext, x2) -% @as(Cext, x1)) >> bits) & eq; + eq &= @truncate(T, (@as(Cext, (x2 ^ x1)) -% 1) >> bits); + } + } + if (gt != 0) { + return Order.gt; + } else if (eq != 0) { + return Order.eq; + } + return Order.lt; +} + /// Sets a slice to zeroes. /// Prevents the store from being optimized out. pub fn secureZero(comptime T: type, s: []T) void { @@ -70,6 +109,19 @@ test "crypto.utils.timingSafeEql (vectors)" { testing.expect(timingSafeEql(std.meta.Vector(100, u8), v1, v3)); } +test "crypto.utils.timingSafeCompare" { + var a = [_]u8{10} ** 32; + var b = [_]u8{10} ** 32; + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Big), .eq); + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .eq); + a[31] = 1; + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Big), .lt); + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .lt); + a[0] = 20; + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Big), .gt); + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .lt); +} + test "crypto.utils.secureZero" { var a = [_]u8{0xfe} ** 8; var b = [_]u8{0xfe} ** 8; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 89c5119156..68828ad24c 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -339,6 +339,13 @@ pub const StackIterator = struct { fp: usize, pub fn init(first_address: ?usize, fp: ?usize) StackIterator { + if (native_arch == .sparcv9) { + // Flush all the register windows on stack. + asm volatile ( + \\ flushw + ::: "memory"); + } + return StackIterator{ .first_address = first_address, .fp = fp orelse @frameAddress(), @@ -346,18 +353,18 @@ pub const StackIterator = struct { } // Offset of the saved BP wrt the frame pointer. - const fp_offset = if (native_arch.isRISCV()) + const fp_offset = if (comptime native_arch.isRISCV()) // On RISC-V the frame pointer points to the top of the saved register // area, on pretty much every other architecture it points to the stack // slot where the previous frame pointer is saved. 2 * @sizeOf(usize) - else if (native_arch.isSPARC()) + else if (comptime native_arch.isSPARC()) // On SPARC the previous frame pointer is stored at 14 slots past %fp+BIAS. 14 * @sizeOf(usize) else 0; - const fp_bias = if (native_arch.isSPARC()) + const fp_bias = if (comptime native_arch.isSPARC()) // On SPARC frame pointers are biased by a constant. 2047 else @@ -383,7 +390,7 @@ pub const StackIterator = struct { } fn next_internal(self: *StackIterator) ?usize { - const fp = if (native_arch.isSPARC()) + const fp = if (comptime native_arch.isSPARC()) // On SPARC the offset is positive. (!) math.add(usize, self.fp, fp_offset) catch return null else diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index b3cf307c16..5d84138159 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1523,9 +1523,11 @@ test "parseUnsigned" { } pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; +pub const parseHexFloat = @import("fmt/parse_hex_float.zig").parseHexFloat; -test "parseFloat" { +test { _ = @import("fmt/parse_float.zig"); + _ = @import("fmt/parse_hex_float.zig"); } pub fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) { diff --git a/lib/std/fmt/parse_hex_float.zig b/lib/std/fmt/parse_hex_float.zig new file mode 100644 index 0000000000..4bff267a82 --- /dev/null +++ b/lib/std/fmt/parse_hex_float.zig @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software.const std = @import("std"); +// +// The rounding logic is inspired by LLVM's APFloat and Go's atofHex +// implementation. + +const std = @import("std"); +const ascii = std.ascii; +const fmt = std.fmt; +const math = std.math; +const testing = std.testing; + +const assert = std.debug.assert; + +pub fn parseHexFloat(comptime T: type, s: []const u8) !T { + assert(@typeInfo(T) == .Float); + + const IntT = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); + + const mantissa_bits = math.floatMantissaBits(T); + const exponent_bits = math.floatExponentBits(T); + + const sign_shift = mantissa_bits + exponent_bits; + + const exponent_bias = (1 << (exponent_bits - 1)) - 1; + const exponent_min = 1 - exponent_bias; + const exponent_max = exponent_bias; + + if (s.len == 0) + return error.InvalidCharacter; + + if (ascii.eqlIgnoreCase(s, "nan")) { + return math.nan(T); + } else if (ascii.eqlIgnoreCase(s, "inf") or ascii.eqlIgnoreCase(s, "+inf")) { + return math.inf(T); + } else if (ascii.eqlIgnoreCase(s, "-inf")) { + return -math.inf(T); + } + + var negative: bool = false; + var exp_negative: bool = false; + + var mantissa: u128 = 0; + var exponent: i16 = 0; + var frac_scale: i16 = 0; + + const State = enum { + MaybeSign, + Prefix, + LeadingIntegerDigit, + IntegerDigit, + MaybeDot, + LeadingFractionDigit, + FractionDigit, + ExpPrefix, + MaybeExpSign, + ExpDigit, + }; + + var state = State.MaybeSign; + + var i: usize = 0; + while (i < s.len) { + const c = s[i]; + + switch (state) { + .MaybeSign => { + state = .Prefix; + + if (c == '+') { + i += 1; + } else if (c == '-') { + negative = true; + i += 1; + } + }, + .Prefix => { + state = .LeadingIntegerDigit; + + // Match both 0x and 0X. + if (i + 2 > s.len or s[i] != '0' or s[i + 1] | 32 != 'x') + return error.InvalidCharacter; + i += 2; + }, + .LeadingIntegerDigit => { + if (c == '0') { + // Skip leading zeros. + i += 1; + } else if (c == '_') { + return error.InvalidCharacter; + } else { + state = .IntegerDigit; + } + }, + .IntegerDigit => { + if (ascii.isXDigit(c)) { + if (mantissa >= math.maxInt(u128) / 16) + return error.Overflow; + mantissa *%= 16; + mantissa += try fmt.charToDigit(c, 16); + i += 1; + } else if (c == '_') { + i += 1; + } else { + state = .MaybeDot; + } + }, + .MaybeDot => { + if (c == '.') { + state = .LeadingFractionDigit; + i += 1; + } else state = .ExpPrefix; + }, + .LeadingFractionDigit => { + if (c == '_') { + return error.InvalidCharacter; + } else state = .FractionDigit; + }, + .FractionDigit => { + if (ascii.isXDigit(c)) { + if (mantissa < math.maxInt(u128) / 16) { + mantissa *%= 16; + mantissa +%= try fmt.charToDigit(c, 16); + frac_scale += 1; + } else if (c != '0') { + return error.Overflow; + } + i += 1; + } else if (c == '_') { + i += 1; + } else { + state = .ExpPrefix; + } + }, + .ExpPrefix => { + state = .MaybeExpSign; + // Match both p and P. + if (c | 32 != 'p') + return error.InvalidCharacter; + i += 1; + }, + .MaybeExpSign => { + state = .ExpDigit; + + if (c == '+') { + i += 1; + } else if (c == '-') { + exp_negative = true; + i += 1; + } + }, + .ExpDigit => { + if (ascii.isXDigit(c)) { + if (exponent >= math.maxInt(i16) / 10) + return error.Overflow; + exponent *%= 10; + exponent +%= try fmt.charToDigit(c, 10); + i += 1; + } else if (c == '_') { + i += 1; + } else { + return error.InvalidCharacter; + } + }, + } + } + + if (exp_negative) + exponent *= -1; + + // Bring the decimal part to the left side of the decimal dot. + exponent -= frac_scale * 4; + + if (mantissa == 0) { + // Signed zero. + return if (negative) -0.0 else 0.0; + } + + // Divide by 2^mantissa_bits to right-align the mantissa in the fractional + // part. + exponent += mantissa_bits; + + // Keep around two extra bits to correctly round any value that doesn't fit + // the available mantissa bits. The result LSB serves as Guard bit, the + // following one is the Round bit and the last one is the Sticky bit, + // computed by OR-ing all the dropped bits. + + // Normalize by aligning the implicit one bit. + while (mantissa >> (mantissa_bits + 2) == 0) { + mantissa <<= 1; + exponent -= 1; + } + + // Normalize again by dropping the excess precision. + // Note that the discarded bits are folded into the Sticky bit. + while (mantissa >> (mantissa_bits + 2 + 1) != 0) { + mantissa = mantissa >> 1 | (mantissa & 1); + exponent += 1; + } + + // Very small numbers can be possibly represented as denormals, reduce the + // exponent as much as possible. + while (mantissa != 0 and exponent < exponent_min - 2) { + mantissa = mantissa >> 1 | (mantissa & 1); + exponent += 1; + } + + // There are two cases to handle: + // - We've truncated more than 0.5ULP (R=S=1), increase the mantissa. + // - We've truncated exactly 0.5ULP (R=1 S=0), increase the mantissa if the + // result is odd (G=1). + // The two checks can be neatly folded as follows. + mantissa |= @boolToInt(mantissa & 0b100 != 0); + mantissa += 1; + + mantissa >>= 2; + exponent += 2; + + if (mantissa & (1 << (mantissa_bits + 1)) != 0) { + // Renormalize, if the exponent overflows we'll catch that below. + mantissa >>= 1; + exponent += 1; + } + + if (mantissa >> mantissa_bits == 0) { + // This is a denormal number, the biased exponent is zero. + exponent = -exponent_bias; + } + + if (exponent > exponent_max) { + // Overflow, return +inf. + return math.inf(T); + } + + // Remove the implicit bit. + mantissa &= @as(u128, (1 << mantissa_bits) - 1); + + const raw: IntT = + (if (negative) @as(IntT, 1) << sign_shift else 0) | + @as(IntT, @bitCast(u16, exponent + exponent_bias)) << mantissa_bits | + @truncate(IntT, mantissa); + + return @bitCast(T, raw); +} + +test "special" { + testing.expect(math.isNan(try parseHexFloat(f32, "nAn"))); + testing.expect(math.isPositiveInf(try parseHexFloat(f32, "iNf"))); + testing.expect(math.isPositiveInf(try parseHexFloat(f32, "+Inf"))); + testing.expect(math.isNegativeInf(try parseHexFloat(f32, "-iNf"))); +} +test "zero" { + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0")); + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "-0x0")); + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0p42")); + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "-0x0.00000p42")); + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0.00000p666")); +} + +test "f16" { + const Case = struct { s: []const u8, v: f16 }; + const cases: []const Case = &[_]Case{ + .{ .s = "0x1p0", .v = 1.0 }, + .{ .s = "-0x1p-1", .v = -0.5 }, + .{ .s = "0x10p+10", .v = 16384.0 }, + .{ .s = "0x10p-10", .v = 0.015625 }, + // Max normalized value. + .{ .s = "0x1.ffcp+15", .v = math.f16_max }, + .{ .s = "-0x1.ffcp+15", .v = -math.f16_max }, + // Min normalized value. + .{ .s = "0x1p-14", .v = math.f16_min }, + .{ .s = "-0x1p-14", .v = -math.f16_min }, + // Min denormal value. + .{ .s = "0x1p-24", .v = math.f16_true_min }, + .{ .s = "-0x1p-24", .v = -math.f16_true_min }, + }; + + for (cases) |case| { + testing.expectEqual(case.v, try parseHexFloat(f16, case.s)); + } +} +test "f32" { + const Case = struct { s: []const u8, v: f32 }; + const cases: []const Case = &[_]Case{ + .{ .s = "0x1p0", .v = 1.0 }, + .{ .s = "-0x1p-1", .v = -0.5 }, + .{ .s = "0x10p+10", .v = 16384.0 }, + .{ .s = "0x10p-10", .v = 0.015625 }, + .{ .s = "0x0.ffffffp128", .v = 0x0.ffffffp128 }, + .{ .s = "0x0.1234570p-125", .v = 0x0.1234570p-125 }, + // Max normalized value. + .{ .s = "0x1.fffffeP+127", .v = math.f32_max }, + .{ .s = "-0x1.fffffeP+127", .v = -math.f32_max }, + // Min normalized value. + .{ .s = "0x1p-126", .v = math.f32_min }, + .{ .s = "-0x1p-126", .v = -math.f32_min }, + // Min denormal value. + .{ .s = "0x1P-149", .v = math.f32_true_min }, + .{ .s = "-0x1P-149", .v = -math.f32_true_min }, + }; + + for (cases) |case| { + testing.expectEqual(case.v, try parseHexFloat(f32, case.s)); + } +} +test "f64" { + const Case = struct { s: []const u8, v: f64 }; + const cases: []const Case = &[_]Case{ + .{ .s = "0x1p0", .v = 1.0 }, + .{ .s = "-0x1p-1", .v = -0.5 }, + .{ .s = "0x10p+10", .v = 16384.0 }, + .{ .s = "0x10p-10", .v = 0.015625 }, + // Max normalized value. + .{ .s = "0x1.fffffffffffffp+1023", .v = math.f64_max }, + .{ .s = "-0x1.fffffffffffffp1023", .v = -math.f64_max }, + // Min normalized value. + .{ .s = "0x1p-1022", .v = math.f64_min }, + .{ .s = "-0x1p-1022", .v = -math.f64_min }, + // Min denormalized value. + .{ .s = "0x1p-1074", .v = math.f64_true_min }, + .{ .s = "-0x1p-1074", .v = -math.f64_true_min }, + }; + + for (cases) |case| { + testing.expectEqual(case.v, try parseHexFloat(f64, case.s)); + } +} +test "f128" { + const Case = struct { s: []const u8, v: f128 }; + const cases: []const Case = &[_]Case{ + .{ .s = "0x1p0", .v = 1.0 }, + .{ .s = "-0x1p-1", .v = -0.5 }, + .{ .s = "0x10p+10", .v = 16384.0 }, + .{ .s = "0x10p-10", .v = 0.015625 }, + // Max normalized value. + .{ .s = "0xf.fffffffffffffffffffffffffff8p+16380", .v = math.f128_max }, + .{ .s = "-0xf.fffffffffffffffffffffffffff8p+16380", .v = -math.f128_max }, + // Min normalized value. + .{ .s = "0x1p-16382", .v = math.f128_min }, + .{ .s = "-0x1p-16382", .v = -math.f128_min }, + // // Min denormalized value. + .{ .s = "0x1p-16494", .v = math.f128_true_min }, + .{ .s = "-0x1p-16494", .v = -math.f128_true_min }, + }; + + for (cases) |case| { + testing.expectEqual(@bitCast(u128, case.v), @bitCast(u128, try parseHexFloat(f128, case.s))); + } +} diff --git a/lib/std/json/test.zig b/lib/std/json/test.zig index b6ff03350c..b0d873c910 100644 --- a/lib/std/json/test.zig +++ b/lib/std/json/test.zig @@ -29,8 +29,7 @@ fn ok(s: []const u8) !void { fn err(s: []const u8) void { testing.expect(!json.validate(s)); - testNonStreaming(s) catch return; - testing.expect(false); + testing.expect(std.meta.isError(testNonStreaming(s))); } fn utf8Error(s: []const u8) void { @@ -48,8 +47,7 @@ fn any(s: []const u8) void { fn anyStreamingErrNonStreaming(s: []const u8) void { _ = json.validate(s); - testNonStreaming(s) catch return; - testing.expect(false); + testing.expect(std.meta.isError(testNonStreaming(s))); } fn roundTrip(s: []const u8) !void { diff --git a/lib/std/mem.zig b/lib/std/mem.zig index fc01599864..b35c349052 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1877,7 +1877,11 @@ test "rotate" { /// Replace needle with replacement as many times as possible, writing to an output buffer which is assumed to be of /// appropriate size. Use replacementSize to calculate an appropriate buffer size. +/// The needle must not be empty. pub fn replace(comptime T: type, input: []const T, needle: []const T, replacement: []const T, output: []T) usize { + // Empty needle will loop until output buffer overflows. + assert(needle.len > 0); + var i: usize = 0; var slide: usize = 0; var replacements: usize = 0; @@ -1900,22 +1904,48 @@ pub fn replace(comptime T: type, input: []const T, needle: []const T, replacemen test "replace" { var output: [29]u8 = undefined; var replacements = replace(u8, "All your base are belong to us", "base", "Zig", output[0..]); + var expected: []const u8 = "All your Zig are belong to us"; testing.expect(replacements == 1); - testing.expect(eql(u8, output[0..], "All your Zig are belong to us")); + testing.expectEqualStrings(expected, output[0..expected.len]); replacements = replace(u8, "Favor reading code over writing code.", "code", "", output[0..]); + expected = "Favor reading over writing ."; testing.expect(replacements == 2); - testing.expect(eql(u8, output[0..], "Favor reading over writing .")); + testing.expectEqualStrings(expected, output[0..expected.len]); + + // Empty needle is not allowed but input may be empty. + replacements = replace(u8, "", "x", "y", output[0..0]); + expected = ""; + testing.expect(replacements == 0); + testing.expectEqualStrings(expected, output[0..expected.len]); + + // Adjacent replacements. + + replacements = replace(u8, "\\n\\n", "\\n", "\n", output[0..]); + expected = "\n\n"; + testing.expect(replacements == 2); + testing.expectEqualStrings(expected, output[0..expected.len]); + + replacements = replace(u8, "abbba", "b", "cd", output[0..]); + expected = "acdcdcda"; + testing.expect(replacements == 3); + testing.expectEqualStrings(expected, output[0..expected.len]); } /// Calculate the size needed in an output buffer to perform a replacement. +/// The needle must not be empty. pub fn replacementSize(comptime T: type, input: []const T, needle: []const T, replacement: []const T) usize { + // Empty needle will loop forever. + assert(needle.len > 0); + var i: usize = 0; var size: usize = input.len; - while (i < input.len) : (i += 1) { + while (i < input.len) { if (mem.indexOf(T, input[i..], needle) == @as(usize, 0)) { size = size - needle.len + replacement.len; i += needle.len; + } else { + i += 1; } } @@ -1924,9 +1954,15 @@ pub fn replacementSize(comptime T: type, input: []const T, needle: []const T, re test "replacementSize" { testing.expect(replacementSize(u8, "All your base are belong to us", "base", "Zig") == 29); - testing.expect(replacementSize(u8, "", "", "") == 0); testing.expect(replacementSize(u8, "Favor reading code over writing code.", "code", "") == 29); testing.expect(replacementSize(u8, "Only one obvious way to do things.", "things.", "things in Zig.") == 41); + + // Empty needle is not allowed but input may be empty. + testing.expect(replacementSize(u8, "", "x", "y") == 0); + + // Adjacent replacements. + testing.expect(replacementSize(u8, "\\n\\n", "\\n", "\n") == 2); + testing.expect(replacementSize(u8, "abbba", "b", "cd") == 8); } /// Perform a replacement on an allocated buffer of pre-determined size. Caller must free returned memory. diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 83ef3f83e0..a09ddd7307 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -117,10 +117,21 @@ test "std.meta.bitCount" { testing.expect(bitCount(f32) == 32); } +/// Returns the alignment of type T. +/// Note that if T is a pointer or function type the result is different than +/// the one returned by @alignOf(T). +/// If T is a pointer type the alignment of the type it points to is returned. +/// If T is a function type the alignment a target-dependent value is returned. pub fn alignment(comptime T: type) comptime_int { - //@alignOf works on non-pointer types - const P = if (comptime trait.is(.Pointer)(T)) T else *T; - return @typeInfo(P).Pointer.alignment; + return switch (@typeInfo(T)) { + .Optional => |info| switch (@typeInfo(info.child)) { + .Pointer, .Fn => alignment(info.child), + else => @alignOf(T), + }, + .Pointer => |info| info.alignment, + .Fn => |info| info.alignment, + else => @alignOf(T), + }; } test "std.meta.alignment" { @@ -129,6 +140,8 @@ test "std.meta.alignment" { testing.expect(alignment(*align(2) u8) == 2); testing.expect(alignment([]align(1) u8) == 1); testing.expect(alignment([]align(2) u8) == 2); + testing.expect(alignment(fn () void) > 0); + testing.expect(alignment(fn () align(128) void) == 128); } pub fn Child(comptime T: type) type { @@ -1342,3 +1355,13 @@ test "shuffleVectorIndex" { testing.expect(shuffleVectorIndex(6, vector_len) == -3); testing.expect(shuffleVectorIndex(7, vector_len) == -4); } + +/// Returns whether `error_union` contains an error. +pub fn isError(error_union: anytype) bool { + return if (error_union) |_| false else |_| true; +} + +test "isError" { + std.testing.expect(isError(math.absInt(@as(i8, -128)))); + std.testing.expect(!isError(math.absInt(@as(i8, -127)))); +} diff --git a/lib/std/os/bits/haiku.zig b/lib/std/os/bits/haiku.zig index 1b6e7a3378..0be900d839 100644 --- a/lib/std/os/bits/haiku.zig +++ b/lib/std/os/bits/haiku.zig @@ -279,20 +279,10 @@ pub const PROT_READ = 1; pub const PROT_WRITE = 2; pub const PROT_EXEC = 4; -pub const CLOCK_REALTIME = 0; -pub const CLOCK_VIRTUAL = 1; -pub const CLOCK_PROF = 2; -pub const CLOCK_MONOTONIC = 4; -pub const CLOCK_UPTIME = 5; -pub const CLOCK_UPTIME_PRECISE = 7; -pub const CLOCK_UPTIME_FAST = 8; -pub const CLOCK_REALTIME_PRECISE = 9; -pub const CLOCK_REALTIME_FAST = 10; -pub const CLOCK_MONOTONIC_PRECISE = 11; -pub const CLOCK_MONOTONIC_FAST = 12; -pub const CLOCK_SECOND = 13; -pub const CLOCK_THREAD_CPUTIME_ID = 14; -pub const CLOCK_PROCESS_CPUTIME_ID = 15; +pub const CLOCK_MONOTONIC = 0; +pub const CLOCK_REALTIME = -1; +pub const CLOCK_PROCESS_CPUTIME_ID = -2; +pub const CLOCK_THREAD_CPUTIME_ID = -3; pub const MAP_FAILED = @intToPtr(*c_void, maxInt(usize)); pub const MAP_SHARED = 0x0001; @@ -310,58 +300,59 @@ pub const MAP_NOCORE = 0x00020000; pub const MAP_PREFAULT_READ = 0x00040000; pub const MAP_32BIT = 0x00080000; -pub const WNOHANG = 1; -pub const WUNTRACED = 2; -pub const WSTOPPED = WUNTRACED; -pub const WCONTINUED = 4; -pub const WNOWAIT = 8; -pub const WEXITED = 16; -pub const WTRAPPED = 32; +pub const WNOHANG = 0x1; +pub const WUNTRACED = 0x2; +pub const WSTOPPED = 0x10; +pub const WCONTINUED = 0x4; +pub const WNOWAIT = 0x20; +pub const WEXITED = 0x08; -pub const SA_ONSTACK = 0x0001; -pub const SA_RESTART = 0x0002; -pub const SA_RESETHAND = 0x0004; -pub const SA_NOCLDSTOP = 0x0008; -pub const SA_NODEFER = 0x0010; -pub const SA_NOCLDWAIT = 0x0020; -pub const SA_SIGINFO = 0x0040; +pub const SA_ONSTACK = 0x20; +pub const SA_RESTART = 0x10; +pub const SA_RESETHAND = 0x04; +pub const SA_NOCLDSTOP = 0x01; +pub const SA_NODEFER = 0x08; +pub const SA_NOCLDWAIT = 0x02; +pub const SA_SIGINFO = 0x40; +pub const SA_NOMASK = SA_NODEFER; +pub const SA_STACK = SA_ONSTACK; +pub const SA_ONESHOT = SA_RESETHAND; pub const SIGHUP = 1; pub const SIGINT = 2; pub const SIGQUIT = 3; pub const SIGILL = 4; -pub const SIGTRAP = 5; +pub const SIGCHLD = 5; pub const SIGABRT = 6; pub const SIGIOT = SIGABRT; -pub const SIGEMT = 7; +pub const SIGPIPE = 7; pub const SIGFPE = 8; pub const SIGKILL = 9; -pub const SIGBUS = 10; +pub const SIGSTOP = 10; pub const SIGSEGV = 11; -pub const SIGSYS = 12; -pub const SIGPIPE = 13; +pub const SIGCONT = 12; +pub const SIGTSTP = 13; pub const SIGALRM = 14; pub const SIGTERM = 15; -pub const SIGURG = 16; -pub const SIGSTOP = 17; -pub const SIGTSTP = 18; -pub const SIGCONT = 19; -pub const SIGCHLD = 20; -pub const SIGTTIN = 21; -pub const SIGTTOU = 22; -pub const SIGIO = 23; -pub const SIGXCPU = 24; -pub const SIGXFSZ = 25; -pub const SIGVTALRM = 26; -pub const SIGPROF = 27; -pub const SIGWINCH = 28; -pub const SIGINFO = 29; -pub const SIGUSR1 = 30; -pub const SIGUSR2 = 31; -pub const SIGTHR = 32; -pub const SIGLWP = SIGTHR; -pub const SIGLIBRT = 33; +pub const SIGTTIN = 16; +pub const SIGTTOU = 17; +pub const SIGUSR1 = 18; +pub const SIGUSR2 = 19; +pub const SIGWINCH = 20; +pub const SIGKILLTHR = 21; +pub const SIGTRAP = 22; +pub const SIGPOLL = 23; +pub const SIGPROF = 24; +pub const SIGSYS = 25; +pub const SIGURG = 26; +pub const SIGVTALRM = 27; +pub const SIGXCPU = 28; +pub const SIGXFSZ = 29; +pub const SIGBUS = 30; +pub const SIGRESERVED1 = 31; +pub const SIGRESERVED2 = 32; +// TODO: check pub const SIGRTMIN = 65; pub const SIGRTMAX = 126; @@ -645,135 +636,51 @@ pub const EVFILT_SENDFILE = -12; pub const EVFILT_EMPTY = -13; -/// On input, NOTE_TRIGGER causes the event to be triggered for output. -pub const NOTE_TRIGGER = 0x01000000; +pub const TCGETA = 0x8000; +pub const TCSETA = 0x8001; +pub const TCSETAW = 0x8004; +pub const TCSETAF = 0x8003; +pub const TCSBRK = 08005; +pub const TCXONC = 0x8007; +pub const TCFLSH = 0x8006; -/// ignore input fflags -pub const NOTE_FFNOP = 0x00000000; - -/// and fflags -pub const NOTE_FFAND = 0x40000000; - -/// or fflags -pub const NOTE_FFOR = 0x80000000; - -/// copy fflags -pub const NOTE_FFCOPY = 0xc0000000; - -/// mask for operations -pub const NOTE_FFCTRLMASK = 0xc0000000; -pub const NOTE_FFLAGSMASK = 0x00ffffff; - -/// low water mark -pub const NOTE_LOWAT = 0x00000001; - -/// behave like poll() -pub const NOTE_FILE_POLL = 0x00000002; - -/// vnode was removed -pub const NOTE_DELETE = 0x00000001; - -/// data contents changed -pub const NOTE_WRITE = 0x00000002; - -/// size increased -pub const NOTE_EXTEND = 0x00000004; - -/// attributes changed -pub const NOTE_ATTRIB = 0x00000008; - -/// link count changed -pub const NOTE_LINK = 0x00000010; - -/// vnode was renamed -pub const NOTE_RENAME = 0x00000020; - -/// vnode access was revoked -pub const NOTE_REVOKE = 0x00000040; - -/// vnode was opened -pub const NOTE_OPEN = 0x00000080; - -/// file closed, fd did not allow write -pub const NOTE_CLOSE = 0x00000100; - -/// file closed, fd did allow write -pub const NOTE_CLOSE_WRITE = 0x00000200; - -/// file was read -pub const NOTE_READ = 0x00000400; - -/// process exited -pub const NOTE_EXIT = 0x80000000; - -/// process forked -pub const NOTE_FORK = 0x40000000; - -/// process exec'd -pub const NOTE_EXEC = 0x20000000; - -/// mask for signal & exit status -pub const NOTE_PDATAMASK = 0x000fffff; -pub const NOTE_PCTRLMASK = (~NOTE_PDATAMASK); - -/// data is seconds -pub const NOTE_SECONDS = 0x00000001; - -/// data is milliseconds -pub const NOTE_MSECONDS = 0x00000002; - -/// data is microseconds -pub const NOTE_USECONDS = 0x00000004; - -/// data is nanoseconds -pub const NOTE_NSECONDS = 0x00000008; - -/// timeout is absolute -pub const NOTE_ABSTIME = 0x00000010; - -pub const TIOCEXCL = 0x2000740d; -pub const TIOCNXCL = 0x2000740e; -pub const TIOCSCTTY = 0x20007461; -pub const TIOCGPGRP = 0x40047477; -pub const TIOCSPGRP = 0x80047476; -pub const TIOCOUTQ = 0x40047473; -pub const TIOCSTI = 0x80017472; -pub const TIOCGWINSZ = 0x40087468; -pub const TIOCSWINSZ = 0x80087467; -pub const TIOCMGET = 0x4004746a; -pub const TIOCMBIS = 0x8004746c; -pub const TIOCMBIC = 0x8004746b; -pub const TIOCMSET = 0x8004746d; -pub const FIONREAD = 0x4004667f; -pub const TIOCCONS = 0x80047462; -pub const TIOCPKT = 0x80047470; -pub const FIONBIO = 0x8004667e; -pub const TIOCNOTTY = 0x20007471; -pub const TIOCSETD = 0x8004741b; -pub const TIOCGETD = 0x4004741a; -pub const TIOCSBRK = 0x2000747b; -pub const TIOCCBRK = 0x2000747a; -pub const TIOCGSID = 0x40047463; -pub const TIOCGPTN = 0x4004740f; -pub const TIOCSIG = 0x2004745f; +pub const TIOCSCTTY = 0x8017; +pub const TIOCGPGRP = 0x8015; +pub const TIOCSPGRP = 0x8016; +pub const TIOCGWINSZ = 0x8012; +pub const TIOCSWINSZ = 0x8013; +pub const TIOCMGET = 0x8018; +pub const TIOCMBIS = 0x8022; +pub const TIOCMBIC = 0x8023; +pub const TIOCMSET = 0x8019; +pub const FIONREAD = 0xbe000001; +pub const FIONBIO = 0xbe000000; +pub const TIOCSBRK = 0x8020; +pub const TIOCCBRK = 0x8021; +pub const TIOCGSID = 0x8024; pub fn WEXITSTATUS(s: u32) u32 { - return (s & 0xff00) >> 8; + return (s & 0xff); } + pub fn WTERMSIG(s: u32) u32 { - return s & 0x7f; + return (s >> 8) & 0xff; } + pub fn WSTOPSIG(s: u32) u32 { return WEXITSTATUS(s); } + pub fn WIFEXITED(s: u32) bool { return WTERMSIG(s) == 0; } + pub fn WIFSTOPPED(s: u32) bool { - return @intCast(u16, (((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00; + return ((s >> 16) & 0xff) != 0; } + pub fn WIFSIGNALED(s: u32) bool { - return (s & 0xffff) -% 1 < 0xff; + return ((s >> 8) & 0xff) != 0; } pub const winsize = extern struct { @@ -823,49 +730,47 @@ pub const sigset_t = extern struct { __bits: [_SIG_WORDS]u32, }; -pub const EPERM = 1; // Operation not permitted -pub const ENOENT = 2; // No such file or directory -pub const ESRCH = 3; // No such process -pub const EINTR = 4; // Interrupted system call -pub const EIO = 5; // Input/output error -pub const ENXIO = 6; // Device not configured -pub const E2BIG = 7; // Argument list too long -pub const ENOEXEC = 8; // Exec format error -pub const EBADF = 9; // Bad file descriptor -pub const ECHILD = 10; // No child processes -pub const EDEADLK = 11; // Resource deadlock avoided -// 11 was EAGAIN -pub const ENOMEM = 12; // Cannot allocate memory -pub const EACCES = 13; // Permission denied -pub const EFAULT = 14; // Bad address -pub const ENOTBLK = 15; // Block device required -pub const EBUSY = 16; // Device busy -pub const EEXIST = 17; // File exists -pub const EXDEV = 18; // Cross-device link -pub const ENODEV = 19; // Operation not supported by device -pub const ENOTDIR = 20; // Not a directory -pub const EISDIR = 21; // Is a directory -pub const EINVAL = 22; // Invalid argument -pub const ENFILE = 23; // Too many open files in system -pub const EMFILE = 24; // Too many open files -pub const ENOTTY = 25; // Inappropriate ioctl for device -pub const ETXTBSY = 26; // Text file busy -pub const EFBIG = 27; // File too large -pub const ENOSPC = 28; // No space left on device -pub const ESPIPE = 29; // Illegal seek -pub const EROFS = 30; // Read-only filesystem -pub const EMLINK = 31; // Too many links -pub const EPIPE = 32; // Broken pipe +pub const EPERM = -0x7ffffff1; // Operation not permitted +pub const ENOENT = -0x7fff9ffd; // No such file or directory +pub const ESRCH = -0x7fff8ff3; // No such process +pub const EINTR = -0x7ffffff6; // Interrupted system call +pub const EIO = -0x7fffffff; // Input/output error +pub const ENXIO = -0x7fff8ff5; // Device not configured +pub const E2BIG = -0x7fff8fff; // Argument list too long +pub const ENOEXEC = -0x7fffecfe; // Exec format error +pub const ECHILD = -0x7fff8ffe; // No child processes +pub const EDEADLK = -0x7fff8ffd; // Resource deadlock avoided +pub const ENOMEM = -0x80000000; // Cannot allocate memory +pub const EACCES = -0x7ffffffe; // Permission denied +pub const EFAULT = -0x7fffecff; // Bad address +pub const EBUSY = -0x7ffffff2; // Device busy +pub const EEXIST = -0x7fff9ffe; // File exists +pub const EXDEV = -0x7fff9ff5; // Cross-device link +pub const ENODEV = -0x7fff8ff9; // Operation not supported by device +pub const ENOTDIR = -0x7fff9ffb; // Not a directory +pub const EISDIR = -0x7fff9ff7; // Is a directory +pub const EINVAL = -0x7ffffffb; // Invalid argument +pub const ENFILE = -0x7fff8ffa; // Too many open files in system +pub const EMFILE = -0x7fff9ff6; // Too many open files +pub const ENOTTY = -0x7fff8ff6; // Inappropriate ioctl for device +pub const ETXTBSY = -0x7fff8fc5; // Text file busy +pub const EFBIG = -0x7fff8ffc; // File too large +pub const ENOSPC = -0x7fff9ff9; // No space left on device +pub const ESPIPE = -0x7fff8ff4; // Illegal seek +pub const EROFS = -0x7fff9ff8; // Read-only filesystem +pub const EMLINK = -0x7fff8ffb; // Too many links +pub const EPIPE = -0x7fff9ff3; // Broken pipe +pub const EBADF = -0x7fffa000; // Bad file descriptor // math software pub const EDOM = 33; // Numerical argument out of domain pub const ERANGE = 34; // Result too large // non-blocking and interrupt i/o -pub const EAGAIN = 35; // Resource temporarily unavailable -pub const EWOULDBLOCK = EAGAIN; // Operation would block -pub const EINPROGRESS = 36; // Operation now in progress -pub const EALREADY = 37; // Operation already in progress +pub const EAGAIN = -0x7ffffff5; +pub const EWOULDBLOCK = -0x7ffffff5; +pub const EINPROGRESS = -0x7fff8fdc; +pub const EALREADY = -0x7fff8fdb; // ipc/network software -- argument errors pub const ENOTSOCK = 38; // Socket operation on non-socket @@ -1447,3 +1352,20 @@ pub const directory_which = enum(c_int) { _, }; + +pub const cc_t = u8; +pub const speed_t = u8; +pub const tcflag_t = u32; + +pub const NCCS = 32; + +pub const termios = extern struct { + c_iflag: tcflag_t, + c_oflag: tcflag_t, + c_cflag: tcflag_t, + c_lflag: tcflag_t, + c_line: cc_t, + c_ispeed: speed_t, + c_ospeed: speed_t, + cc_t: [NCCS]cc_t, +}; diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index b93e67e25d..38a307ac2f 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -248,7 +248,7 @@ fn initTLS() void { tls_data = @intToPtr([*]u8, img_base + phdr.p_vaddr)[0..phdr.p_filesz]; tls_data_alloc_size = phdr.p_memsz; } else { - tls_align_factor = @alignOf(*usize); + tls_align_factor = @alignOf(usize); tls_data = &[_]u8{}; tls_data_alloc_size = 0; } @@ -308,7 +308,7 @@ fn initTLS() void { } fn alignPtrCast(comptime T: type, ptr: [*]u8) callconv(.Inline) *T { - return @ptrCast(*T, @alignCast(@alignOf(*T), ptr)); + return @ptrCast(*T, @alignCast(@alignOf(T), ptr)); } /// Initializes all the fields of the static TLS area and returns the computed diff --git a/lib/std/std.zig b/lib/std/std.zig index 5f10def72e..940d1ab42e 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -88,6 +88,7 @@ pub const time = @import("time.zig"); pub const unicode = @import("unicode.zig"); pub const valgrind = @import("valgrind.zig"); pub const wasm = @import("wasm.zig"); +pub const x = @import("x.zig"); pub const zig = @import("zig.zig"); pub const start = @import("start.zig"); diff --git a/lib/std/target.zig b/lib/std/target.zig index 3e8159aae9..acca7fd13c 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -211,8 +211,9 @@ pub const Target = struct { /// If neither of these cases apply, a runtime check should be used to determine if the /// target supports a given OS feature. /// - /// Binaries built with a given maximum version will continue to function on newer operating system - /// versions. However, such a binary may not take full advantage of the newer operating system APIs. + /// Binaries built with a given maximum version will continue to function on newer + /// operating system versions. However, such a binary may not take full advantage of the + /// newer operating system APIs. /// /// See `Os.isAtLeast`. pub const VersionRange = union { @@ -260,7 +261,7 @@ pub const Target = struct { .freebsd => return .{ .semver = Version.Range{ .min = .{ .major = 12, .minor = 0 }, - .max = .{ .major = 12, .minor = 1 }, + .max = .{ .major = 13, .minor = 0 }, }, }, .macos => return .{ diff --git a/lib/std/unicode.zig b/lib/std/unicode.zig index 9b44a84477..89dda5bbad 100644 --- a/lib/std/unicode.zig +++ b/lib/std/unicode.zig @@ -206,7 +206,7 @@ pub fn utf8ValidateSlice(s: []const u8) bool { return false; } - if (utf8Decode(s[i .. i + cp_len])) |_| {} else |_| { + if (std.meta.isError(utf8Decode(s[i .. i + cp_len]))) { return false; } i += cp_len; diff --git a/lib/std/x.zig b/lib/std/x.zig new file mode 100644 index 0000000000..df7debf121 --- /dev/null +++ b/lib/std/x.zig @@ -0,0 +1 @@ +pub const os = @import("x/os/os.zig"); diff --git a/lib/std/x/os/Socket.zig b/lib/std/x/os/Socket.zig new file mode 100644 index 0000000000..3f1971a877 --- /dev/null +++ b/lib/std/x/os/Socket.zig @@ -0,0 +1,276 @@ +const std = @import("../../std.zig"); + +const os = std.os; +const mem = std.mem; +const net = std.net; +const time = std.time; +const builtin = std.builtin; +const testing = std.testing; + +const Socket = @This(); + +/// A socket-address pair. +pub const Connection = struct { + socket: Socket, + address: net.Address, +}; + +/// The underlying handle of a socket. +fd: os.socket_t, + +/// Open a new socket. +pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket { + return Socket{ .fd = try os.socket(domain, socket_type, protocol) }; +} + +/// Closes the socket. +pub fn deinit(self: Socket) void { + os.closeSocket(self.fd); +} + +/// Shutdown either the read side, or write side, or the entirety of a socket. +pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { + return os.shutdown(self.fd, how); +} + +/// Binds the socket to an address. +pub fn bind(self: Socket, address: net.Address) !void { + return os.bind(self.fd, &address.any, address.getOsSockLen()); +} + +/// Start listening for incoming connections on the socket. +pub fn listen(self: Socket, max_backlog_size: u31) !void { + return os.listen(self.fd, max_backlog_size); +} + +/// Have the socket attempt to the connect to an address. +pub fn connect(self: Socket, address: net.Address) !void { + return os.connect(self.fd, &address.any, address.getOsSockLen()); +} + +/// Accept a pending incoming connection queued to the kernel backlog +/// of the socket. +pub fn accept(self: Socket, flags: u32) !Socket.Connection { + var address: os.sockaddr = undefined; + var address_len: u32 = @sizeOf(os.sockaddr); + + const fd = try os.accept(self.fd, &address, &address_len, flags); + + return Connection{ + .socket = Socket{ .fd = fd }, + .address = net.Address.initPosix(@alignCast(4, &address)), + }; +} + +/// Read data from the socket into the buffer provided. It returns the +/// number of bytes read into the buffer provided. +pub fn read(self: Socket, buf: []u8) !usize { + return os.read(self.fd, buf); +} + +/// Read data from the socket into the buffer provided with a set of flags +/// specified. It returns the number of bytes read into the buffer provided. +pub fn recv(self: Socket, buf: []u8, flags: u32) !usize { + return os.recv(self.fd, buf, flags); +} + +/// Write a buffer of data provided to the socket. It returns the number +/// of bytes that are written to the socket. +pub fn write(self: Socket, buf: []const u8) !usize { + return os.write(self.fd, buf); +} + +/// Writes multiple I/O vectors to the socket. It returns the number +/// of bytes that are written to the socket. +pub fn writev(self: Socket, buffers: []const os.iovec_const) !usize { + return os.writev(self.fd, buffers); +} + +/// Write a buffer of data provided to the socket with a set of flags specified. +/// It returns the number of bytes that are written to the socket. +pub fn send(self: Socket, buf: []const u8, flags: u32) !usize { + return os.send(self.fd, buf, flags); +} + +/// Writes multiple I/O vectors with a prepended message header to the socket +/// with a set of flags specified. It returns the number of bytes that are +/// written to the socket. +pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize { + return os.sendmsg(self.fd, msg, flags); +} + +/// Query the address that the socket is locally bounded to. +pub fn getLocalAddress(self: Socket) !net.Address { + var address: os.sockaddr = undefined; + var address_len: u32 = @sizeOf(os.sockaddr); + try os.getsockname(self.fd, &address, &address_len); + return net.Address.initPosix(@alignCast(4, &address)); +} + +/// Query and return the latest cached error on the socket. +pub fn getError(self: Socket) !void { + return os.getsockoptError(self.fd); +} + +/// Query the read buffer size of the socket. +pub fn getReadBufferSize(self: Socket) !u32 { + var value: u32 = undefined; + var value_len: u32 = @sizeOf(u32); + + const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&value), &value_len); + return switch (os.errno(rc)) { + 0 => value, + os.EBADF => error.BadFileDescriptor, + os.EFAULT => error.InvalidAddressSpace, + os.EINVAL => error.InvalidSocketOption, + os.ENOPROTOOPT => error.UnknownSocketOption, + os.ENOTSOCK => error.NotASocket, + else => |err| os.unexpectedErrno(err), + }; +} + +/// Query the write buffer size of the socket. +pub fn getWriteBufferSize(self: Socket) !u32 { + var value: u32 = undefined; + var value_len: u32 = @sizeOf(u32); + + const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&value), &value_len); + return switch (os.errno(rc)) { + 0 => value, + os.EBADF => error.BadFileDescriptor, + os.EFAULT => error.InvalidAddressSpace, + os.EINVAL => error.InvalidSocketOption, + os.ENOPROTOOPT => error.UnknownSocketOption, + os.ENOTSOCK => error.NotASocket, + else => |err| os.unexpectedErrno(err), + }; +} + +/// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if +/// the host does not support sockets listening the same address. +pub fn setReuseAddress(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "SO_REUSEADDR")) { + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_REUSEADDR, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if +/// the host does not supports sockets listening on the same port. +pub fn setReusePort(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "SO_REUSEPORT")) { + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_REUSEPORT, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not support +/// sockets disabling Nagle's algorithm. +pub fn setNoDelay(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "TCP_NODELAY")) { + return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_NODELAY, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not +/// support TCP Fast Open. +pub fn setFastOpen(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "TCP_FASTOPEN")) { + return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns +/// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK. +pub fn setQuickACK(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "TCP_QUICKACK")) { + return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Set the write buffer size of the socket. +pub fn setWriteBufferSize(self: Socket, size: u32) !void { + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size)); +} + +/// Set the read buffer size of the socket. +pub fn setReadBufferSize(self: Socket, size: u32) !void { + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&size)); +} + +/// Set a timeout on the socket that is to occur if no messages are successfully written +/// to its bound destination after a specified number of milliseconds. A subsequent write +/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. +pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void { + const timeout = os.timeval{ + .tv_sec = @intCast(isize, milliseconds / time.ms_per_s), + .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms), + }; + + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout)); +} + +/// Set a timeout on the socket that is to occur if no messages are successfully read +/// from its bound destination after a specified number of milliseconds. A subsequent +/// read from the socket will thereafter return `error.WouldBlock` should the timeout be +/// exceeded. +pub fn setReadTimeout(self: Socket, milliseconds: usize) !void { + const timeout = os.timeval{ + .tv_sec = @intCast(isize, milliseconds / time.ms_per_s), + .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms), + }; + + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout)); +} + +test { + testing.refAllDecls(@This()); +} + +test "socket/linux: set read timeout of 1 millisecond on blocking socket" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + + const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP); + defer a.deinit(); + + try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0)); + try a.listen(128); + + const binded_address = try a.getLocalAddress(); + + const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP); + defer b.deinit(); + + try b.connect(binded_address); + try b.setReadTimeout(1); + + const ab = try a.accept(os.SOCK_CLOEXEC); + defer ab.socket.deinit(); + + var buf: [1]u8 = undefined; + testing.expectError(error.WouldBlock, b.read(&buf)); +} + +test "socket/linux: create non-blocking socket pair" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + + const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP); + defer a.deinit(); + + try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0)); + try a.listen(128); + + const binded_address = try a.getLocalAddress(); + + const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP); + defer b.deinit(); + + testing.expectError(error.WouldBlock, b.connect(binded_address)); + try b.getError(); + + const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + defer ab.socket.deinit(); +} diff --git a/lib/std/x/os/os.zig b/lib/std/x/os/os.zig new file mode 100644 index 0000000000..1d1b8edc7c --- /dev/null +++ b/lib/std/x/os/os.zig @@ -0,0 +1,9 @@ +const std = @import("../../std.zig"); + +const testing = std.testing; + +pub const Socket = @import("Socket.zig"); + +test { + testing.refAllDecls(@This()); +} diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 63df8d37a5..04b3d75cb4 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -15,6 +15,7 @@ const Target = std.Target; const CrossTarget = std.zig.CrossTarget; const macos = @import("system/macos.zig"); const native_endian = std.Target.current.cpu.arch.endian(); +const linux = @import("system/linux.zig"); pub const windows = @import("system/windows.zig"); pub const getSDKPath = macos.getSDKPath; @@ -912,15 +913,19 @@ pub const NativeTargetInfo = struct { .x86_64, .i386 => { return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target); }, - else => { - // This architecture does not have CPU model & feature detection yet. - // See https://github.com/ziglang/zig/issues/4591 - return null; - }, + else => {}, } + + // This architecture does not have CPU model & feature detection yet. + // See https://github.com/ziglang/zig/issues/4591 + if (std.Target.current.os.tag != .linux) + return null; + + return linux.detectNativeCpuAndFeatures(); } }; test { _ = @import("system/macos.zig"); + _ = @import("system/linux.zig"); } diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig new file mode 100644 index 0000000000..dc0d3c7f6a --- /dev/null +++ b/lib/std/zig/system/linux.zig @@ -0,0 +1,199 @@ +const std = @import("std"); +const mem = std.mem; +const io = std.io; +const fs = std.fs; +const fmt = std.fmt; +const testing = std.testing; + +const Target = std.Target; +const CrossTarget = std.zig.CrossTarget; + +const assert = std.debug.assert; + +const SparcCpuinfoImpl = struct { + model: ?*const Target.Cpu.Model = null, + is_64bit: bool = false, + + const cpu_names = .{ + .{ "SuperSparc", &Target.sparc.cpu.supersparc }, + .{ "HyperSparc", &Target.sparc.cpu.hypersparc }, + .{ "SpitFire", &Target.sparc.cpu.ultrasparc }, + .{ "BlackBird", &Target.sparc.cpu.ultrasparc }, + .{ "Sabre", &Target.sparc.cpu.ultrasparc }, + .{ "Hummingbird", &Target.sparc.cpu.ultrasparc }, + .{ "Cheetah", &Target.sparc.cpu.ultrasparc3 }, + .{ "Jalapeno", &Target.sparc.cpu.ultrasparc3 }, + .{ "Jaguar", &Target.sparc.cpu.ultrasparc3 }, + .{ "Panther", &Target.sparc.cpu.ultrasparc3 }, + .{ "Serrano", &Target.sparc.cpu.ultrasparc3 }, + .{ "UltraSparc T1", &Target.sparc.cpu.niagara }, + .{ "UltraSparc T2", &Target.sparc.cpu.niagara2 }, + .{ "UltraSparc T3", &Target.sparc.cpu.niagara3 }, + .{ "UltraSparc T4", &Target.sparc.cpu.niagara4 }, + .{ "UltraSparc T5", &Target.sparc.cpu.niagara4 }, + .{ "LEON", &Target.sparc.cpu.leon3 }, + }; + + fn line_hook(self: *SparcCpuinfoImpl, key: []const u8, value: []const u8) !bool { + if (mem.eql(u8, key, "cpu")) { + inline for (cpu_names) |pair| { + if (mem.indexOfPos(u8, value, 0, pair[0]) != null) { + self.model = pair[1]; + break; + } + } + } else if (mem.eql(u8, key, "type")) { + self.is_64bit = mem.eql(u8, value, "sun4u") or mem.eql(u8, value, "sun4v"); + } + + return true; + } + + fn finalize(self: *const SparcCpuinfoImpl, arch: Target.Cpu.Arch) ?Target.Cpu { + // At the moment we only support 64bit SPARC systems. + assert(self.is_64bit); + + const model = self.model orelse Target.Cpu.Model.generic(arch); + return Target.Cpu{ + .arch = arch, + .model = model, + .features = model.features, + }; + } +}; + +const SparcCpuinfoParser = CpuinfoParser(SparcCpuinfoImpl); + +test "cpuinfo: SPARC" { + try testParser(SparcCpuinfoParser, &Target.sparc.cpu.niagara2, + \\cpu : UltraSparc T2 (Niagara2) + \\fpu : UltraSparc T2 integrated FPU + \\pmu : niagara2 + \\type : sun4v + ); +} + +const PowerpcCpuinfoImpl = struct { + model: ?*const Target.Cpu.Model = null, + + const cpu_names = .{ + .{ "604e", &Target.powerpc.cpu.@"604e" }, + .{ "604", &Target.powerpc.cpu.@"604" }, + .{ "7400", &Target.powerpc.cpu.@"7400" }, + .{ "7410", &Target.powerpc.cpu.@"7400" }, + .{ "7447", &Target.powerpc.cpu.@"7400" }, + .{ "7455", &Target.powerpc.cpu.@"7450" }, + .{ "G4", &Target.powerpc.cpu.@"g4" }, + .{ "POWER4", &Target.powerpc.cpu.@"970" }, + .{ "PPC970FX", &Target.powerpc.cpu.@"970" }, + .{ "PPC970MP", &Target.powerpc.cpu.@"970" }, + .{ "G5", &Target.powerpc.cpu.@"g5" }, + .{ "POWER5", &Target.powerpc.cpu.@"g5" }, + .{ "A2", &Target.powerpc.cpu.@"a2" }, + .{ "POWER6", &Target.powerpc.cpu.@"pwr6" }, + .{ "POWER7", &Target.powerpc.cpu.@"pwr7" }, + .{ "POWER8", &Target.powerpc.cpu.@"pwr8" }, + .{ "POWER8E", &Target.powerpc.cpu.@"pwr8" }, + .{ "POWER8NVL", &Target.powerpc.cpu.@"pwr8" }, + .{ "POWER9", &Target.powerpc.cpu.@"pwr9" }, + .{ "POWER10", &Target.powerpc.cpu.@"pwr10" }, + }; + + fn line_hook(self: *PowerpcCpuinfoImpl, key: []const u8, value: []const u8) !bool { + if (mem.eql(u8, key, "cpu")) { + // The model name is often followed by a comma or space and extra + // info. + inline for (cpu_names) |pair| { + const end_index = mem.indexOfAny(u8, value, ", ") orelse value.len; + if (mem.eql(u8, value[0..end_index], pair[0])) { + self.model = pair[1]; + break; + } + } + + // Stop the detection once we've seen the first core. + return false; + } + + return true; + } + + fn finalize(self: *const PowerpcCpuinfoImpl, arch: Target.Cpu.Arch) ?Target.Cpu { + const model = self.model orelse Target.Cpu.Model.generic(arch); + return Target.Cpu{ + .arch = arch, + .model = model, + .features = model.features, + }; + } +}; + +const PowerpcCpuinfoParser = CpuinfoParser(PowerpcCpuinfoImpl); + +test "cpuinfo: PowerPC" { + try testParser(PowerpcCpuinfoParser, &Target.powerpc.cpu.@"970", + \\processor : 0 + \\cpu : PPC970MP, altivec supported + \\clock : 1250.000000MHz + \\revision : 1.1 (pvr 0044 0101) + ); + try testParser(PowerpcCpuinfoParser, &Target.powerpc.cpu.pwr8, + \\processor : 0 + \\cpu : POWER8 (raw), altivec supported + \\clock : 2926.000000MHz + \\revision : 2.0 (pvr 004d 0200) + ); +} + +fn testParser(parser: anytype, expected_model: *const Target.Cpu.Model, input: []const u8) !void { + var fbs = io.fixedBufferStream(input); + const result = try parser.parse(.powerpc, fbs.reader()); + testing.expectEqual(expected_model, result.?.model); + testing.expect(expected_model.features.eql(result.?.features)); +} + +// The generic implementation of a /proc/cpuinfo parser. +// For every line it invokes the line_hook method with the key and value strings +// as first and second parameters. Returning false from the hook function stops +// the iteration without raising an error. +// When all the lines have been analyzed the finalize method is called. +fn CpuinfoParser(comptime impl: anytype) type { + return struct { + fn parse(arch: Target.Cpu.Arch, reader: anytype) anyerror!?Target.Cpu { + var line_buf: [1024]u8 = undefined; + var obj: impl = .{}; + + while (true) { + const line = (try reader.readUntilDelimiterOrEof(&line_buf, '\n')) orelse break; + const colon_pos = mem.indexOfScalar(u8, line, ':') orelse continue; + const key = mem.trimRight(u8, line[0..colon_pos], " \t"); + const value = mem.trimLeft(u8, line[colon_pos + 1 ..], " \t"); + + if (!try obj.line_hook(key, value)) + break; + } + + return obj.finalize(arch); + } + }; +} + +pub fn detectNativeCpuAndFeatures() ?Target.Cpu { + var f = fs.openFileAbsolute("/proc/cpuinfo", .{ .intended_io_mode = .blocking }) catch |err| switch (err) { + else => return null, + }; + defer f.close(); + + const current_arch = std.Target.current.cpu.arch; + switch (current_arch) { + .sparcv9 => { + return SparcCpuinfoParser.parse(current_arch, f.reader()) catch null; + }, + .powerpc, .powerpcle, .powerpc64, .powerpc64le => { + return PowerpcCpuinfoParser.parse(current_arch, f.reader()) catch null; + }, + else => {}, + } + + return null; +} diff --git a/src/clang.zig b/src/clang.zig index d53accbaf7..e00271968b 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -104,6 +104,16 @@ pub const APFloat = opaque { extern fn ZigClangAPFloat_toString(*const APFloat, precision: c_uint, maxPadding: c_uint, truncateZero: bool) [*:0]const u8; }; +pub const APFloatBaseSemantics = extern enum { + IEEEhalf, + BFloat, + IEEEsingle, + IEEEdouble, + x86DoubleExtended, + IEEEquad, + PPCDoubleDouble, +}; + pub const APInt = opaque { pub const getLimitedValue = ZigClangAPInt_getLimitedValue; extern fn ZigClangAPInt_getLimitedValue(*const APInt, limit: u64) u64; @@ -455,6 +465,12 @@ pub const FileID = opaque {}; pub const FloatingLiteral = opaque { pub const getValueAsApproximateDouble = ZigClangFloatingLiteral_getValueAsApproximateDouble; extern fn ZigClangFloatingLiteral_getValueAsApproximateDouble(*const FloatingLiteral) f64; + + pub const getBeginLoc = ZigClangIntegerLiteral_getBeginLoc; + extern fn ZigClangIntegerLiteral_getBeginLoc(*const FloatingLiteral) SourceLocation; + + pub const getRawSemantics = ZigClangFloatingLiteral_getRawSemantics; + extern fn ZigClangFloatingLiteral_getRawSemantics(*const FloatingLiteral) APFloatBaseSemantics; }; pub const ForStmt = opaque { diff --git a/src/codegen.zig b/src/codegen.zig index 40b383c24f..290daee457 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -449,7 +449,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .rbrace_src = src_data.rbrace_src, .source = src_data.source, }; - defer function.register_manager.deinit(bin_file.allocator); defer function.stack.deinit(bin_file.allocator); defer function.exitlude_jump_relocs.deinit(bin_file.allocator); @@ -779,8 +778,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { branch.inst_table.putAssumeCapacity(inst, .dead); switch (prev_value) { .register => |reg| { - const canon_reg = toCanonicalReg(reg); - self.register_manager.freeReg(canon_reg); + // TODO separate architectures with registers from + // stack-based architectures (spu_2) + if (callee_preserved_regs.len > 0) { + const canon_reg = toCanonicalReg(reg); + self.register_manager.freeReg(canon_reg); + } }, else => {}, // TODO process stack allocation death } @@ -920,9 +923,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const ptr_bits = arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); if (abi_size <= ptr_bytes) { - try self.register_manager.registers.ensureCapacity(self.gpa, self.register_manager.registers.count() + 1); - if (self.register_manager.tryAllocReg(inst)) |reg| { - return MCValue{ .register = registerAlias(reg, abi_size) }; + // TODO separate architectures with registers from + // stack-based architectures (spu_2) + if (callee_preserved_regs.len > 0) { + if (self.register_manager.tryAllocReg(inst)) |reg| { + return MCValue{ .register = registerAlias(reg, abi_size) }; + } } } } @@ -952,8 +958,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { /// `reg_owner` is the instruction that gets associated with the register in the register table. /// This can have a side effect of spilling instructions to the stack to free up a register. fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue { - try self.register_manager.registers.ensureCapacity(self.gpa, @intCast(u32, self.register_manager.registers.count() + 1)); - const reg = try self.register_manager.allocReg(reg_owner); try self.genSetReg(reg_owner.src, reg_owner.ty, reg, mcv); return MCValue{ .register = reg }; @@ -1240,10 +1244,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .register => |reg| { // If it's in the registers table, need to associate the register with the // new instruction. - if (self.register_manager.registers.getEntry(toCanonicalReg(reg))) |entry| { - entry.value = inst; + // TODO separate architectures with registers from + // stack-based architectures (spu_2) + if (callee_preserved_regs.len > 0) { + if (reg.allocIndex()) |index| { + if (!self.register_manager.isRegFree(reg)) { + self.register_manager.registers[index] = inst; + } + } + log.debug("reusing {} => {*}", .{ reg, inst }); } - log.debug("reusing {} => {*}", .{ reg, inst }); }, .stack_offset => |off| { log.debug("reusing stack offset {} => {*}", .{ off, inst }); @@ -1738,6 +1748,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const arg_index = self.arg_index; self.arg_index += 1; + // TODO separate architectures with registers from + // stack-based architectures (spu_2) if (callee_preserved_regs.len == 0) { return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch}); } @@ -1769,7 +1781,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (mcv) { .register => |reg| { - try self.register_manager.registers.ensureCapacity(self.gpa, self.register_manager.registers.count() + 1); self.register_manager.getRegAssumeFree(toCanonicalReg(reg), &inst.base); }, else => {}, @@ -2075,7 +2086,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (mc_arg) { .none => continue, .register => |reg| { - try self.register_manager.getRegWithoutTracking(reg); + // TODO prevent this macho if block to be generated for all archs + switch (arch) { + .x86_64, .aarch64 => try self.register_manager.getRegWithoutTracking(reg), + else => unreachable, + } try self.genSetReg(arg.src, arg.ty, reg, arg_mcv); }, .stack_offset => { @@ -2397,8 +2412,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const parent_free_registers = self.register_manager.free_registers; var parent_stack = try self.stack.clone(self.gpa); defer parent_stack.deinit(self.gpa); - var parent_registers = try self.register_manager.registers.clone(self.gpa); - defer parent_registers.deinit(self.gpa); + const parent_registers = self.register_manager.registers; try self.branch_stack.append(.{}); @@ -2414,9 +2428,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { var saved_then_branch = self.branch_stack.pop(); defer saved_then_branch.deinit(self.gpa); - self.register_manager.registers.deinit(self.gpa); self.register_manager.registers = parent_registers; - parent_registers = .{}; self.stack.deinit(self.gpa); self.stack = parent_stack; diff --git a/src/codegen/riscv64.zig b/src/codegen/riscv64.zig index 96b9c58f9c..a01f38289a 100644 --- a/src/codegen/riscv64.zig +++ b/src/codegen/riscv64.zig @@ -1,5 +1,7 @@ const std = @import("std"); const DW = std.dwarf; +const assert = std.debug.assert; +const testing = std.testing; // TODO: this is only tagged to facilitate the monstrosity. // Once packed structs work make it packed. @@ -110,7 +112,7 @@ pub const Instruction = union(enum) { // -- less burden on callsite, bonus semantic checking fn bType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i13) Instruction { const umm = @bitCast(u13, imm); - if (umm % 2 != 0) @panic("Internal error: misaligned branch target"); + assert(umm % 2 == 0); // misaligned branch target return Instruction{ .B = .{ @@ -140,15 +142,15 @@ pub const Instruction = union(enum) { } fn jType(op: u7, rd: Register, imm: i21) Instruction { - const umm = @bitcast(u21, imm); - if (umm % 2 != 0) @panic("Internal error: misaligned jump target"); + const umm = @bitCast(u21, imm); + assert(umm % 2 == 0); // misaligned jump target return Instruction{ .J = .{ .opcode = op, .rd = @enumToInt(rd), .imm1_10 = @truncate(u10, umm >> 1), - .imm11 = @truncate(u1, umm >> 1), + .imm11 = @truncate(u1, umm >> 11), .imm12_19 = @truncate(u8, umm >> 12), .imm20 = @truncate(u1, umm >> 20), }, @@ -340,27 +342,27 @@ pub const Instruction = union(enum) { // Branch - pub fn beq(r1: Register, r2: Register, offset: u13) Instruction { + pub fn beq(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b000, r1, r2, offset); } - pub fn bne(r1: Register, r2: Register, offset: u13) Instruction { + pub fn bne(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b001, r1, r2, offset); } - pub fn blt(r1: Register, r2: Register, offset: u13) Instruction { + pub fn blt(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b100, r1, r2, offset); } - pub fn bge(r1: Register, r2: Register, offset: u13) Instruction { + pub fn bge(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b101, r1, r2, offset); } - pub fn bltu(r1: Register, r2: Register, offset: u13) Instruction { + pub fn bltu(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b110, r1, r2, offset); } - pub fn bgeu(r1: Register, r2: Register, offset: u13) Instruction { + pub fn bgeu(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b111, r1, r2, offset); } @@ -431,3 +433,38 @@ pub const Register = enum(u5) { pub const callee_preserved_regs = [_]Register{ .s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11, }; + +test "serialize instructions" { + const Testcase = struct { + inst: Instruction, + expected: u32, + }; + + const testcases = [_]Testcase{ + .{ // add t6, zero, zero + .inst = Instruction.add(.t6, .zero, .zero), + .expected = 0b0000000_00000_00000_000_11111_0110011, + }, + .{ // sd s0, 0x7f(s0) + .inst = Instruction.sd(.s0, 0x7f, .s0), + .expected = 0b0000011_01000_01000_011_11111_0100011, + }, + .{ // bne s0, s1, 0x42 + .inst = Instruction.bne(.s0, .s1, 0x42), + .expected = 0b0_000010_01001_01000_001_0001_0_1100011, + }, + .{ // j 0x1a + .inst = Instruction.jal(.zero, 0x1a), + .expected = 0b0_0000001101_0_00000000_00000_1101111, + }, + .{ // ebreak + .inst = Instruction.ebreak, + .expected = 0b000000000001_00000_000_00000_1110011, + }, + }; + + for (testcases) |case| { + const actual = case.inst.toU32(); + testing.expectEqual(case.expected, actual); + } +} diff --git a/src/libc_installation.zig b/src/libc_installation.zig index 6700787925..bb83416d30 100644 --- a/src/libc_installation.zig +++ b/src/libc_installation.zig @@ -9,6 +9,7 @@ const build_options = @import("build_options"); const is_darwin = Target.current.isDarwin(); const is_windows = Target.current.os.tag == .windows; const is_gnu = Target.current.isGnu(); +const is_haiku = Target.current.os.tag == .haiku; const log = std.log.scoped(.libc_installation); @@ -279,8 +280,14 @@ pub const LibCInstallation = struct { return error.CCompilerCannotFindHeaders; } - const include_dir_example_file = "stdlib.h"; - const sys_include_dir_example_file = if (is_windows) "sys\\types.h" else "sys/errno.h"; + const include_dir_example_file = if (is_haiku) "posix/stdlib.h" else "stdlib.h"; + const sys_include_dir_example_file = if (is_windows) + "sys\\types.h" + else if (is_haiku) + "posix/errno.h" + else + "sys/errno.h" + ; var path_i: usize = 0; while (path_i < search_paths.items.len) : (path_i += 1) { diff --git a/src/register_manager.zig b/src/register_manager.zig index 01f83aa2f5..270c762887 100644 --- a/src/register_manager.zig +++ b/src/register_manager.zig @@ -16,7 +16,7 @@ pub fn RegisterManager( ) type { return struct { /// The key must be canonical register. - registers: std.AutoHashMapUnmanaged(Register, *ir.Inst) = .{}, + registers: [callee_preserved_regs.len]?*ir.Inst = [_]?*ir.Inst{null} ** callee_preserved_regs.len, free_registers: FreeRegInt = math.maxInt(FreeRegInt), /// Tracks all registers allocated in the course of this function allocated_registers: FreeRegInt = 0, @@ -31,14 +31,6 @@ pub fn RegisterManager( return @fieldParentPtr(Function, "register_manager", self); } - pub fn deinit(self: *Self, allocator: *Allocator) void { - self.registers.deinit(allocator); - } - - fn isTracked(reg: Register) bool { - return reg.allocIndex() != null; - } - fn markRegUsed(self: *Self, reg: Register) void { if (FreeRegInt == u0) return; const index = reg.allocIndex() orelse return; @@ -73,13 +65,13 @@ pub fn RegisterManager( return self.allocated_registers & @as(FreeRegInt, 1) << shift != 0; } - /// Before calling, must ensureCapacity + count on self.registers. /// Returns `null` if all registers are allocated. pub fn tryAllocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ?[count]Register { if (self.tryAllocRegsWithoutTracking(count)) |regs| { for (regs) |reg, i| { + const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null + self.registers[index] = insts[i]; self.markRegUsed(reg); - self.registers.putAssumeCapacityNoClobber(reg, insts[i]); } return regs; @@ -88,13 +80,11 @@ pub fn RegisterManager( } } - /// Before calling, must ensureCapacity + 1 on self.registers. /// Returns `null` if all registers are allocated. pub fn tryAllocReg(self: *Self, inst: *ir.Inst) ?Register { return if (tryAllocRegs(self, 1, .{inst})) |regs| regs[0] else null; } - /// Before calling, must ensureCapacity + count on self.registers. pub fn allocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ![count]Register { comptime assert(count > 0 and count <= callee_preserved_regs.len); @@ -106,24 +96,22 @@ pub fn RegisterManager( std.mem.copy(Register, ®s, callee_preserved_regs[0..count]); for (regs) |reg, i| { + const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null if (self.isRegFree(reg)) { self.markRegUsed(reg); - self.registers.putAssumeCapacityNoClobber(reg, insts[i]); } else { - const regs_entry = self.registers.getEntry(reg).?; - const spilled_inst = regs_entry.value; - regs_entry.value = insts[i]; + const spilled_inst = self.registers[index].?; try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); } + self.registers[index] = insts[i]; } break :blk regs; }; } - /// Before calling, must ensureCapacity + 1 on self.registers. pub fn allocReg(self: *Self, inst: *ir.Inst) !Register { - return (try allocRegs(self, 1, .{inst}))[0]; + return (try self.allocRegs(1, .{inst}))[0]; } /// Does not track the registers. @@ -150,37 +138,48 @@ pub fn RegisterManager( /// Does not track the register. /// Returns `null` if all registers are allocated. pub fn tryAllocRegWithoutTracking(self: *Self) ?Register { - return if (tryAllocRegsWithoutTracking(self, 1)) |regs| regs[0] else null; + return if (self.tryAllocRegsWithoutTracking(1)) |regs| regs[0] else null; + } + + /// Does not track the registers + pub fn allocRegsWithoutTracking(self: *Self, comptime count: comptime_int) ![count]Register { + return self.tryAllocRegsWithoutTracking(count) orelse blk: { + // We'll take over the first count registers. Spill + // the instructions that were previously there to a + // stack allocations. + var regs: [count]Register = undefined; + std.mem.copy(Register, ®s, callee_preserved_regs[0..count]); + + for (regs) |reg, i| { + const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null + if (!self.isRegFree(reg)) { + const spilled_inst = self.registers[index].?; + try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); + self.registers[index] = null; + self.markRegFree(reg); + } + } + + break :blk regs; + }; } /// Does not track the register. pub fn allocRegWithoutTracking(self: *Self) !Register { - return self.tryAllocRegWithoutTracking() orelse b: { - // We'll take over the first register. Move the instruction that was previously - // there to a stack allocation. - const reg = callee_preserved_regs[0]; - const regs_entry = self.registers.remove(reg).?; - const spilled_inst = regs_entry.value; - try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); - self.markRegFree(reg); - - break :b reg; - }; + return (try self.allocRegsWithoutTracking(1))[0]; } /// Allocates the specified register with the specified /// instruction. Spills the register if it is currently /// allocated. - /// Before calling, must ensureCapacity + 1 on self.registers. pub fn getReg(self: *Self, reg: Register, inst: *ir.Inst) !void { - if (!isTracked(reg)) return; + const index = reg.allocIndex() orelse return; if (!self.isRegFree(reg)) { // Move the instruction that was previously there to a // stack allocation. - const regs_entry = self.registers.getEntry(reg).?; - const spilled_inst = regs_entry.value; - regs_entry.value = inst; + const spilled_inst = self.registers[index].?; + self.registers[index] = inst; try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); } else { self.getRegAssumeFree(reg, inst); @@ -190,34 +189,33 @@ pub fn RegisterManager( /// Spills the register if it is currently allocated. /// Does not track the register. pub fn getRegWithoutTracking(self: *Self, reg: Register) !void { - if (!isTracked(reg)) return; + const index = reg.allocIndex() orelse return; if (!self.isRegFree(reg)) { // Move the instruction that was previously there to a // stack allocation. - const regs_entry = self.registers.remove(reg).?; - const spilled_inst = regs_entry.value; + const spilled_inst = self.registers[index].?; try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); self.markRegFree(reg); } } /// Allocates the specified register with the specified - /// instruction. Assumes that the register is free and no + /// instruction. Asserts that the register is free and no /// spilling is necessary. - /// Before calling, must ensureCapacity + 1 on self.registers. pub fn getRegAssumeFree(self: *Self, reg: Register, inst: *ir.Inst) void { - if (!isTracked(reg)) return; + const index = reg.allocIndex() orelse return; - self.registers.putAssumeCapacityNoClobber(reg, inst); + assert(self.registers[index] == null); + self.registers[index] = inst; self.markRegUsed(reg); } /// Marks the specified register as free pub fn freeReg(self: *Self, reg: Register) void { - if (!isTracked(reg)) return; + const index = reg.allocIndex() orelse return; - _ = self.registers.remove(reg); + self.registers[index] = null; self.markRegFree(reg); } }; @@ -247,7 +245,6 @@ const MockFunction = struct { const Self = @This(); pub fn deinit(self: *Self) void { - self.register_manager.deinit(self.allocator); self.spilled.deinit(self.allocator); } @@ -273,7 +270,6 @@ test "tryAllocReg: no spilling" { std.testing.expect(!function.register_manager.isRegAllocated(.r2)); std.testing.expect(!function.register_manager.isRegAllocated(.r3)); - try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); std.testing.expectEqual(@as(?MockRegister, .r2), function.register_manager.tryAllocReg(&mock_instruction)); std.testing.expectEqual(@as(?MockRegister, .r3), function.register_manager.tryAllocReg(&mock_instruction)); std.testing.expectEqual(@as(?MockRegister, null), function.register_manager.tryAllocReg(&mock_instruction)); @@ -305,7 +301,6 @@ test "allocReg: spilling" { std.testing.expect(!function.register_manager.isRegAllocated(.r2)); std.testing.expect(!function.register_manager.isRegAllocated(.r3)); - try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction)); std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction)); @@ -336,14 +331,12 @@ test "getReg" { std.testing.expect(!function.register_manager.isRegAllocated(.r2)); std.testing.expect(!function.register_manager.isRegAllocated(.r3)); - try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); try function.register_manager.getReg(.r3, &mock_instruction); std.testing.expect(!function.register_manager.isRegAllocated(.r2)); std.testing.expect(function.register_manager.isRegAllocated(.r3)); // Spill r3 - try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); try function.register_manager.getReg(.r3, &mock_instruction); std.testing.expect(!function.register_manager.isRegAllocated(.r2)); diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 6cd28f4bbc..0991faf815 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -84,6 +84,7 @@ enum CallingConvention { CallingConventionAPCS, CallingConventionAAPCS, CallingConventionAAPCSVFP, + CallingConventionSysV }; // This one corresponds to the builtin.zig enum. diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index 21ad55b8f7..8ca845d2cf 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -974,6 +974,7 @@ const char *calling_convention_name(CallingConvention cc) { case CallingConventionAAPCS: return "AAPCS"; case CallingConventionAAPCSVFP: return "AAPCSVFP"; case CallingConventionInline: return "Inline"; + case CallingConventionSysV: return "SysV"; } zig_unreachable(); } @@ -995,6 +996,7 @@ bool calling_convention_allows_zig_types(CallingConvention cc) { case CallingConventionAPCS: case CallingConventionAAPCS: case CallingConventionAAPCSVFP: + case CallingConventionSysV: return false; } zig_unreachable(); @@ -1969,6 +1971,10 @@ Error emit_error_unless_callconv_allowed_for_target(CodeGen *g, AstNode *source_ case CallingConventionAAPCSVFP: if (!target_is_arm(g->zig_target)) allowed_platforms = "ARM"; + break; + case CallingConventionSysV: + if (g->zig_target->arch != ZigLLVM_x86_64) + allowed_platforms = "x86_64"; } if (allowed_platforms != nullptr) { add_node_error(g, source_node, buf_sprintf( @@ -3805,6 +3811,7 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { case CallingConventionAPCS: case CallingConventionAAPCS: case CallingConventionAAPCSVFP: + case CallingConventionSysV: add_fn_export(g, fn_table_entry, buf_ptr(&fn_table_entry->symbol_name), GlobalLinkageIdStrong, fn_cc); break; @@ -4769,11 +4776,11 @@ Error type_is_nonnull_ptr2(CodeGen *g, ZigType *type, bool *result) { return ErrorNone; } -static uint32_t get_async_frame_align_bytes(CodeGen *g) { - uint32_t a = g->pointer_size_bytes * 2; - // promises have at least alignment 8 so that we can have 3 extra bits when doing atomicrmw - if (a < 8) a = 8; - return a; +uint32_t get_async_frame_align_bytes(CodeGen *g) { + // Due to how the frame structure is built the minimum alignment is the one + // of a usize (or pointer). + // label (grep this): [fn_frame_struct_layout] + return max(g->builtin_types.entry_usize->abi_align, target_fn_align(g->zig_target)); } uint32_t get_ptr_align(CodeGen *g, ZigType *type) { @@ -4789,11 +4796,8 @@ uint32_t get_ptr_align(CodeGen *g, ZigType *type) { return (ptr_type->data.pointer.explicit_alignment == 0) ? get_abi_alignment(g, ptr_type->data.pointer.child_type) : ptr_type->data.pointer.explicit_alignment; } else if (ptr_type->id == ZigTypeIdFn) { - // I tried making this use LLVMABIAlignmentOfType but it trips this assertion in LLVM: - // "Cannot getTypeInfo() on a type that is unsized!" - // when getting the alignment of `?fn() callconv(.C) void`. - // See http://lists.llvm.org/pipermail/llvm-dev/2018-September/126142.html - return (ptr_type->data.fn.fn_type_id.alignment == 0) ? 1 : ptr_type->data.fn.fn_type_id.alignment; + return (ptr_type->data.fn.fn_type_id.alignment == 0) ? + target_fn_ptr_align(g->zig_target) : ptr_type->data.fn.fn_type_id.alignment; } else if (ptr_type->id == ZigTypeIdAnyFrame) { return get_async_frame_align_bytes(g); } else { diff --git a/src/stage1/analyze.hpp b/src/stage1/analyze.hpp index cea48b893c..2815274f63 100644 --- a/src/stage1/analyze.hpp +++ b/src/stage1/analyze.hpp @@ -47,6 +47,7 @@ ZigType *get_test_fn_type(CodeGen *g); ZigType *get_any_frame_type(CodeGen *g, ZigType *result_type); bool handle_is_ptr(CodeGen *g, ZigType *type_entry); Error emit_error_unless_callconv_allowed_for_target(CodeGen *g, AstNode *source_node, CallingConvention cc); +uint32_t get_async_frame_align_bytes(CodeGen *g); bool type_has_bits(CodeGen *g, ZigType *type_entry); Error type_has_bits2(CodeGen *g, ZigType *type_entry, bool *result); diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 1f30cd0a85..cf1cf81004 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -204,6 +204,9 @@ static ZigLLVM_CallingConv get_llvm_cc(CodeGen *g, CallingConvention cc) { case CallingConventionSignal: assert(g->zig_target->arch == ZigLLVM_avr); return ZigLLVM_AVR_SIGNAL; + case CallingConventionSysV: + assert(g->zig_target->arch == ZigLLVM_x86_64); + return ZigLLVM_X86_64_SysV; } zig_unreachable(); } @@ -348,6 +351,7 @@ static bool cc_want_sret_attr(CallingConvention cc) { case CallingConventionAPCS: case CallingConventionAAPCS: case CallingConventionAAPCSVFP: + case CallingConventionSysV: return true; case CallingConventionAsync: case CallingConventionUnspecified: @@ -9079,6 +9083,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { static_assert(CallingConventionAPCS == 11, ""); static_assert(CallingConventionAAPCS == 12, ""); static_assert(CallingConventionAAPCSVFP == 13, ""); + static_assert(CallingConventionSysV == 14, ""); static_assert(BuiltinPtrSizeOne == 0, ""); static_assert(BuiltinPtrSizeMany == 1, ""); diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index c59f63399c..2f345a8411 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -19216,6 +19216,7 @@ static IrInstGen *ir_analyze_instruction_export(IrAnalyze *ira, IrInstSrcExport case CallingConventionAPCS: case CallingConventionAAPCS: case CallingConventionAAPCSVFP: + case CallingConventionSysV: add_fn_export(ira->codegen, fn_entry, buf_ptr(symbol_name), global_linkage_id, cc); fn_entry->section_name = section_name; break; @@ -20659,8 +20660,12 @@ static IrInstGen *analyze_casted_new_stack(IrAnalyze *ira, IrInst* source_instr, get_fn_frame_type(ira->codegen, fn_entry), false); return ir_implicit_cast(ira, new_stack, needed_frame_type); } else { + // XXX The stack alignment is hardcoded to 16 here and in + // std.Target.stack_align. + const uint32_t required_align = is_async_call_builtin ? + get_async_frame_align_bytes(ira->codegen) : 16; ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8, - false, false, PtrLenUnknown, target_fn_align(ira->codegen->zig_target), 0, 0, false); + false, false, PtrLenUnknown, required_align, 0, 0, false); ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr); ira->codegen->need_frame_size_prefix_data = true; return ir_implicit_cast2(ira, new_stack_src, new_stack, u8_slice); @@ -26079,11 +26084,11 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy fields[0]->special = ConstValSpecialStatic; fields[0]->type = get_builtin_type(ira->codegen, "CallingConvention"); bigint_init_unsigned(&fields[0]->data.x_enum_tag, type_entry->data.fn.fn_type_id.cc); - // alignment: u29 + // alignment: comptime_int ensure_field_index(result->type, "alignment", 1); fields[1]->special = ConstValSpecialStatic; fields[1]->type = ira->codegen->builtin_types.entry_num_lit_int; - bigint_init_unsigned(&fields[1]->data.x_bigint, type_entry->data.fn.fn_type_id.alignment); + bigint_init_unsigned(&fields[1]->data.x_bigint, get_ptr_align(ira->codegen, type_entry)); // is_generic: bool ensure_field_index(result->type, "is_generic", 2); bool is_generic = type_entry->data.fn.is_generic; @@ -30095,7 +30100,7 @@ static IrInstGen *ir_align_cast(IrAnalyze *ira, IrInstGen *target, uint32_t alig fn_type_id.alignment = align_bytes; result_type = get_fn_type(ira->codegen, &fn_type_id); } else if (target_type->id == ZigTypeIdAnyFrame) { - if (align_bytes >= target_fn_align(ira->codegen->zig_target)) { + if (align_bytes >= get_async_frame_align_bytes(ira->codegen)) { result_type = target_type; } else { ir_add_error(ira, &target->base, buf_sprintf("sub-aligned anyframe not allowed")); diff --git a/src/stage1/target.cpp b/src/stage1/target.cpp index 028f6e5fa8..6aa3cfcbd0 100644 --- a/src/stage1/target.cpp +++ b/src/stage1/target.cpp @@ -1253,6 +1253,37 @@ bool target_is_ppc(const ZigTarget *target) { target->arch == ZigLLVM_ppc64le; } -unsigned target_fn_align(const ZigTarget *target) { - return 16; +// Returns the minimum alignment for every function pointer on the given +// architecture. +unsigned target_fn_ptr_align(const ZigTarget *target) { + // TODO This is a pessimization but is always correct. + return 1; +} + +// Returns the minimum alignment for every function on the given architecture. +unsigned target_fn_align(const ZigTarget *target) { + switch (target->arch) { + case ZigLLVM_riscv32: + case ZigLLVM_riscv64: + // TODO If the C extension is not present the value is 4. + return 2; + case ZigLLVM_ppc: + case ZigLLVM_ppcle: + case ZigLLVM_ppc64: + case ZigLLVM_ppc64le: + case ZigLLVM_aarch64: + case ZigLLVM_aarch64_be: + case ZigLLVM_aarch64_32: + case ZigLLVM_sparc: + case ZigLLVM_sparcel: + case ZigLLVM_sparcv9: + case ZigLLVM_mips: + case ZigLLVM_mipsel: + case ZigLLVM_mips64: + case ZigLLVM_mips64el: + return 4; + + default: + return 1; + } } diff --git a/src/stage1/target.hpp b/src/stage1/target.hpp index 099c3c1e78..362d63eb32 100644 --- a/src/stage1/target.hpp +++ b/src/stage1/target.hpp @@ -98,6 +98,7 @@ size_t target_libc_count(void); void target_libc_enum(size_t index, ZigTarget *out_target); bool target_libc_needs_crti_crtn(const ZigTarget *target); +unsigned target_fn_ptr_align(const ZigTarget *target); unsigned target_fn_align(const ZigTarget *target); #endif diff --git a/src/translate_c.zig b/src/translate_c.zig index ac5c52ee0d..8244d66e94 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -8,6 +8,7 @@ const ctok = std.c.tokenizer; const CToken = std.c.Token; const mem = std.mem; const math = std.math; +const meta = std.meta; const ast = @import("translate_c/ast.zig"); const Node = ast.Node; const Tag = Node.Tag; @@ -1741,7 +1742,7 @@ fn transImplicitCastExpr( } fn isBuiltinDefined(name: []const u8) bool { - inline for (std.meta.declarations(c_builtins)) |decl| { + inline for (meta.declarations(c_builtins)) |decl| { if (std.mem.eql(u8, name, decl.name)) return true; } return false; @@ -3157,7 +3158,7 @@ const ClangFunctionType = union(enum) { NoProto: *const clang.FunctionType, fn getReturnType(self: @This()) clang.QualType { - switch (@as(std.meta.Tag(@This()), self)) { + switch (@as(meta.Tag(@This()), self)) { .Proto => return self.Proto.getReturnType(), .NoProto => return self.NoProto.getReturnType(), } @@ -3539,7 +3540,7 @@ fn transCPtrCast( expr else blk: { const child_type_node = try transQualType(c, scope, child_type, loc); - const alignof = try Tag.alignof.create(c.arena, child_type_node); + const alignof = try Tag.std_meta_alignment.create(c.arena, child_type_node); const align_cast = try Tag.align_cast.create(c.arena, .{ .lhs = alignof, .rhs = expr }); break :blk align_cast; }; @@ -3547,9 +3548,22 @@ fn transCPtrCast( } } -fn transFloatingLiteral(c: *Context, scope: *Scope, stmt: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node { +fn transFloatingLiteral(c: *Context, scope: *Scope, expr: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node { + switch (expr.getRawSemantics()) { + .IEEEhalf, // f16 + .IEEEsingle, // f32 + .IEEEdouble, // f64 + => {}, + else => |format| return fail( + c, + error.UnsupportedTranslation, + expr.getBeginLoc(), + "unsupported floating point constant format {}", + .{format}, + ), + } // TODO use something more accurate - var dbl = stmt.getValueAsApproximateDouble(); + var dbl = expr.getValueAsApproximateDouble(); const is_negative = dbl < 0; if (is_negative) dbl = -dbl; const str = if (dbl == std.math.floor(dbl)) @@ -4093,7 +4107,7 @@ fn transCreateNodeAPInt(c: *Context, int: *const clang.APSInt) !Node { } fn transCreateNodeNumber(c: *Context, num: anytype, num_kind: enum { int, float }) !Node { - const fmt_s = if (comptime std.meta.trait.isNumber(@TypeOf(num))) "{d}" else "{s}"; + const fmt_s = if (comptime meta.trait.isNumber(@TypeOf(num))) "{d}" else "{s}"; const str = try std.fmt.allocPrint(c.arena, fmt_s, .{num}); if (num_kind == .float) return Tag.float_literal.create(c.arena, str) @@ -4402,6 +4416,7 @@ fn transCC( .X86ThisCall => return CallingConvention.Thiscall, .AAPCS => return CallingConvention.AAPCS, .AAPCS_VFP => return CallingConvention.AAPCSVFP, + .X86_64SysV => return CallingConvention.SysV, else => return fail( c, error.UnsupportedType, @@ -4848,12 +4863,12 @@ fn parseCNumLit(c: *Context, m: *MacroCtx) ParseError!Node { // make the output less noisy by skipping promoteIntLiteral where // it's guaranteed to not be required because of C standard type constraints const guaranteed_to_fit = switch (suffix) { - .none => if (math.cast(i16, value)) |_| true else |_| false, - .u => if (math.cast(u16, value)) |_| true else |_| false, - .l => if (math.cast(i32, value)) |_| true else |_| false, - .lu => if (math.cast(u32, value)) |_| true else |_| false, - .ll => if (math.cast(i64, value)) |_| true else |_| false, - .llu => if (math.cast(u64, value)) |_| true else |_| false, + .none => !meta.isError(math.cast(i16, value)), + .u => !meta.isError(math.cast(u16, value)), + .l => !meta.isError(math.cast(i32, value)), + .lu => !meta.isError(math.cast(u32, value)), + .ll => !meta.isError(math.cast(i64, value)), + .llu => !meta.isError(math.cast(u64, value)), .f => unreachable, }; diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 61d28bb22d..2e75bf1182 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -120,8 +120,11 @@ pub const Node = extern union { std_math_Log2Int, /// @intCast(lhs, rhs) int_cast, - /// @rem(lhs, rhs) + /// @import("std").meta.promoteIntLiteral(value, type, radix) std_meta_promoteIntLiteral, + /// @import("std").meta.alignment(value) + std_meta_alignment, + /// @rem(lhs, rhs) rem, /// @divTrunc(lhs, rhs) div_trunc, @@ -260,6 +263,7 @@ pub const Node = extern union { .switch_else, .block_single, .std_meta_sizeof, + .std_meta_alignment, .bool_to_int, .sizeof, .alignof, @@ -876,6 +880,11 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { const import_node = try renderStdImport(c, "meta", "promoteIntLiteral"); return renderCall(c, import_node, &.{ payload.type, payload.value, payload.radix }); }, + .std_meta_alignment => { + const payload = node.castTag(.std_meta_alignment).?.data; + const import_node = try renderStdImport(c, "meta", "alignment"); + return renderCall(c, import_node, &.{payload}); + }, .std_meta_sizeof => { const payload = node.castTag(.std_meta_sizeof).?.data; const import_node = try renderStdImport(c, "meta", "sizeof"); @@ -2144,6 +2153,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .typeof, .typeinfo, .std_meta_sizeof, + .std_meta_alignment, .std_meta_cast, .std_meta_promoteIntLiteral, .std_meta_vector, diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index 32bb9b4487..efdef1e26b 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -2528,6 +2528,11 @@ double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatin return casted->getValueAsApproximateDouble(); } +ZigClangAPFloatBase_Semantics ZigClangFloatingLiteral_getRawSemantics(const ZigClangFloatingLiteral *self) { + auto casted = reinterpret_cast(self); + return static_cast(casted->getRawSemantics()); +} + enum ZigClangStringLiteral_StringKind ZigClangStringLiteral_getKind(const struct ZigClangStringLiteral *self) { auto casted = reinterpret_cast(self); return (ZigClangStringLiteral_StringKind)casted->getKind(); diff --git a/src/zig_clang.h b/src/zig_clang.h index eac3592692..4ea8cc731b 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -881,6 +881,16 @@ enum ZigClangAPFloat_roundingMode { ZigClangAPFloat_roundingMode_Invalid = -1, }; +enum ZigClangAPFloatBase_Semantics { + ZigClangAPFloatBase_Semantics_IEEEhalf, + ZigClangAPFloatBase_Semantics_BFloat, + ZigClangAPFloatBase_Semantics_IEEEsingle, + ZigClangAPFloatBase_Semantics_IEEEdouble, + ZigClangAPFloatBase_Semantics_x87DoubleExtended, + ZigClangAPFloatBase_Semantics_IEEEquad, + ZigClangAPFloatBase_Semantics_PPCDoubleDouble, +}; + enum ZigClangStringLiteral_StringKind { ZigClangStringLiteral_StringKind_Ascii, ZigClangStringLiteral_StringKind_Wide, @@ -1142,6 +1152,7 @@ ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangDeclStmt_getBeginLoc(const st ZIG_EXTERN_C unsigned ZigClangAPFloat_convertToHexString(const struct ZigClangAPFloat *self, char *DST, unsigned HexDigits, bool UpperCase, enum ZigClangAPFloat_roundingMode RM); ZIG_EXTERN_C double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatingLiteral *self); +ZIG_EXTERN_C ZigClangAPFloatBase_Semantics ZigClangFloatingLiteral_getRawSemantics(const ZigClangFloatingLiteral *self); ZIG_EXTERN_C enum ZigClangStringLiteral_StringKind ZigClangStringLiteral_getKind(const struct ZigClangStringLiteral *self); ZIG_EXTERN_C uint32_t ZigClangStringLiteral_getCodeUnit(const struct ZigClangStringLiteral *self, size_t i); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index bfa9b592b4..5b36027248 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2136,7 +2136,9 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\} \\fn func() callconv(.Async) void {} , &[_][]const u8{ - "tmp.zig:4:21: error: expected type '[]align(16) u8', found '*[64]u8'", + // Split the check in two as the alignment value is target dependent. + "tmp.zig:4:21: error: expected type '[]align(", + ") u8', found '*[64]u8'", }); cases.add("atomic orderings of fence Acquire or stricter", diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index 6dec7ca4d2..f944b7904c 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -306,7 +306,7 @@ test "type info: function type info" { fn testFunction() void { const fn_info = @typeInfo(@TypeOf(foo)); expect(fn_info == .Fn); - expect(fn_info.Fn.alignment == 0); + expect(fn_info.Fn.alignment > 0); expect(fn_info.Fn.calling_convention == .C); expect(!fn_info.Fn.is_generic); expect(fn_info.Fn.args.len == 2); diff --git a/test/stage2/riscv64.zig b/test/stage2/riscv64.zig new file mode 100644 index 0000000000..e2035be47a --- /dev/null +++ b/test/stage2/riscv64.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const TestContext = @import("../../src/test.zig").TestContext; + +const linux_riscv64 = std.zig.CrossTarget{ + .cpu_arch = .riscv64, + .os_tag = .linux, +}; + +pub fn addCases(ctx: *TestContext) !void { + { + var case = ctx.exe("riscv64 hello world", linux_riscv64); + // Regular old hello world + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("ecall" + \\ : + \\ : [number] "{a7}" (64), + \\ [arg1] "{a0}" (1), + \\ [arg2] "{a1}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{a2}" ("Hello, World!\n".len) + \\ : "rcx", "r11", "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("ecall" + \\ : + \\ : [number] "{a7}" (94), + \\ [arg1] "{a0}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + } +} diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 5fec836038..60c0c72793 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -11,11 +11,6 @@ const linux_x64 = std.zig.CrossTarget{ .os_tag = .linux, }; -const linux_riscv64 = std.zig.CrossTarget{ - .cpu_arch = .riscv64, - .os_tag = .linux, -}; - pub fn addCases(ctx: *TestContext) !void { try @import("cbe.zig").addCases(ctx); try @import("spu-ii.zig").addCases(ctx); @@ -24,6 +19,7 @@ pub fn addCases(ctx: *TestContext) !void { try @import("llvm.zig").addCases(ctx); try @import("wasm.zig").addCases(ctx); try @import("darwin.zig").addCases(ctx); + try @import("riscv64.zig").addCases(ctx); { var case = ctx.exe("hello world with updates", linux_x64); @@ -137,42 +133,6 @@ pub fn addCases(ctx: *TestContext) !void { ); } - { - var case = ctx.exe("riscv64 hello world", linux_riscv64); - // Regular old hello world - case.addCompareOutput( - \\export fn _start() noreturn { - \\ print(); - \\ - \\ exit(); - \\} - \\ - \\fn print() void { - \\ asm volatile ("ecall" - \\ : - \\ : [number] "{a7}" (64), - \\ [arg1] "{a0}" (1), - \\ [arg2] "{a1}" (@ptrToInt("Hello, World!\n")), - \\ [arg3] "{a2}" ("Hello, World!\n".len) - \\ : "rcx", "r11", "memory" - \\ ); - \\ return; - \\} - \\ - \\fn exit() noreturn { - \\ asm volatile ("ecall" - \\ : - \\ : [number] "{a7}" (94), - \\ [arg1] "{a0}" (0) - \\ : "rcx", "r11", "memory" - \\ ); - \\ unreachable; - \\} - , - "Hello, World!\n", - ); - } - { var case = ctx.exe("adding numbers at comptime", linux_x64); case.addCompareOutput( diff --git a/test/translate_c.zig b/test/translate_c.zig index 5fa4c32041..4021210b29 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1363,7 +1363,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , &[_][]const u8{ \\pub export fn ptrcast() [*c]f32 { \\ var a: [*c]c_int = undefined; - \\ return @ptrCast([*c]f32, @alignCast(@alignOf(f32), a)); + \\ return @ptrCast([*c]f32, @alignCast(@import("std").meta.alignment(f32), a)); \\} }); @@ -1387,16 +1387,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub export fn test_ptr_cast() void { \\ var p: ?*c_void = undefined; \\ { - \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@alignOf(u8), p)); - \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@alignOf(c_short), p)); - \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@alignOf(c_int), p)); - \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@alignOf(c_longlong), p)); + \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@import("std").meta.alignment(u8), p)); + \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@import("std").meta.alignment(c_short), p)); + \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@import("std").meta.alignment(c_int), p)); + \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@import("std").meta.alignment(c_longlong), p)); \\ } \\ { - \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@alignOf(u8), p)); - \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@alignOf(c_short), p)); - \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@alignOf(c_int), p)); - \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@alignOf(c_longlong), p)); + \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@import("std").meta.alignment(u8), p)); + \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@import("std").meta.alignment(c_short), p)); + \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@import("std").meta.alignment(c_int), p)); + \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@import("std").meta.alignment(c_longlong), p)); \\ } \\} }); @@ -3028,7 +3028,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\void call() { \\ fn_int(3.0f); \\ fn_int(3.0); - \\ fn_int(3.0L); \\ fn_int('ABCD'); \\ fn_f32(3); \\ fn_f64(3); @@ -3053,7 +3052,6 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub export fn call() void { \\ fn_int(@floatToInt(c_int, 3.0)); \\ fn_int(@floatToInt(c_int, 3.0)); - \\ fn_int(@floatToInt(c_int, 3.0)); \\ fn_int(@as(c_int, 1094861636)); \\ fn_f32(@intToFloat(f32, @as(c_int, 3))); \\ fn_f64(@intToFloat(f64, @as(c_int, 3)));