commit df24ce52b1059232db66e9af425a02df558aa1ca (tree)
parent eb9c29eb817a24e5326b9a63ebf7265d3b2bad2c
Author: Andrew Kelley <andrew@ziglang.org>
Date: Wed, 28 Apr 2021 14:53:50 -0700
Merge remote-tracking branch 'origin/master' into stage2-whole-file-astgen
In particular I wanted to take advantage of the new hex float parsing
code.
Diffstat:
52 files changed, 1674 insertions(+), 485 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 a<b, .gt if a>b 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 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 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 = 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;
-
-/// 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 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;
+
+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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 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);
+ /// 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]);
- break :b reg;
+ 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 (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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1253,6 +1253,37 @@ bool target_is_ppc(const ZigTarget *target) {
target->arch == ZigLLVM_ppc64le;
}
+// 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) {
- return 16;
+ 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
@@ -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
@@ -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
@@ -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
@@ -2528,6 +2528,11 @@ double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatin
return casted->getValueAsApproximateDouble();
}
+ZigClangAPFloatBase_Semantics ZigClangFloatingLiteral_getRawSemantics(const ZigClangFloatingLiteral *self) {
+ auto casted = reinterpret_cast<const clang::FloatingLiteral *>(self);
+ return static_cast<ZigClangAPFloatBase_Semantics>(casted->getRawSemantics());
+}
+
enum ZigClangStringLiteral_StringKind ZigClangStringLiteral_getKind(const struct ZigClangStringLiteral *self) {
auto casted = reinterpret_cast<const clang::StringLiteral *>(self);
return (ZigClangStringLiteral_StringKind)casted->getKind();
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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);
@@ -138,42 +134,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(
\\export fn _start() noreturn {
diff --git 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)));