From 9ebdbca379fb71da7236e7534e1d3465f7fb151f Mon Sep 17 00:00:00 2001 From: Sreehari S Date: Thu, 8 Apr 2021 21:00:53 -0700 Subject: [PATCH 01/33] callconv: add SysV --- lib/std/builtin.zig | 1 + src/stage1/all_types.hpp | 1 + src/stage1/analyze.cpp | 7 +++++++ src/stage1/codegen.cpp | 5 +++++ src/stage1/ir.cpp | 1 + src/translate_c.zig | 1 + 6 files changed, 16 insertions(+) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 93de8ae3b9..a4da9ba2f3 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -165,6 +165,7 @@ pub const CallingConvention = enum { APCS, AAPCS, AAPCSVFP, + SysV }; /// This data structure is used by the Zig language code generation and diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 9085acd557..d3015cb90f 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -84,6 +84,7 @@ enum CallingConvention { CallingConventionAPCS, CallingConventionAAPCS, CallingConventionAAPCSVFP, + CallingConventionSysV }; // This one corresponds to the builtin.zig enum. diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index d7535ed806..196ddd9fa2 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -974,6 +974,7 @@ const char *calling_convention_name(CallingConvention cc) { case CallingConventionAAPCS: return "AAPCS"; case CallingConventionAAPCSVFP: return "AAPCSVFP"; case CallingConventionInline: return "Inline"; + case CallingConventionSysV: return "SysV"; } zig_unreachable(); } @@ -995,6 +996,7 @@ bool calling_convention_allows_zig_types(CallingConvention cc) { case CallingConventionAPCS: case CallingConventionAAPCS: case CallingConventionAAPCSVFP: + case CallingConventionSysV: return false; } zig_unreachable(); @@ -1969,6 +1971,10 @@ Error emit_error_unless_callconv_allowed_for_target(CodeGen *g, AstNode *source_ case CallingConventionAAPCSVFP: if (!target_is_arm(g->zig_target)) allowed_platforms = "ARM"; + case CallingConventionSysV: + if (g->zig_target->arch != ZigLLVM_x86_64) + allowed_platforms = "x86_64"; + break; } 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; diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 5c37a1247b..41555f8ef7 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -204,6 +204,9 @@ static ZigLLVM_CallingConv get_llvm_cc(CodeGen *g, CallingConvention cc) { case CallingConventionSignal: assert(g->zig_target->arch == ZigLLVM_avr); return ZigLLVM_AVR_SIGNAL; + case CallingConventionSysV: + assert(g->zig_target->arch == ZigLLVM_x86_64); + return ZigLLVM_X86_64_SysV; } zig_unreachable(); } @@ -348,6 +351,7 @@ static bool cc_want_sret_attr(CallingConvention cc) { case CallingConventionAPCS: case CallingConventionAAPCS: case CallingConventionAAPCSVFP: + case CallingConventionSysV: return true; case CallingConventionAsync: case CallingConventionUnspecified: @@ -9101,6 +9105,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { static_assert(CallingConventionAPCS == 11, ""); static_assert(CallingConventionAAPCS == 12, ""); static_assert(CallingConventionAAPCSVFP == 13, ""); + static_assert(CallingConventionSysV == 14, ""); static_assert(BuiltinPtrSizeOne == 0, ""); static_assert(BuiltinPtrSizeMany == 1, ""); diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 71a233c964..cb10bf1d4e 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -19223,6 +19223,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; diff --git a/src/translate_c.zig b/src/translate_c.zig index 5962b85a6b..ffe99f05c8 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -4381,6 +4381,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, From 906ac7b2f9ef9a485c45884d71b6e30379084641 Mon Sep 17 00:00:00 2001 From: Sreehari S Date: Thu, 8 Apr 2021 22:26:35 -0700 Subject: [PATCH 02/33] fix unannotated fall-through bug in case --- src/stage1/analyze.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index 196ddd9fa2..c56344c53a 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -1971,10 +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"; - break; } if (allowed_platforms != nullptr) { add_node_error(g, source_node, buf_sprintf( From a5007d819a0bd4d247602786a36cabae821f52b9 Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Sun, 11 Apr 2021 16:26:29 +0200 Subject: [PATCH 03/33] std.meta: add isError --- lib/std/json/test.zig | 6 ++---- lib/std/meta.zig | 10 ++++++++++ lib/std/unicode.zig | 2 +- src/translate_c.zig | 19 ++++++++++--------- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/std/json/test.zig b/lib/std/json/test.zig index b6ff03350c..b0d873c910 100644 --- a/lib/std/json/test.zig +++ b/lib/std/json/test.zig @@ -29,8 +29,7 @@ fn ok(s: []const u8) !void { fn err(s: []const u8) void { testing.expect(!json.validate(s)); - testNonStreaming(s) catch return; - testing.expect(false); + testing.expect(std.meta.isError(testNonStreaming(s))); } fn utf8Error(s: []const u8) void { @@ -48,8 +47,7 @@ fn any(s: []const u8) void { fn anyStreamingErrNonStreaming(s: []const u8) void { _ = json.validate(s); - testNonStreaming(s) catch return; - testing.expect(false); + testing.expect(std.meta.isError(testNonStreaming(s))); } fn roundTrip(s: []const u8) !void { diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 860b6874c0..122e644bf3 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -1334,3 +1334,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/unicode.zig b/lib/std/unicode.zig index f9ad6e3eb5..9eed0f466f 100644 --- a/lib/std/unicode.zig +++ b/lib/std/unicode.zig @@ -206,7 +206,7 @@ pub fn utf8ValidateSlice(s: []const u8) bool { return false; } - if (utf8Decode(s[i .. i + cp_len])) |_| {} else |_| { + if (std.meta.isError(utf8Decode(s[i .. i + cp_len]))) { return false; } i += cp_len; diff --git a/src/translate_c.zig b/src/translate_c.zig index 5962b85a6b..393c6d08bc 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -8,6 +8,7 @@ const ctok = std.c.tokenizer; const CToken = std.c.Token; const mem = std.mem; const math = std.math; +const meta = std.meta; const ast = @import("translate_c/ast.zig"); const Node = ast.Node; const Tag = Node.Tag; @@ -1737,7 +1738,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; @@ -3136,7 +3137,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(), } @@ -4072,7 +4073,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) @@ -4827,12 +4828,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, }; From 89c41f30c3e62e8b5c03457d1f95dc115a27ef15 Mon Sep 17 00:00:00 2001 From: xackus <14938807+xackus@users.noreply.github.com> Date: Sun, 11 Apr 2021 20:16:17 +0200 Subject: [PATCH 04/33] zig fmt --- lib/std/meta.zig | 2 +- src/translate_c.zig | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 122e644bf3..e4e04ab5d2 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -1337,7 +1337,7 @@ test "shuffleVectorIndex" { /// Returns whether `error_union` contains an error. pub fn isError(error_union: anytype) bool { - return if(error_union) |_| false else |_| true; + return if (error_union) |_| false else |_| true; } test "isError" { diff --git a/src/translate_c.zig b/src/translate_c.zig index 393c6d08bc..19785b29a6 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2358,7 +2358,6 @@ fn transInitListExprVector( expr: *const clang.InitListExpr, ty: *const clang.Type, ) TransError!Node { - const qt = getExprQualType(c, @ptrCast(*const clang.Expr, expr)); const vector_type = try transQualType(c, scope, qt, loc); const init_count = expr.getNumInits(); From 7a78b92fa0283501664a6f25bc68471dea04fab5 Mon Sep 17 00:00:00 2001 From: Al Hoang <3811822-hoanga@users.noreply.gitlab.com> Date: Fri, 23 Apr 2021 23:37:35 -0500 Subject: [PATCH 05/33] header path detection for haiku --- src/libc_installation.zig | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/libc_installation.zig b/src/libc_installation.zig index 6700787925..bb83416d30 100644 --- a/src/libc_installation.zig +++ b/src/libc_installation.zig @@ -9,6 +9,7 @@ const build_options = @import("build_options"); const is_darwin = Target.current.isDarwin(); const is_windows = Target.current.os.tag == .windows; const is_gnu = Target.current.isGnu(); +const is_haiku = Target.current.os.tag == .haiku; const log = std.log.scoped(.libc_installation); @@ -279,8 +280,14 @@ pub const LibCInstallation = struct { return error.CCompilerCannotFindHeaders; } - const include_dir_example_file = "stdlib.h"; - const sys_include_dir_example_file = if (is_windows) "sys\\types.h" else "sys/errno.h"; + const include_dir_example_file = if (is_haiku) "posix/stdlib.h" else "stdlib.h"; + const sys_include_dir_example_file = if (is_windows) + "sys\\types.h" + else if (is_haiku) + "posix/errno.h" + else + "sys/errno.h" + ; var path_i: usize = 0; while (path_i < search_paths.items.len) : (path_i += 1) { From 914d57a1a64dd08983aeaa3e1eeb286043211b58 Mon Sep 17 00:00:00 2001 From: Al Hoang <3811822-hoanga@users.noreply.gitlab.com> Date: Fri, 23 Apr 2021 23:39:13 -0500 Subject: [PATCH 06/33] support output collection for haiku --- lib/std/child_process.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index d37dd9fdf5..01db179bbe 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -264,7 +264,7 @@ pub const ChildProcess = struct { // TODO collect output in a deadlock-avoiding way on Windows. // https://github.com/ziglang/zig/issues/6343 - if (builtin.os.tag == .windows) { + if (builtin.os.tag == .windows or builtin.os.tag == .haiku) { const stdout_in = child.stdout.?.reader(); const stderr_in = child.stderr.?.reader(); From 762d4a959cc4e61fb88f68ba067ec97c5e2fe869 Mon Sep 17 00:00:00 2001 From: Al Hoang <3811822-hoanga@users.noreply.gitlab.com> Date: Fri, 23 Apr 2021 23:40:42 -0500 Subject: [PATCH 07/33] update haiku system constants --- lib/std/c/haiku.zig | 48 ++++++ lib/std/os/bits/haiku.zig | 328 +++++++++++++++----------------------- 2 files changed, 173 insertions(+), 203 deletions(-) diff --git a/lib/std/c/haiku.zig b/lib/std/c/haiku.zig index e361a7520e..1aa3ac31e2 100644 --- a/lib/std/c/haiku.zig +++ b/lib/std/c/haiku.zig @@ -69,3 +69,51 @@ pub const pthread_rwlock_t = extern struct { writer_count: i32 = 0, waiters: [2]?*c_void = [_]?*c_void{ null, null }, }; + +pub const EAI = extern enum(c_int) { + /// address family for hostname not supported + ADDRFAMILY = 1, + + /// name could not be resolved at this time + AGAIN = 2, + + /// flags parameter had an invalid value + BADFLAGS = 3, + + /// non-recoverable failure in name resolution + FAIL = 4, + + /// address family not recognized + FAMILY = 5, + + /// memory allocation failure + MEMORY = 6, + + /// no address associated with hostname + NODATA = 7, + + /// name does not resolve + NONAME = 8, + + /// service not recognized for socket type + SERVICE = 9, + + /// intended socket type was not recognized + SOCKTYPE = 10, + + /// system error returned in errno + SYSTEM = 11, + + /// invalid value for hints + BADHINTS = 12, + + /// resolved protocol is unknown + PROTOCOL = 13, + + /// argument buffer overflow + OVERFLOW = 14, + + _, +}; + +pub const EAI_MAX = 15; diff --git a/lib/std/os/bits/haiku.zig b/lib/std/os/bits/haiku.zig index 32093570d7..25a881ba56 100644 --- a/lib/std/os/bits/haiku.zig +++ b/lib/std/os/bits/haiku.zig @@ -279,20 +279,10 @@ pub const PROT_READ = 1; pub const PROT_WRITE = 2; pub const PROT_EXEC = 4; -pub const CLOCK_REALTIME = 0; -pub const CLOCK_VIRTUAL = 1; -pub const CLOCK_PROF = 2; -pub const CLOCK_MONOTONIC = 4; -pub const CLOCK_UPTIME = 5; -pub const CLOCK_UPTIME_PRECISE = 7; -pub const CLOCK_UPTIME_FAST = 8; -pub const CLOCK_REALTIME_PRECISE = 9; -pub const CLOCK_REALTIME_FAST = 10; -pub const CLOCK_MONOTONIC_PRECISE = 11; -pub const CLOCK_MONOTONIC_FAST = 12; -pub const CLOCK_SECOND = 13; -pub const CLOCK_THREAD_CPUTIME_ID = 14; -pub const CLOCK_PROCESS_CPUTIME_ID = 15; +pub const CLOCK_MONOTONIC = 0; +pub const CLOCK_REALTIME = -1; +pub const CLOCK_PROCESS_CPUTIME_ID = -2; +pub const CLOCK_THREAD_CPUTIME_ID = -3; pub const MAP_FAILED = @intToPtr(*c_void, maxInt(usize)); pub const MAP_SHARED = 0x0001; @@ -310,58 +300,59 @@ pub const MAP_NOCORE = 0x00020000; pub const MAP_PREFAULT_READ = 0x00040000; pub const MAP_32BIT = 0x00080000; -pub const WNOHANG = 1; -pub const WUNTRACED = 2; -pub const WSTOPPED = WUNTRACED; -pub const WCONTINUED = 4; -pub const WNOWAIT = 8; -pub const WEXITED = 16; -pub const WTRAPPED = 32; +pub const WNOHANG = 0x1; +pub const WUNTRACED = 0x2; +pub const WSTOPPED = 0x10; +pub const WCONTINUED = 0x4; +pub const WNOWAIT = 0x20; +pub const WEXITED = 0x08; -pub const SA_ONSTACK = 0x0001; -pub const SA_RESTART = 0x0002; -pub const SA_RESETHAND = 0x0004; -pub const SA_NOCLDSTOP = 0x0008; -pub const SA_NODEFER = 0x0010; -pub const SA_NOCLDWAIT = 0x0020; -pub const SA_SIGINFO = 0x0040; +pub const SA_ONSTACK = 0x20; +pub const SA_RESTART = 0x10; +pub const SA_RESETHAND = 0x04; +pub const SA_NOCLDSTOP = 0x01; +pub const SA_NODEFER = 0x08; +pub const SA_NOCLDWAIT = 0x02; +pub const SA_SIGINFO = 0x40; +pub const SA_NOMASK = SA_NODEFER; +pub const SA_STACK = SA_ONSTACK; +pub const SA_ONESHOT = SA_RESETHAND; pub const SIGHUP = 1; pub const SIGINT = 2; pub const SIGQUIT = 3; pub const SIGILL = 4; -pub const SIGTRAP = 5; +pub const SIGCHLD = 5; pub const SIGABRT = 6; pub const SIGIOT = SIGABRT; -pub const SIGEMT = 7; +pub const SIGPIPE = 7; pub const SIGFPE = 8; pub const SIGKILL = 9; -pub const SIGBUS = 10; +pub const SIGSTOP = 10; pub const SIGSEGV = 11; -pub const SIGSYS = 12; -pub const SIGPIPE = 13; +pub const SIGCONT = 12; +pub const SIGTSTP = 13; pub const SIGALRM = 14; pub const SIGTERM = 15; -pub const SIGURG = 16; -pub const SIGSTOP = 17; -pub const SIGTSTP = 18; -pub const SIGCONT = 19; -pub const SIGCHLD = 20; -pub const SIGTTIN = 21; -pub const SIGTTOU = 22; -pub const SIGIO = 23; -pub const SIGXCPU = 24; -pub const SIGXFSZ = 25; -pub const SIGVTALRM = 26; -pub const SIGPROF = 27; -pub const SIGWINCH = 28; -pub const SIGINFO = 29; -pub const SIGUSR1 = 30; -pub const SIGUSR2 = 31; -pub const SIGTHR = 32; -pub const SIGLWP = SIGTHR; -pub const SIGLIBRT = 33; +pub const SIGTTIN = 16; +pub const SIGTTOU = 17; +pub const SIGUSR1 = 18; +pub const SIGUSR2 = 19; +pub const SIGWINCH = 20; +pub const SIGKILLTHR = 21; +pub const SIGTRAP = 22; +pub const SIGPOLL = 23; +pub const SIGPROF = 24; +pub const SIGSYS = 25; +pub const SIGURG = 26; +pub const SIGVTALRM = 27; +pub const SIGXCPU = 28; +pub const SIGXFSZ = 29; +pub const SIGBUS = 30; +pub const SIGRESERVED1 = 31; +pub const SIGRESERVED2 = 32; +// TODO: check pub const SIGRTMIN = 65; pub const SIGRTMAX = 126; @@ -645,135 +636,51 @@ pub const EVFILT_SENDFILE = -12; pub const EVFILT_EMPTY = -13; -/// On input, NOTE_TRIGGER causes the event to be triggered for output. -pub const NOTE_TRIGGER = 0x01000000; +pub const TCGETA = 0x8000; +pub const TCSETA = 0x8001; +pub const TCSETAW = 0x8004; +pub const TCSETAF = 0x8003; +pub const TCSBRK = 08005; +pub const TCXONC = 0x8007; +pub const TCFLSH = 0x8006; -/// ignore input fflags -pub const NOTE_FFNOP = 0x00000000; - -/// and fflags -pub const NOTE_FFAND = 0x40000000; - -/// or fflags -pub const NOTE_FFOR = 0x80000000; - -/// copy fflags -pub const NOTE_FFCOPY = 0xc0000000; - -/// mask for operations -pub const NOTE_FFCTRLMASK = 0xc0000000; -pub const NOTE_FFLAGSMASK = 0x00ffffff; - -/// low water mark -pub const NOTE_LOWAT = 0x00000001; - -/// behave like poll() -pub const NOTE_FILE_POLL = 0x00000002; - -/// vnode was removed -pub const NOTE_DELETE = 0x00000001; - -/// data contents changed -pub const NOTE_WRITE = 0x00000002; - -/// size increased -pub const NOTE_EXTEND = 0x00000004; - -/// attributes changed -pub const NOTE_ATTRIB = 0x00000008; - -/// link count changed -pub const NOTE_LINK = 0x00000010; - -/// vnode was renamed -pub const NOTE_RENAME = 0x00000020; - -/// vnode access was revoked -pub const NOTE_REVOKE = 0x00000040; - -/// vnode was opened -pub const NOTE_OPEN = 0x00000080; - -/// file closed, fd did not allow write -pub const NOTE_CLOSE = 0x00000100; - -/// file closed, fd did allow write -pub const NOTE_CLOSE_WRITE = 0x00000200; - -/// file was read -pub const NOTE_READ = 0x00000400; - -/// process exited -pub const NOTE_EXIT = 0x80000000; - -/// process forked -pub const NOTE_FORK = 0x40000000; - -/// process exec'd -pub const NOTE_EXEC = 0x20000000; - -/// mask for signal & exit status -pub const NOTE_PDATAMASK = 0x000fffff; -pub const NOTE_PCTRLMASK = (~NOTE_PDATAMASK); - -/// data is seconds -pub const NOTE_SECONDS = 0x00000001; - -/// data is milliseconds -pub const NOTE_MSECONDS = 0x00000002; - -/// data is microseconds -pub const NOTE_USECONDS = 0x00000004; - -/// data is nanoseconds -pub const NOTE_NSECONDS = 0x00000008; - -/// timeout is absolute -pub const NOTE_ABSTIME = 0x00000010; - -pub const TIOCEXCL = 0x2000740d; -pub const TIOCNXCL = 0x2000740e; -pub const TIOCSCTTY = 0x20007461; -pub const TIOCGPGRP = 0x40047477; -pub const TIOCSPGRP = 0x80047476; -pub const TIOCOUTQ = 0x40047473; -pub const TIOCSTI = 0x80017472; -pub const TIOCGWINSZ = 0x40087468; -pub const TIOCSWINSZ = 0x80087467; -pub const TIOCMGET = 0x4004746a; -pub const TIOCMBIS = 0x8004746c; -pub const TIOCMBIC = 0x8004746b; -pub const TIOCMSET = 0x8004746d; -pub const FIONREAD = 0x4004667f; -pub const TIOCCONS = 0x80047462; -pub const TIOCPKT = 0x80047470; -pub const FIONBIO = 0x8004667e; -pub const TIOCNOTTY = 0x20007471; -pub const TIOCSETD = 0x8004741b; -pub const TIOCGETD = 0x4004741a; -pub const TIOCSBRK = 0x2000747b; -pub const TIOCCBRK = 0x2000747a; -pub const TIOCGSID = 0x40047463; -pub const TIOCGPTN = 0x4004740f; -pub const TIOCSIG = 0x2004745f; +pub const TIOCSCTTY = 0x8017; +pub const TIOCGPGRP = 0x8015; +pub const TIOCSPGRP = 0x8016; +pub const TIOCGWINSZ = 0x8012; +pub const TIOCSWINSZ = 0x8013; +pub const TIOCMGET = 0x8018; +pub const TIOCMBIS = 0x8022; +pub const TIOCMBIC = 0x8023; +pub const TIOCMSET = 0x8019; +pub const FIONREAD = 0xbe000001; +pub const FIONBIO = 0xbe000000; +pub const TIOCSBRK = 0x8020; +pub const TIOCCBRK = 0x8021; +pub const TIOCGSID = 0x8024; pub fn WEXITSTATUS(s: u32) u32 { - return (s & 0xff00) >> 8; + return (s & 0xff); } + pub fn WTERMSIG(s: u32) u32 { - return s & 0x7f; + return (s >> 8) & 0xff; } + pub fn WSTOPSIG(s: u32) u32 { return WEXITSTATUS(s); } + pub fn WIFEXITED(s: u32) bool { return WTERMSIG(s) == 0; } + pub fn WIFSTOPPED(s: u32) bool { - return @intCast(u16, (((s & 0xffff) *% 0x10001) >> 8)) > 0x7f00; + return ((s >> 16) & 0xff) != 0; } + pub fn WIFSIGNALED(s: u32) bool { - return (s & 0xffff) -% 1 < 0xff; + return ((s >> 8) & 0xff) != 0; } pub const winsize = extern struct { @@ -823,49 +730,47 @@ pub const sigset_t = extern struct { __bits: [_SIG_WORDS]u32, }; -pub const EPERM = 1; // Operation not permitted -pub const ENOENT = 2; // No such file or directory -pub const ESRCH = 3; // No such process -pub const EINTR = 4; // Interrupted system call -pub const EIO = 5; // Input/output error -pub const ENXIO = 6; // Device not configured -pub const E2BIG = 7; // Argument list too long -pub const ENOEXEC = 8; // Exec format error -pub const EBADF = 9; // Bad file descriptor -pub const ECHILD = 10; // No child processes -pub const EDEADLK = 11; // Resource deadlock avoided -// 11 was EAGAIN -pub const ENOMEM = 12; // Cannot allocate memory -pub const EACCES = 13; // Permission denied -pub const EFAULT = 14; // Bad address -pub const ENOTBLK = 15; // Block device required -pub const EBUSY = 16; // Device busy -pub const EEXIST = 17; // File exists -pub const EXDEV = 18; // Cross-device link -pub const ENODEV = 19; // Operation not supported by device -pub const ENOTDIR = 20; // Not a directory -pub const EISDIR = 21; // Is a directory -pub const EINVAL = 22; // Invalid argument -pub const ENFILE = 23; // Too many open files in system -pub const EMFILE = 24; // Too many open files -pub const ENOTTY = 25; // Inappropriate ioctl for device -pub const ETXTBSY = 26; // Text file busy -pub const EFBIG = 27; // File too large -pub const ENOSPC = 28; // No space left on device -pub const ESPIPE = 29; // Illegal seek -pub const EROFS = 30; // Read-only filesystem -pub const EMLINK = 31; // Too many links -pub const EPIPE = 32; // Broken pipe +pub const EPERM = -0x7ffffff1; // Operation not permitted +pub const ENOENT = -0x7fff9ffd; // No such file or directory +pub const ESRCH = -0x7fff8ff3; // No such process +pub const EINTR = -0x7ffffff6; // Interrupted system call +pub const EIO = -0x7fffffff; // Input/output error +pub const ENXIO = -0x7fff8ff5; // Device not configured +pub const E2BIG = -0x7fff8fff; // Argument list too long +pub const ENOEXEC = -0x7fffecfe; // Exec format error +pub const ECHILD = -0x7fff8ffe; // No child processes +pub const EDEADLK = -0x7fff8ffd; // Resource deadlock avoided +pub const ENOMEM = -0x80000000; // Cannot allocate memory +pub const EACCES = -0x7ffffffe; // Permission denied +pub const EFAULT = -0x7fffecff; // Bad address +pub const EBUSY = -0x7ffffff2; // Device busy +pub const EEXIST = -0x7fff9ffe; // File exists +pub const EXDEV = -0x7fff9ff5; // Cross-device link +pub const ENODEV = -0x7fff8ff9; // Operation not supported by device +pub const ENOTDIR = -0x7fff9ffb; // Not a directory +pub const EISDIR = -0x7fff9ff7; // Is a directory +pub const EINVAL = -0x7ffffffb; // Invalid argument +pub const ENFILE = -0x7fff8ffa; // Too many open files in system +pub const EMFILE = -0x7fff9ff6; // Too many open files +pub const ENOTTY = -0x7fff8ff6; // Inappropriate ioctl for device +pub const ETXTBSY = -0x7fff8fc5; // Text file busy +pub const EFBIG = -0x7fff8ffc; // File too large +pub const ENOSPC = -0x7fff9ff9; // No space left on device +pub const ESPIPE = -0x7fff8ff4; // Illegal seek +pub const EROFS = -0x7fff9ff8; // Read-only filesystem +pub const EMLINK = -0x7fff8ffb; // Too many links +pub const EPIPE = -0x7fff9ff3; // Broken pipe +pub const EBADF = -0x7fffa000; // Bad file descriptor // math software pub const EDOM = 33; // Numerical argument out of domain pub const ERANGE = 34; // Result too large // non-blocking and interrupt i/o -pub const EAGAIN = 35; // Resource temporarily unavailable -pub const EWOULDBLOCK = EAGAIN; // Operation would block -pub const EINPROGRESS = 36; // Operation now in progress -pub const EALREADY = 37; // Operation already in progress +pub const EAGAIN = -0x7ffffff5; +pub const EWOULDBLOCK = -0x7ffffff5; +pub const EINPROGRESS = -0x7fff8fdc; +pub const EALREADY = -0x7fff8fdb; // ipc/network software -- argument errors pub const ENOTSOCK = 38; // Socket operation on non-socket @@ -1447,3 +1352,20 @@ pub const directory_which = extern 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, +}; From 7c3896e6cd20a51e63a018fd2e05ea3c552b7401 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sat, 24 Apr 2021 18:42:23 +0200 Subject: [PATCH 08/33] translate-c: Prevent mistranslation of fp literals When trying to retrieve 80bit fp values from clang using getValueAsApproximateDouble we'd eventually hit the ceiling value and return infinity, an invalid value for a fp literal. Add some logic to prevent this error and warn the user. Closes #8602 --- src/clang.zig | 16 ++++++++++++++++ src/translate_c.zig | 17 +++++++++++++++-- src/zig_clang.cpp | 5 +++++ src/zig_clang.h | 11 +++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/clang.zig b/src/clang.zig index d53accbaf7..e00271968b 100644 --- a/src/clang.zig +++ b/src/clang.zig @@ -104,6 +104,16 @@ pub const APFloat = opaque { extern fn ZigClangAPFloat_toString(*const APFloat, precision: c_uint, maxPadding: c_uint, truncateZero: bool) [*:0]const u8; }; +pub const APFloatBaseSemantics = extern enum { + IEEEhalf, + BFloat, + IEEEsingle, + IEEEdouble, + x86DoubleExtended, + IEEEquad, + PPCDoubleDouble, +}; + pub const APInt = opaque { pub const getLimitedValue = ZigClangAPInt_getLimitedValue; extern fn ZigClangAPInt_getLimitedValue(*const APInt, limit: u64) u64; @@ -455,6 +465,12 @@ pub const FileID = opaque {}; pub const FloatingLiteral = opaque { pub const getValueAsApproximateDouble = ZigClangFloatingLiteral_getValueAsApproximateDouble; extern fn ZigClangFloatingLiteral_getValueAsApproximateDouble(*const FloatingLiteral) f64; + + pub const getBeginLoc = ZigClangIntegerLiteral_getBeginLoc; + extern fn ZigClangIntegerLiteral_getBeginLoc(*const FloatingLiteral) SourceLocation; + + pub const getRawSemantics = ZigClangFloatingLiteral_getRawSemantics; + extern fn ZigClangFloatingLiteral_getRawSemantics(*const FloatingLiteral) APFloatBaseSemantics; }; pub const ForStmt = opaque { diff --git a/src/translate_c.zig b/src/translate_c.zig index ac5c52ee0d..60b3961e6e 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -3547,9 +3547,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)) diff --git a/src/zig_clang.cpp b/src/zig_clang.cpp index 32bb9b4487..efdef1e26b 100644 --- a/src/zig_clang.cpp +++ b/src/zig_clang.cpp @@ -2528,6 +2528,11 @@ double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatin return casted->getValueAsApproximateDouble(); } +ZigClangAPFloatBase_Semantics ZigClangFloatingLiteral_getRawSemantics(const ZigClangFloatingLiteral *self) { + auto casted = reinterpret_cast(self); + return static_cast(casted->getRawSemantics()); +} + enum ZigClangStringLiteral_StringKind ZigClangStringLiteral_getKind(const struct ZigClangStringLiteral *self) { auto casted = reinterpret_cast(self); return (ZigClangStringLiteral_StringKind)casted->getKind(); diff --git a/src/zig_clang.h b/src/zig_clang.h index eac3592692..4ea8cc731b 100644 --- a/src/zig_clang.h +++ b/src/zig_clang.h @@ -881,6 +881,16 @@ enum ZigClangAPFloat_roundingMode { ZigClangAPFloat_roundingMode_Invalid = -1, }; +enum ZigClangAPFloatBase_Semantics { + ZigClangAPFloatBase_Semantics_IEEEhalf, + ZigClangAPFloatBase_Semantics_BFloat, + ZigClangAPFloatBase_Semantics_IEEEsingle, + ZigClangAPFloatBase_Semantics_IEEEdouble, + ZigClangAPFloatBase_Semantics_x87DoubleExtended, + ZigClangAPFloatBase_Semantics_IEEEquad, + ZigClangAPFloatBase_Semantics_PPCDoubleDouble, +}; + enum ZigClangStringLiteral_StringKind { ZigClangStringLiteral_StringKind_Ascii, ZigClangStringLiteral_StringKind_Wide, @@ -1142,6 +1152,7 @@ ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangDeclStmt_getBeginLoc(const st ZIG_EXTERN_C unsigned ZigClangAPFloat_convertToHexString(const struct ZigClangAPFloat *self, char *DST, unsigned HexDigits, bool UpperCase, enum ZigClangAPFloat_roundingMode RM); ZIG_EXTERN_C double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatingLiteral *self); +ZIG_EXTERN_C ZigClangAPFloatBase_Semantics ZigClangFloatingLiteral_getRawSemantics(const ZigClangFloatingLiteral *self); ZIG_EXTERN_C enum ZigClangStringLiteral_StringKind ZigClangStringLiteral_getKind(const struct ZigClangStringLiteral *self); ZIG_EXTERN_C uint32_t ZigClangStringLiteral_getCodeUnit(const struct ZigClangStringLiteral *self, size_t i); From cf98dfbe22548728f247c314e3119c5a5dfceac5 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sat, 24 Apr 2021 20:35:04 +0200 Subject: [PATCH 09/33] Remove translate-c test using long double literal Removed until the rendering logic is adapted to deal with 80bit (and bigger) floats, those are used sparingly in the wild so it's not much of a loss. --- test/translate_c.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/translate_c.zig b/test/translate_c.zig index 5fa4c32041..846eec0d62 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -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))); From 37b05742ff1544bccf7c8ae9b12c6707a5a54df2 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Sat, 24 Apr 2021 16:39:26 -0400 Subject: [PATCH 10/33] ci: drone disable debug test suite for aarch64 Temporary workaround for #8597 until upstream fix lands. - add `zig build -Dskip-debug` option - build a release-fast build.exe - disable test suite build modes { debug, release-small, release-safe } - enable passing test-* components - use parallel pipeline steps for tests --- build.zig | 7 ++-- ci/drone/drone.yml | 35 ++++++++++++++++-- ci/drone/linux_script | 65 ---------------------------------- ci/drone/linux_script_base | 22 ++++++++++++ ci/drone/linux_script_build | 18 ++++++++++ ci/drone/linux_script_finalize | 46 ++++++++++++++++++++++++ ci/drone/linux_script_test | 44 +++++++++++++++++++++++ 7 files changed, 168 insertions(+), 69 deletions(-) delete mode 100755 ci/drone/linux_script create mode 100755 ci/drone/linux_script_base create mode 100755 ci/drone/linux_script_build create mode 100755 ci/drone/linux_script_finalize create mode 100755 ci/drone/linux_script_test diff --git a/build.zig b/build.zig index 031c7f8e87..d15766c08e 100644 --- a/build.zig +++ b/build.zig @@ -44,6 +44,7 @@ pub fn build(b: *Builder) !void { const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"}); + const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false; const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false; const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release; const skip_release_fast = b.option(bool, "skip-release-fast", "Main test suite skips release-fast builds") orelse skip_release; @@ -237,8 +238,10 @@ pub fn build(b: *Builder) !void { var chosen_modes: [4]builtin.Mode = undefined; var chosen_mode_index: usize = 0; - chosen_modes[chosen_mode_index] = builtin.Mode.Debug; - chosen_mode_index += 1; + if (!skip_debug) { + chosen_modes[chosen_mode_index] = builtin.Mode.Debug; + chosen_mode_index += 1; + } if (!skip_release_safe) { chosen_modes[chosen_mode_index] = builtin.Mode.ReleaseSafe; chosen_mode_index += 1; diff --git a/ci/drone/drone.yml b/ci/drone/drone.yml index 5af5fe0350..f0c4ab66d4 100644 --- a/ci/drone/drone.yml +++ b/ci/drone/drone.yml @@ -6,7 +6,38 @@ platform: arch: arm64 steps: -- name: build-and-test +- name: build + image: ziglang/static-base:llvm12-aarch64-1 + commands: + - ./ci/drone/linux_script_build + +- name: test-1 + depends_on: + - build + image: ziglang/static-base:llvm12-aarch64-1 + commands: + - ./ci/drone/linux_script_test 1 + +- name: test-2 + depends_on: + - build + image: ziglang/static-base:llvm12-aarch64-1 + commands: + - ./ci/drone/linux_script_test 2 + +- name: test-3 + depends_on: + - build + image: ziglang/static-base:llvm12-aarch64-1 + commands: + - ./ci/drone/linux_script_test 3 + +- name: finalize + depends_on: + - build + - test-1 + - test-2 + - test-3 image: ziglang/static-base:llvm12-aarch64-1 environment: SRHT_OAUTH_TOKEN: @@ -16,4 +47,4 @@ steps: AWS_SECRET_ACCESS_KEY: from_secret: AWS_SECRET_ACCESS_KEY commands: - - ./ci/drone/linux_script + - ./ci/drone/linux_script_finalize diff --git a/ci/drone/linux_script b/ci/drone/linux_script deleted file mode 100755 index 0994604f80..0000000000 --- a/ci/drone/linux_script +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/sh - -set -x -set -e - -TRIPLEARCH="$(uname -m)" -BUILDDIR="$(pwd)" -DISTDIR="$(pwd)/dist" - -apk update -apk add py3-pip xz perl-utils jq curl samurai -pip3 install s3cmd - -# Make the `zig version` number consistent. -# This will affect the cmake command below. -git config core.abbrev 9 -git fetch --unshallow || true -git fetch --tags - -mkdir build -cd build -cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_INSTALL_PREFIX=$DISTDIR" -DZIG_STATIC=ON -DCMAKE_PREFIX_PATH=/deps/local -GNinja - -samu install -# run-translated-c tests are skipped due to: https://github.com/ziglang/zig/issues/8537 -./zig build test \ - -Dskip-release \ - -Dskip-non-native \ - -Dskip-compile-errors \ - -Dskip-run-translated-c - -if [ -z "$DRONE_PULL_REQUEST" ]; then - mv ../LICENSE "$DISTDIR/" - mv ../zig-cache/langref.html "$DISTDIR/" - mv "$DISTDIR/bin/zig" "$DISTDIR/" - rmdir "$DISTDIR/bin" - - GITBRANCH="$DRONE_BRANCH" - VERSION="$("$DISTDIR/zig" version)" - DIRNAME="zig-linux-$TRIPLEARCH-$VERSION" - TARBALL="$DIRNAME.tar.xz" - mv "$DISTDIR" "$DIRNAME" - tar cfJ "$TARBALL" "$DIRNAME" - - s3cmd put -P --add-header="cache-control: public, max-age=31536000, immutable" "$TARBALL" s3://ziglang.org/builds/ - - SHASUM=$(shasum -a 256 $TARBALL | cut '-d ' -f1) - BYTESIZE=$(wc -c < $TARBALL) - - JSONFILE="$TRIPLEARCH-linux-$GITBRANCH.json" - touch $JSONFILE - echo "{\"tarball\": \"$TARBALL\"," >>$JSONFILE - echo "\"shasum\": \"$SHASUM\"," >>$JSONFILE - echo "\"size\": \"$BYTESIZE\"}" >>$JSONFILE - - s3cmd put -P --add-header="Cache-Control: max-age=0, must-revalidate" "$JSONFILE" "s3://ziglang.org/builds/$JSONFILE" - s3cmd put -P "$JSONFILE" "s3://ziglang.org/builds/$TRIPLEARCH-linux-$VERSION.json" - if [ "$GITBRANCH" = "master" ]; then - # avoid leaking oauth token - set +x - - cd "$BUILDDIR" - ./ci/srht/on_master_success "$VERSION" "$SRHT_OAUTH_TOKEN" - fi -fi diff --git a/ci/drone/linux_script_base b/ci/drone/linux_script_base new file mode 100755 index 0000000000..2788eb2df6 --- /dev/null +++ b/ci/drone/linux_script_base @@ -0,0 +1,22 @@ +#!/bin/sh + +# https://docs.drone.io/pipeline/docker/syntax/workspace/ +# +# Drone automatically creates a temporary volume, known as your workspace, +# where it clones your repository. The workspace is the current working +# directory for each step in your pipeline. +# +# Because the workspace is a volume, filesystem changes are persisted between +# pipeline steps. In other words, individual steps can communicate and share +# state using the filesystem. +# +# Workspace volumes are ephemeral. They are created when the pipeline starts +# and destroyed after the pipeline completes. + +set -x +set -e + +TRIPLEARCH="$(uname -m)" +DISTDIR="$DRONE_WORKSPACE/dist" + +export ZIG_GLOBAL_CACHE_DIR="$DRONE_WORKSPACE/zig-cache" diff --git a/ci/drone/linux_script_build b/ci/drone/linux_script_build new file mode 100755 index 0000000000..3aedafbb89 --- /dev/null +++ b/ci/drone/linux_script_build @@ -0,0 +1,18 @@ +#!/bin/sh + +. ./ci/drone/linux_script_base + +apk update +apk add samurai + +# Make the `zig version` number consistent. +# This will affect the cmake command below. +git config core.abbrev 9 +git fetch --unshallow || true +git fetch --tags + +mkdir build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_INSTALL_PREFIX=$DISTDIR" -DZIG_STATIC=ON -DCMAKE_PREFIX_PATH=/deps/local -GNinja + +samu install diff --git a/ci/drone/linux_script_finalize b/ci/drone/linux_script_finalize new file mode 100755 index 0000000000..da754a6b54 --- /dev/null +++ b/ci/drone/linux_script_finalize @@ -0,0 +1,46 @@ +#!/bin/sh + +. ./ci/drone/linux_script_base + +if [ -n "$DRONE_PULL_REQUEST" ]; then + exit 0 +fi + +apk update +apk add py3-pip xz perl-utils jq curl samurai +pip3 install s3cmd + +cd build + +mv ../LICENSE "$DISTDIR/" +# docs are disabled due to: https://github.com/ziglang/zig/issues/8597 +#mv ../zig-cache/langref.html "$DISTDIR/" +mv "$DISTDIR/bin/zig" "$DISTDIR/" +rmdir "$DISTDIR/bin" + +GITBRANCH="$DRONE_BRANCH" +VERSION="$("$DISTDIR/zig" version)" +DIRNAME="zig-linux-$TRIPLEARCH-$VERSION" +TARBALL="$DIRNAME.tar.xz" +mv "$DISTDIR" "$DIRNAME" +tar cfJ "$TARBALL" "$DIRNAME" + +s3cmd put -P --add-header="cache-control: public, max-age=31536000, immutable" "$TARBALL" s3://ziglang.org/builds/ + +SHASUM=$(shasum -a 256 $TARBALL | cut '-d ' -f1) +BYTESIZE=$(wc -c < $TARBALL) + +JSONFILE="tarball.json" +touch $JSONFILE +echo "{\"tarball\": \"$TARBALL\"," >>$JSONFILE +echo "\"shasum\": \"$SHASUM\"," >>$JSONFILE +echo "\"size\": \"$BYTESIZE\"}" >>$JSONFILE + +s3cmd put -P "$JSONFILE" "s3://ziglang.org/builds/$TRIPLEARCH-linux-$VERSION.json" +if [ "$GITBRANCH" = "master" ]; then + # avoid leaking oauth token + set +x + + cd "$DRONE_WORKSPACE" + ./ci/srht/on_master_success "$VERSION" "$SRHT_OAUTH_TOKEN" +fi diff --git a/ci/drone/linux_script_test b/ci/drone/linux_script_test new file mode 100755 index 0000000000..d59a2008ba --- /dev/null +++ b/ci/drone/linux_script_test @@ -0,0 +1,44 @@ +#!/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" + ;; + '') + 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 From b435d7fa6050a8867b7228eeaa71a399a9d8d7c1 Mon Sep 17 00:00:00 2001 From: lithdew Date: Wed, 21 Apr 2021 16:33:53 +0900 Subject: [PATCH 11/33] x, x/os/Socket: initial work on new Socket abstraction Kick-start initial work on new cross-platform abstraction for sockets. Adds a test for read timeouts and a test for creating a non-blocking socket pair on Linux. The new Socket abstraction is barebones and is made to support both blocking and non-blocking abstractions, alongside different socket protocols and domains. Support for platform-dependant socket options that handles unsupported platforms gracefully via. comptime checks is provided for the new Socket abstraction. This also marks the first out of many commits for introducing breaking changes to the standard library in a separate `x` folder, which was pre-approved by @andrewrk. The intent for the new `x` package is to introduce new async, event loop, networking, and operating system abstractions that would require breaking the standard library significantly. By having the `x` package, code in the standard library and compiler may then slowly be refactored to use the `x` package. Once modules in the `x` package are stabilized, they can be moved out of the `x` package, and a global 'grep' can be done to update import paths that resolve to the stabilized module in the `x` package. --- lib/std/std.zig | 1 + lib/std/x.zig | 1 + lib/std/x/os/Socket.zig | 276 ++++++++++++++++++++++++++++++++++++++++ lib/std/x/os/os.zig | 9 ++ 4 files changed, 287 insertions(+) create mode 100644 lib/std/x.zig create mode 100644 lib/std/x/os/Socket.zig create mode 100644 lib/std/x/os/os.zig diff --git a/lib/std/std.zig b/lib/std/std.zig index 5f10def72e..940d1ab42e 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -88,6 +88,7 @@ pub const time = @import("time.zig"); pub const unicode = @import("unicode.zig"); pub const valgrind = @import("valgrind.zig"); pub const wasm = @import("wasm.zig"); +pub const x = @import("x.zig"); pub const zig = @import("zig.zig"); pub const start = @import("start.zig"); diff --git a/lib/std/x.zig b/lib/std/x.zig new file mode 100644 index 0000000000..4053bcd063 --- /dev/null +++ b/lib/std/x.zig @@ -0,0 +1 @@ +pub const os = @import("x/os/os.zig"); \ No newline at end of file diff --git a/lib/std/x/os/Socket.zig b/lib/std/x/os/Socket.zig new file mode 100644 index 0000000000..4242a89d7f --- /dev/null +++ b/lib/std/x/os/Socket.zig @@ -0,0 +1,276 @@ +const std = @import("../../std.zig"); + +const os = std.os; +const mem = std.mem; +const net = std.net; +const time = std.time; +const builtin = std.builtin; +const testing = std.testing; + +const Socket = @This(); + +/// A socket-address pair. +pub const Connection = struct { + socket: Socket, + address: net.Address, +}; + +/// The underlying handle of a socket. +fd: os.socket_t, + +/// Open a new socket. +pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket { + return Socket{ .fd = try os.socket(domain, socket_type, protocol) }; +} + +/// Closes the socket. +pub fn deinit(self: Socket) void { + os.closeSocket(self.fd); +} + +/// Shutdown either the read side, or write side, or the entirety of a socket. +pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { + return os.shutdown(self.fd, how); +} + +/// Binds the socket to an address. +pub fn bind(self: Socket, address: net.Address) !void { + return os.bind(self.fd, &address.any, address.getOsSockLen()); +} + +/// Start listening for incoming connections on the socket. +pub fn listen(self: Socket, max_backlog_size: u31) !void { + return os.listen(self.fd, max_backlog_size); +} + +/// Have the socket attempt to the connect to an address. +pub fn connect(self: Socket, address: net.Address) !void { + return os.connect(self.fd, &address.any, address.getOsSockLen()); +} + +/// Accept a pending incoming connection queued to the kernel backlog +/// of the socket. +pub fn accept(self: Socket, flags: u32) !Socket.Connection { + var address: os.sockaddr = undefined; + var address_len: u32 = @sizeOf(os.sockaddr); + + const fd = try os.accept(self.fd, &address, &address_len, flags); + + return Connection{ + .socket = Socket{ .fd = fd }, + .address = net.Address.initPosix(@alignCast(4, &address)), + }; +} + +/// Read data from the socket into the buffer provided. It returns the +/// number of bytes read into the buffer provided. +pub fn read(self: Socket, buf: []u8) !usize { + return os.read(self.fd, buf); +} + +/// Read data from the socket into the buffer provided with a set of flags +/// specified. It returns the number of bytes read into the buffer provided. +pub fn recv(self: Socket, buf: []u8, flags: u32) !usize { + return os.recv(self.fd, buf, flags); +} + +/// Write a buffer of data provided to the socket. It returns the number +/// of bytes that are written to the socket. +pub fn write(self: Socket, buf: []const u8) !usize { + return os.write(self.fd, buf); +} + +/// Writes multiple I/O vectors to the socket. It returns the number +/// of bytes that are written to the socket. +pub fn writev(self: Socket, buffers: []const os.iovec_const) !usize { + return os.writev(self.fd, buffers); +} + +/// Write a buffer of data provided to the socket with a set of flags specified. +/// It returns the number of bytes that are written to the socket. +pub fn send(self: Socket, buf: []const u8, flags: u32) !usize { + return os.send(self.fd, buf, flags); +} + +/// Writes multiple I/O vectors with a prepended message header to the socket +/// with a set of flags specified. It returns the number of bytes that are +/// written to the socket. +pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize { + return os.sendmsg(self.fd, msg, flags); +} + +/// Query the address that the socket is locally bounded to. +pub fn getLocalAddress(self: Socket) !net.Address { + var address: os.sockaddr = undefined; + var address_len: u32 = @sizeOf(os.sockaddr); + try os.getsockname(self.fd, &address, &address_len); + return net.Address.initPosix(@alignCast(4, &address)); +} + +/// Query and return the latest cached error on the socket. +pub fn getError(self: Socket) !void { + return os.getsockoptError(self.fd); +} + +/// Query the read buffer size of the socket. +pub fn getReadBufferSize(self: Socket) !u32 { + var value: u32 = undefined; + var value_len: u32 = @sizeOf(u32); + + const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&value), &value_len); + return switch (os.errno(rc)) { + 0 => value, + os.EBADF => error.BadFileDescriptor, + os.EFAULT => error.InvalidAddressSpace, + os.EINVAL => error.InvalidSocketOption, + os.ENOPROTOOPT => error.UnknownSocketOption, + os.ENOTSOCK => error.NotASocket, + else => |err| os.unexpectedErrno(err), + }; +} + +/// Query the write buffer size of the socket. +pub fn getWriteBufferSize(self: Socket) !u32 { + var value: u32 = undefined; + var value_len: u32 = @sizeOf(u32); + + const rc = os.system.getsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&value), &value_len); + return switch (os.errno(rc)) { + 0 => value, + os.EBADF => error.BadFileDescriptor, + os.EFAULT => error.InvalidAddressSpace, + os.EINVAL => error.InvalidSocketOption, + os.ENOPROTOOPT => error.UnknownSocketOption, + os.ENOTSOCK => error.NotASocket, + else => |err| os.unexpectedErrno(err), + }; +} + +/// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if +/// the host does not support sockets listening the same address. +pub fn setReuseAddress(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "SO_REUSEADDR")) { + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_REUSEADDR, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if +/// the host does not supports sockets listening on the same port. +pub fn setReusePort(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "SO_REUSEPORT")) { + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_REUSEPORT, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not support +/// sockets disabling Nagle's algorithm. +pub fn setNoDelay(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "TCP_NODELAY")) { + return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_NODELAY, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not +/// support TCP Fast Open. +pub fn setFastOpen(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "TCP_FASTOPEN")) { + return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns +/// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK. +pub fn setQuickACK(self: Socket, enabled: bool) !void { + if (comptime @hasDecl(os, "TCP_QUICKACK")) { + return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled)))); + } + return error.UnsupportedSocketOption; +} + +/// Set the write buffer size of the socket. +pub fn setWriteBufferSize(self: Socket, size: u32) !void { + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size)); +} + +/// Set the read buffer size of the socket. +pub fn setReadBufferSize(self: Socket, size: u32) !void { + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVBUF, mem.asBytes(&size)); +} + +/// Set a timeout on the socket that is to occur if no messages are successfully written +/// to its bound destination after a specified number of milliseconds. A subsequent write +/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. +pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void { + const timeout = os.timeval{ + .tv_sec = @intCast(isize, milliseconds / time.ms_per_s), + .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms), + }; + + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout)); +} + +/// Set a timeout on the socket that is to occur if no messages are successfully read +/// from its bound destination after a specified number of milliseconds. A subsequent +/// read from the socket will thereafter return `error.WouldBlock` should the timeout be +/// exceeded. +pub fn setReadTimeout(self: Socket, milliseconds: usize) !void { + const timeout = os.timeval{ + .tv_sec = @intCast(isize, milliseconds / time.ms_per_s), + .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms), + }; + + return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout)); +} + +test { + testing.refAllDecls(@This()); +} + +test "socket/linux: set read timeout of 1 millisecond on blocking socket" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + + const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP); + defer a.deinit(); + + try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0)); + try a.listen(128); + + const binded_address = try a.getLocalAddress(); + + const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP); + defer b.deinit(); + + try b.connect(binded_address); + try b.setReadTimeout(1); + + const ab = try a.accept(os.SOCK_CLOEXEC); + defer ab.socket.deinit(); + + var buf: [1]u8 = undefined; + testing.expectError(error.WouldBlock, b.read(&buf)); +} + +test "socket/linux: create non-blocking socket pair" { + if (builtin.os.tag != .linux) return error.SkipZigTest; + + const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP); + defer a.deinit(); + + try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0)); + try a.listen(128); + + const binded_address = try a.getLocalAddress(); + + const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP); + defer b.deinit(); + + testing.expectError(error.WouldBlock, b.connect(binded_address)); + try b.getError(); + + const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + defer ab.socket.deinit(); +} \ No newline at end of file diff --git a/lib/std/x/os/os.zig b/lib/std/x/os/os.zig new file mode 100644 index 0000000000..6f4417c081 --- /dev/null +++ b/lib/std/x/os/os.zig @@ -0,0 +1,9 @@ +const std = @import("../../std.zig"); + +const testing = std.testing; + +pub const Socket = @import("Socket.zig"); + +test { + testing.refAllDecls(@This()); +} \ No newline at end of file From 98706c968677caafeb12c56c884b7cc952c696bc Mon Sep 17 00:00:00 2001 From: lithdew Date: Thu, 22 Apr 2021 14:53:44 +0900 Subject: [PATCH 12/33] x: fmt source code --- lib/std/os/bits/openbsd.zig | 2 +- lib/std/x.zig | 2 +- lib/std/x/os/Socket.zig | 6 +++--- lib/std/x/os/os.zig | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/std/os/bits/openbsd.zig b/lib/std/os/bits/openbsd.zig index 8d1e74ec20..1aa5ce1c9c 100644 --- a/lib/std/os/bits/openbsd.zig +++ b/lib/std/os/bits/openbsd.zig @@ -805,7 +805,7 @@ comptime { if (@sizeOf(usize) == 4) std.debug.assert(@sizeOf(siginfo_t) == 128) else - // Take into account the padding between errno and data fields. + // Take into account the padding between errno and data fields. std.debug.assert(@sizeOf(siginfo_t) == 136); } diff --git a/lib/std/x.zig b/lib/std/x.zig index 4053bcd063..df7debf121 100644 --- a/lib/std/x.zig +++ b/lib/std/x.zig @@ -1 +1 @@ -pub const os = @import("x/os/os.zig"); \ No newline at end of file +pub const os = @import("x/os/os.zig"); diff --git a/lib/std/x/os/Socket.zig b/lib/std/x/os/Socket.zig index 4242a89d7f..3f1971a877 100644 --- a/lib/std/x/os/Socket.zig +++ b/lib/std/x/os/Socket.zig @@ -12,7 +12,7 @@ const Socket = @This(); /// A socket-address pair. pub const Connection = struct { socket: Socket, - address: net.Address, + address: net.Address, }; /// The underlying handle of a socket. @@ -231,7 +231,7 @@ test { } test "socket/linux: set read timeout of 1 millisecond on blocking socket" { - if (builtin.os.tag != .linux) return error.SkipZigTest; + 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(); @@ -273,4 +273,4 @@ test "socket/linux: create non-blocking socket pair" { const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); defer ab.socket.deinit(); -} \ No newline at end of file +} diff --git a/lib/std/x/os/os.zig b/lib/std/x/os/os.zig index 6f4417c081..1d1b8edc7c 100644 --- a/lib/std/x/os/os.zig +++ b/lib/std/x/os/os.zig @@ -6,4 +6,4 @@ pub const Socket = @import("Socket.zig"); test { testing.refAllDecls(@This()); -} \ No newline at end of file +} From 50a8124f45cd0994b52af3fd166dd3bda48f4b99 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 25 Apr 2021 16:40:41 +0200 Subject: [PATCH 13/33] stage1: Change how the Frame alignment is computed The code would previously assume every function would start at addresses being multiples of 16, this is not true beside some specific cases. Moreover LLVM picks different alignment values depending on whether it's trying to generate dense or fast code. Let's use the minimum guaranteed alignment as base value, computed according to how big the opcodes are. The alignment of function pointers is always 1, a safe value that won't cause any error at runtime. Note that this was already the case before this commit, here we're making this choice explicit. Let the 'alignment' field for TypeInfo of fn types reflect the ABI alignment used by the compiler, make this field behave similarly to the 'alignment' one for pointers. --- src/stage1/analyze.cpp | 15 +++++-------- src/stage1/ir.cpp | 4 ++-- src/stage1/target.cpp | 35 ++++++++++++++++++++++++++++-- src/stage1/target.hpp | 1 + test/stage1/behavior/type_info.zig | 2 +- 5 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index 21ad55b8f7..e1e0c496f6 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -4770,10 +4770,10 @@ Error type_is_nonnull_ptr2(CodeGen *g, ZigType *type, bool *result) { } 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; + // 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 +4789,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/ir.cpp b/src/stage1/ir.cpp index c59f63399c..176d50dc72 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -26079,11 +26079,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; diff --git a/src/stage1/target.cpp b/src/stage1/target.cpp index 028f6e5fa8..6aa3cfcbd0 100644 --- a/src/stage1/target.cpp +++ b/src/stage1/target.cpp @@ -1253,6 +1253,37 @@ bool target_is_ppc(const ZigTarget *target) { target->arch == ZigLLVM_ppc64le; } -unsigned target_fn_align(const ZigTarget *target) { - return 16; +// Returns the minimum alignment for every function pointer on the given +// architecture. +unsigned target_fn_ptr_align(const ZigTarget *target) { + // TODO This is a pessimization but is always correct. + return 1; +} + +// Returns the minimum alignment for every function on the given architecture. +unsigned target_fn_align(const ZigTarget *target) { + switch (target->arch) { + case ZigLLVM_riscv32: + case ZigLLVM_riscv64: + // TODO If the C extension is not present the value is 4. + return 2; + case ZigLLVM_ppc: + case ZigLLVM_ppcle: + case ZigLLVM_ppc64: + case ZigLLVM_ppc64le: + case ZigLLVM_aarch64: + case ZigLLVM_aarch64_be: + case ZigLLVM_aarch64_32: + case ZigLLVM_sparc: + case ZigLLVM_sparcel: + case ZigLLVM_sparcv9: + case ZigLLVM_mips: + case ZigLLVM_mipsel: + case ZigLLVM_mips64: + case ZigLLVM_mips64el: + return 4; + + default: + return 1; + } } diff --git a/src/stage1/target.hpp b/src/stage1/target.hpp index 099c3c1e78..362d63eb32 100644 --- a/src/stage1/target.hpp +++ b/src/stage1/target.hpp @@ -98,6 +98,7 @@ size_t target_libc_count(void); void target_libc_enum(size_t index, ZigTarget *out_target); bool target_libc_needs_crti_crtn(const ZigTarget *target); +unsigned target_fn_ptr_align(const ZigTarget *target); unsigned target_fn_align(const ZigTarget *target); #endif diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index 6dec7ca4d2..f944b7904c 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -306,7 +306,7 @@ test "type info: function type info" { fn testFunction() void { const fn_info = @typeInfo(@TypeOf(foo)); expect(fn_info == .Fn); - expect(fn_info.Fn.alignment == 0); + expect(fn_info.Fn.alignment > 0); expect(fn_info.Fn.calling_convention == .C); expect(!fn_info.Fn.is_generic); expect(fn_info.Fn.args.len == 2); From 19cec0db1e8a9fefd8295c81edb03d631c624e62 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 25 Apr 2021 16:49:21 +0200 Subject: [PATCH 14/33] std: Make met.alignment work on more types Make it return the correct value for function types and optional pointers. --- lib/std/meta.zig | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index c6f800ca2c..9bb02013e6 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -117,10 +117,21 @@ test "std.meta.bitCount" { testing.expect(bitCount(f32) == 32); } +/// Returns the alignment of type T. +/// Note that if T is a pointer or function type the result is different than +/// the one returned by @alignOf(T). +/// If T is a pointer type the alignment of the type it points to is returned. +/// If T is a function type the alignment a target-dependent value is returned. pub fn alignment(comptime T: type) comptime_int { - //@alignOf works on non-pointer types - const P = if (comptime trait.is(.Pointer)(T)) T else *T; - return @typeInfo(P).Pointer.alignment; + return switch (@typeInfo(T)) { + .Optional => |info| switch (@typeInfo(info.child)) { + .Pointer, .Fn => alignment(info.child), + else => @alignOf(T), + }, + .Pointer => |info| info.alignment, + .Fn => |info| info.alignment, + else => @alignOf(T), + }; } test "std.meta.alignment" { @@ -129,6 +140,8 @@ test "std.meta.alignment" { testing.expect(alignment(*align(2) u8) == 2); testing.expect(alignment([]align(1) u8) == 1); testing.expect(alignment([]align(2) u8) == 2); + testing.expect(alignment(fn () void) > 0); + testing.expect(alignment(fn () align(128) void) == 128); } pub fn Child(comptime T: type) type { From ae15022406e5d59787195130cfd5261c6336f41c Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 25 Apr 2021 16:50:41 +0200 Subject: [PATCH 15/33] translate-c: Fix casting of function pointers The @ptrCast(X, @alignCast(@alignOf(T), Y)) pattern is only correct if T is not a function type or a pointer, in that case the @alignOf refers to the pointer itself and not to the pointee type. --- src/translate_c.zig | 2 +- src/translate_c/ast.zig | 12 +++++++++++- test/translate_c.zig | 18 +++++++++--------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/translate_c.zig b/src/translate_c.zig index 60b3961e6e..0a722ab3b4 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -3539,7 +3539,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; }; diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 61d28bb22d..2e75bf1182 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -120,8 +120,11 @@ pub const Node = extern union { std_math_Log2Int, /// @intCast(lhs, rhs) int_cast, - /// @rem(lhs, rhs) + /// @import("std").meta.promoteIntLiteral(value, type, radix) std_meta_promoteIntLiteral, + /// @import("std").meta.alignment(value) + std_meta_alignment, + /// @rem(lhs, rhs) rem, /// @divTrunc(lhs, rhs) div_trunc, @@ -260,6 +263,7 @@ pub const Node = extern union { .switch_else, .block_single, .std_meta_sizeof, + .std_meta_alignment, .bool_to_int, .sizeof, .alignof, @@ -876,6 +880,11 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { const import_node = try renderStdImport(c, "meta", "promoteIntLiteral"); return renderCall(c, import_node, &.{ payload.type, payload.value, payload.radix }); }, + .std_meta_alignment => { + const payload = node.castTag(.std_meta_alignment).?.data; + const import_node = try renderStdImport(c, "meta", "alignment"); + return renderCall(c, import_node, &.{payload}); + }, .std_meta_sizeof => { const payload = node.castTag(.std_meta_sizeof).?.data; const import_node = try renderStdImport(c, "meta", "sizeof"); @@ -2144,6 +2153,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .typeof, .typeinfo, .std_meta_sizeof, + .std_meta_alignment, .std_meta_cast, .std_meta_promoteIntLiteral, .std_meta_vector, diff --git a/test/translate_c.zig b/test/translate_c.zig index 846eec0d62..4021210b29 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1363,7 +1363,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , &[_][]const u8{ \\pub export fn ptrcast() [*c]f32 { \\ var a: [*c]c_int = undefined; - \\ return @ptrCast([*c]f32, @alignCast(@alignOf(f32), a)); + \\ return @ptrCast([*c]f32, @alignCast(@import("std").meta.alignment(f32), a)); \\} }); @@ -1387,16 +1387,16 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub export fn test_ptr_cast() void { \\ var p: ?*c_void = undefined; \\ { - \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@alignOf(u8), p)); - \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@alignOf(c_short), p)); - \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@alignOf(c_int), p)); - \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@alignOf(c_longlong), p)); + \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@import("std").meta.alignment(u8), p)); + \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@import("std").meta.alignment(c_short), p)); + \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@import("std").meta.alignment(c_int), p)); + \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@import("std").meta.alignment(c_longlong), p)); \\ } \\ { - \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@alignOf(u8), p)); - \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@alignOf(c_short), p)); - \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@alignOf(c_int), p)); - \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@alignOf(c_longlong), p)); + \\ var to_char: [*c]u8 = @ptrCast([*c]u8, @alignCast(@import("std").meta.alignment(u8), p)); + \\ var to_short: [*c]c_short = @ptrCast([*c]c_short, @alignCast(@import("std").meta.alignment(c_short), p)); + \\ var to_int: [*c]c_int = @ptrCast([*c]c_int, @alignCast(@import("std").meta.alignment(c_int), p)); + \\ var to_longlong: [*c]c_longlong = @ptrCast([*c]c_longlong, @alignCast(@import("std").meta.alignment(c_longlong), p)); \\ } \\} }); From 0340ed3a9e3fd62e5f8021f2755a26fb3497123e Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 25 Apr 2021 17:02:48 +0200 Subject: [PATCH 16/33] build: Re-add test-translate-c and test-run-translated-c --- ci/drone/linux_script_test | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/drone/linux_script_test b/ci/drone/linux_script_test index d59a2008ba..a8e497a619 100755 --- a/ci/drone/linux_script_test +++ b/ci/drone/linux_script_test @@ -22,7 +22,9 @@ case "$1" in steps="\ test-compiler-rt \ test-minilibc \ - test-compare-output" + test-compare-output \ + test-translate-c \ + test-run-translated-c" ;; '') echo "error: expecting test group argument" From 0747591c3d7469cf3ac5c1a6a80b6058386e337a Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 24 Apr 2021 21:54:42 +0200 Subject: [PATCH 17/33] Add std.crypto.utils.timingSafeCompare A little function to complement the existing crypto.utils.timingSafeEql function with a way to compare large numbers serialized as arrays. This is useful to compare nonces and to check that group elements are in canonical form. Absence of side channels remains a best effort, reusing the common pattern we use elsewhere. --- lib/std/crypto/utils.zig | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/lib/std/crypto/utils.zig b/lib/std/crypto/utils.zig index 08271ac9f4..d5d5d9ade3 100644 --- a/lib/std/crypto/utils.zig +++ b/lib/std/crypto/utils.zig @@ -2,6 +2,9 @@ const std = @import("../std.zig"); 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 +41,48 @@ pub fn timingSafeEql(comptime T: type, a: T, b: T) bool { } } +/// Compare two integers serialized as arrays of the same size, in constant time. +/// Returns .lt if ab and .eq if a=b +pub fn timingSafeCompare(comptime T: type, a: T, b: T, endian: Endian) Order { + switch (@typeInfo(T)) { + .Array => |info| { + const C = info.child; + const bits = switch (@typeInfo(C)) { + .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: C = 0; + var eq: C = 1; + if (endian == .Little) { + var i = a.len; + while (i != 0) { + i -= 1; + const x1 = a[i]; + const x2 = b[i]; + gt |= @truncate(C, (@as(Cext, x2) -% @as(Cext, x1)) >> bits) & eq; + eq &= @truncate(C, (@as(Cext, (x2 ^ x1)) -% 1) >> bits); + } + } else { + for (a) |x1, i| { + const x2 = b[i]; + gt |= @truncate(C, (@as(Cext, x2) -% @as(Cext, x1)) >> bits) & eq; + eq &= @truncate(C, (@as(Cext, (x2 ^ x1)) -% 1) >> bits); + } + } + if (gt != 0) { + return Order.gt; + } else if (eq != 0) { + return Order.eq; + } + return Order.lt; + }, + else => { + @compileError("Only arrays can be compared"); + }, + } +} + /// Sets a slice to zeroes. /// Prevents the store from being optimized out. pub fn secureZero(comptime T: type, s: []T) void { @@ -70,6 +115,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([32]u8, a, b, .Big), .eq); + testing.expectEqual(timingSafeCompare([32]u8, a, b, .Little), .eq); + a[31] = 1; + testing.expectEqual(timingSafeCompare([32]u8, a, b, .Big), .lt); + testing.expectEqual(timingSafeCompare([32]u8, a, b, .Little), .lt); + a[0] = 20; + testing.expectEqual(timingSafeCompare([32]u8, a, b, .Big), .gt); + testing.expectEqual(timingSafeCompare([32]u8, a, b, .Little), .lt); +} + test "crypto.utils.secureZero" { var a = [_]u8{0xfe} ** 8; var b = [_]u8{0xfe} ** 8; From 54db36cd88835ed6b9d9584d9568e9aa72e41480 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 25 Apr 2021 19:10:11 +0200 Subject: [PATCH 18/33] std: Fix backtraces on sparcv9 Flush all the register windows to stack before starting the stack walk, we may otherwise try to read garbage and crash and burn. Add a few comptime annotations to debloat some functions. --- lib/std/debug.zig | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index c84a0e0f18..6cca67f302 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -336,6 +336,13 @@ pub const StackIterator = struct { fp: usize, pub fn init(first_address: ?usize, fp: ?usize) StackIterator { + if (builtin.arch == .sparcv9) { + // Flush all the register windows on stack. + asm volatile ( + \\ flushw + ::: "memory"); + } + return StackIterator{ .first_address = first_address, .fp = fp orelse @frameAddress(), @@ -343,18 +350,18 @@ pub const StackIterator = struct { } // Offset of the saved BP wrt the frame pointer. - const fp_offset = if (builtin.arch.isRISCV()) + const fp_offset = if (comptime builtin.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 (builtin.arch.isSPARC()) + else if (comptime builtin.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 (builtin.arch.isSPARC()) + const fp_bias = if (comptime builtin.arch.isSPARC()) // On SPARC frame pointers are biased by a constant. 2047 else @@ -380,7 +387,7 @@ pub const StackIterator = struct { } fn next_internal(self: *StackIterator) ?usize { - const fp = if (builtin.arch.isSPARC()) + const fp = if (comptime builtin.arch.isSPARC()) // On SPARC the offset is positive. (!) math.add(usize, self.fp, fp_offset) catch return null else From dc29d866490e9d886f838e23cb085a9e28c0143b Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 25 Apr 2021 19:12:49 +0200 Subject: [PATCH 19/33] std: Fix wrong alignOf expression Mostly harmless but conceptually wrong, luckily it hasn't tripped any safety check. --- lib/std/os/linux/tls.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index 757e77bff3..4a36b0d485 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -248,7 +248,7 @@ fn initTLS() void { tls_data = @intToPtr([*]u8, img_base + phdr.p_vaddr)[0..phdr.p_filesz]; tls_data_alloc_size = phdr.p_memsz; } else { - tls_align_factor = @alignOf(*usize); + tls_align_factor = @alignOf(usize); tls_data = &[_]u8{}; tls_data_alloc_size = 0; } @@ -308,7 +308,7 @@ fn initTLS() void { } fn alignPtrCast(comptime T: type, ptr: [*]u8) callconv(.Inline) *T { - return @ptrCast(*T, @alignCast(@alignOf(*T), ptr)); + return @ptrCast(*T, @alignCast(@alignOf(T), ptr)); } /// Initializes all the fields of the static TLS area and returns the computed From 3a55cda14d38a2f83e3c6a9ecb782fcb81d1347d Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sun, 18 Apr 2021 20:31:14 +0200 Subject: [PATCH 20/33] stage2 register manager: Use an array instead of a hashmap for tracking allocated registers --- src/codegen.zig | 46 ++++++++++++-------- src/register_manager.zig | 93 +++++++++++++++++++--------------------- 2 files changed, 72 insertions(+), 67 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index aaab766336..2f83bfe6f3 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -449,7 +449,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .rbrace_src = src_data.rbrace_src, .source = src_data.source, }; - defer function.register_manager.deinit(bin_file.allocator); defer function.stack.deinit(bin_file.allocator); defer function.exitlude_jump_relocs.deinit(bin_file.allocator); @@ -779,8 +778,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { branch.inst_table.putAssumeCapacity(inst, .dead); switch (prev_value) { .register => |reg| { - const canon_reg = toCanonicalReg(reg); - self.register_manager.freeReg(canon_reg); + // TODO separate architectures with registers from + // stack-based architectures (spu_2) + if (callee_preserved_regs.len > 0) { + const canon_reg = toCanonicalReg(reg); + self.register_manager.freeReg(canon_reg); + } }, else => {}, // TODO process stack allocation death } @@ -920,9 +923,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const ptr_bits = arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); if (abi_size <= ptr_bytes) { - try self.register_manager.registers.ensureCapacity(self.gpa, self.register_manager.registers.count() + 1); - if (self.register_manager.tryAllocReg(inst)) |reg| { - return MCValue{ .register = registerAlias(reg, abi_size) }; + // TODO separate architectures with registers from + // stack-based architectures (spu_2) + if (callee_preserved_regs.len > 0) { + if (self.register_manager.tryAllocReg(inst)) |reg| { + return MCValue{ .register = registerAlias(reg, abi_size) }; + } } } } @@ -952,8 +958,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { /// `reg_owner` is the instruction that gets associated with the register in the register table. /// This can have a side effect of spilling instructions to the stack to free up a register. fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue { - try self.register_manager.registers.ensureCapacity(self.gpa, @intCast(u32, self.register_manager.registers.count() + 1)); - const reg = try self.register_manager.allocReg(reg_owner); try self.genSetReg(reg_owner.src, reg_owner.ty, reg, mcv); return MCValue{ .register = reg }; @@ -1240,10 +1244,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .register => |reg| { // If it's in the registers table, need to associate the register with the // new instruction. - if (self.register_manager.registers.getEntry(toCanonicalReg(reg))) |entry| { - entry.value = inst; + // TODO separate architectures with registers from + // stack-based architectures (spu_2) + if (callee_preserved_regs.len > 0) { + if (reg.allocIndex()) |index| { + if (!self.register_manager.isRegFree(reg)) { + self.register_manager.registers[index] = inst; + } + } + log.debug("reusing {} => {*}", .{ reg, inst }); } - log.debug("reusing {} => {*}", .{ reg, inst }); }, .stack_offset => |off| { log.debug("reusing stack offset {} => {*}", .{ off, inst }); @@ -1738,6 +1748,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const arg_index = self.arg_index; self.arg_index += 1; + // TODO separate architectures with registers from + // stack-based architectures (spu_2) if (callee_preserved_regs.len == 0) { return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch}); } @@ -1769,7 +1781,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (mcv) { .register => |reg| { - try self.register_manager.registers.ensureCapacity(self.gpa, self.register_manager.registers.count() + 1); self.register_manager.getRegAssumeFree(toCanonicalReg(reg), &inst.base); }, else => {}, @@ -2075,7 +2086,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (mc_arg) { .none => continue, .register => |reg| { - try self.register_manager.getRegWithoutTracking(reg); + // TODO prevent this macho if block to be generated for all archs + switch (arch) { + .x86_64, .aarch64 => try self.register_manager.getRegWithoutTracking(reg), + else => unreachable, + } try self.genSetReg(arg.src, arg.ty, reg, arg_mcv); }, .stack_offset => { @@ -2397,8 +2412,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const parent_free_registers = self.register_manager.free_registers; var parent_stack = try self.stack.clone(self.gpa); defer parent_stack.deinit(self.gpa); - var parent_registers = try self.register_manager.registers.clone(self.gpa); - defer parent_registers.deinit(self.gpa); + const parent_registers = self.register_manager.registers; try self.branch_stack.append(.{}); @@ -2414,9 +2428,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { var saved_then_branch = self.branch_stack.pop(); defer saved_then_branch.deinit(self.gpa); - self.register_manager.registers.deinit(self.gpa); self.register_manager.registers = parent_registers; - parent_registers = .{}; self.stack.deinit(self.gpa); self.stack = parent_stack; diff --git a/src/register_manager.zig b/src/register_manager.zig index 01f83aa2f5..270c762887 100644 --- a/src/register_manager.zig +++ b/src/register_manager.zig @@ -16,7 +16,7 @@ pub fn RegisterManager( ) type { return struct { /// The key must be canonical register. - registers: std.AutoHashMapUnmanaged(Register, *ir.Inst) = .{}, + registers: [callee_preserved_regs.len]?*ir.Inst = [_]?*ir.Inst{null} ** callee_preserved_regs.len, free_registers: FreeRegInt = math.maxInt(FreeRegInt), /// Tracks all registers allocated in the course of this function allocated_registers: FreeRegInt = 0, @@ -31,14 +31,6 @@ pub fn RegisterManager( return @fieldParentPtr(Function, "register_manager", self); } - pub fn deinit(self: *Self, allocator: *Allocator) void { - self.registers.deinit(allocator); - } - - fn isTracked(reg: Register) bool { - return reg.allocIndex() != null; - } - fn markRegUsed(self: *Self, reg: Register) void { if (FreeRegInt == u0) return; const index = reg.allocIndex() orelse return; @@ -73,13 +65,13 @@ pub fn RegisterManager( return self.allocated_registers & @as(FreeRegInt, 1) << shift != 0; } - /// Before calling, must ensureCapacity + count on self.registers. /// Returns `null` if all registers are allocated. pub fn tryAllocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ?[count]Register { if (self.tryAllocRegsWithoutTracking(count)) |regs| { for (regs) |reg, i| { + const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null + self.registers[index] = insts[i]; self.markRegUsed(reg); - self.registers.putAssumeCapacityNoClobber(reg, insts[i]); } return regs; @@ -88,13 +80,11 @@ pub fn RegisterManager( } } - /// Before calling, must ensureCapacity + 1 on self.registers. /// Returns `null` if all registers are allocated. pub fn tryAllocReg(self: *Self, inst: *ir.Inst) ?Register { return if (tryAllocRegs(self, 1, .{inst})) |regs| regs[0] else null; } - /// Before calling, must ensureCapacity + count on self.registers. pub fn allocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ![count]Register { comptime assert(count > 0 and count <= callee_preserved_regs.len); @@ -106,24 +96,22 @@ pub fn RegisterManager( std.mem.copy(Register, ®s, callee_preserved_regs[0..count]); for (regs) |reg, i| { + const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null if (self.isRegFree(reg)) { self.markRegUsed(reg); - self.registers.putAssumeCapacityNoClobber(reg, insts[i]); } else { - const regs_entry = self.registers.getEntry(reg).?; - const spilled_inst = regs_entry.value; - regs_entry.value = insts[i]; + const spilled_inst = self.registers[index].?; try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); } + self.registers[index] = insts[i]; } break :blk regs; }; } - /// Before calling, must ensureCapacity + 1 on self.registers. pub fn allocReg(self: *Self, inst: *ir.Inst) !Register { - return (try allocRegs(self, 1, .{inst}))[0]; + return (try self.allocRegs(1, .{inst}))[0]; } /// Does not track the registers. @@ -150,37 +138,48 @@ pub fn RegisterManager( /// Does not track the register. /// Returns `null` if all registers are allocated. pub fn tryAllocRegWithoutTracking(self: *Self) ?Register { - return if (tryAllocRegsWithoutTracking(self, 1)) |regs| regs[0] else null; + return if (self.tryAllocRegsWithoutTracking(1)) |regs| regs[0] else null; + } + + /// Does not track the registers + pub fn allocRegsWithoutTracking(self: *Self, comptime count: comptime_int) ![count]Register { + return self.tryAllocRegsWithoutTracking(count) orelse blk: { + // We'll take over the first count registers. Spill + // the instructions that were previously there to a + // stack allocations. + var regs: [count]Register = undefined; + std.mem.copy(Register, ®s, callee_preserved_regs[0..count]); + + for (regs) |reg, i| { + const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null + if (!self.isRegFree(reg)) { + const spilled_inst = self.registers[index].?; + try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); + self.registers[index] = null; + self.markRegFree(reg); + } + } + + break :blk regs; + }; } /// Does not track the register. pub fn allocRegWithoutTracking(self: *Self) !Register { - return self.tryAllocRegWithoutTracking() orelse b: { - // We'll take over the first register. Move the instruction that was previously - // there to a stack allocation. - const reg = callee_preserved_regs[0]; - const regs_entry = self.registers.remove(reg).?; - const spilled_inst = regs_entry.value; - try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); - self.markRegFree(reg); - - break :b reg; - }; + return (try self.allocRegsWithoutTracking(1))[0]; } /// Allocates the specified register with the specified /// instruction. Spills the register if it is currently /// allocated. - /// Before calling, must ensureCapacity + 1 on self.registers. pub fn getReg(self: *Self, reg: Register, inst: *ir.Inst) !void { - if (!isTracked(reg)) return; + const index = reg.allocIndex() orelse return; if (!self.isRegFree(reg)) { // Move the instruction that was previously there to a // stack allocation. - const regs_entry = self.registers.getEntry(reg).?; - const spilled_inst = regs_entry.value; - regs_entry.value = inst; + const spilled_inst = self.registers[index].?; + self.registers[index] = inst; try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); } else { self.getRegAssumeFree(reg, inst); @@ -190,34 +189,33 @@ pub fn RegisterManager( /// Spills the register if it is currently allocated. /// Does not track the register. pub fn getRegWithoutTracking(self: *Self, reg: Register) !void { - if (!isTracked(reg)) return; + const index = reg.allocIndex() orelse return; if (!self.isRegFree(reg)) { // Move the instruction that was previously there to a // stack allocation. - const regs_entry = self.registers.remove(reg).?; - const spilled_inst = regs_entry.value; + const spilled_inst = self.registers[index].?; try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); self.markRegFree(reg); } } /// Allocates the specified register with the specified - /// instruction. Assumes that the register is free and no + /// instruction. Asserts that the register is free and no /// spilling is necessary. - /// Before calling, must ensureCapacity + 1 on self.registers. pub fn getRegAssumeFree(self: *Self, reg: Register, inst: *ir.Inst) void { - if (!isTracked(reg)) return; + const index = reg.allocIndex() orelse return; - self.registers.putAssumeCapacityNoClobber(reg, inst); + assert(self.registers[index] == null); + self.registers[index] = inst; self.markRegUsed(reg); } /// Marks the specified register as free pub fn freeReg(self: *Self, reg: Register) void { - if (!isTracked(reg)) return; + const index = reg.allocIndex() orelse return; - _ = self.registers.remove(reg); + self.registers[index] = null; self.markRegFree(reg); } }; @@ -247,7 +245,6 @@ const MockFunction = struct { const Self = @This(); pub fn deinit(self: *Self) void { - self.register_manager.deinit(self.allocator); self.spilled.deinit(self.allocator); } @@ -273,7 +270,6 @@ test "tryAllocReg: no spilling" { std.testing.expect(!function.register_manager.isRegAllocated(.r2)); std.testing.expect(!function.register_manager.isRegAllocated(.r3)); - try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); std.testing.expectEqual(@as(?MockRegister, .r2), function.register_manager.tryAllocReg(&mock_instruction)); std.testing.expectEqual(@as(?MockRegister, .r3), function.register_manager.tryAllocReg(&mock_instruction)); std.testing.expectEqual(@as(?MockRegister, null), function.register_manager.tryAllocReg(&mock_instruction)); @@ -305,7 +301,6 @@ test "allocReg: spilling" { std.testing.expect(!function.register_manager.isRegAllocated(.r2)); std.testing.expect(!function.register_manager.isRegAllocated(.r3)); - try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction)); std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction)); @@ -336,14 +331,12 @@ test "getReg" { std.testing.expect(!function.register_manager.isRegAllocated(.r2)); std.testing.expect(!function.register_manager.isRegAllocated(.r3)); - try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); try function.register_manager.getReg(.r3, &mock_instruction); std.testing.expect(!function.register_manager.isRegAllocated(.r2)); std.testing.expect(function.register_manager.isRegAllocated(.r3)); // Spill r3 - try function.register_manager.registers.ensureCapacity(allocator, function.register_manager.registers.count() + 2); try function.register_manager.getReg(.r3, &mock_instruction); std.testing.expect(!function.register_manager.isRegAllocated(.r2)); From 5a94794b73c8f9d042bc7928d071cc1004487983 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 20 Apr 2021 10:31:02 +0200 Subject: [PATCH 21/33] std: Fix thread creation with field-less context type Closes #8524 --- lib/std/Thread.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 7e8a6226e6..01405b104f 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -199,7 +199,8 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF inner: Context, }; fn threadMain(raw_arg: windows.LPVOID) callconv(.C) windows.DWORD { - const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; + const arg = if (@sizeOf(Context) == 0) undefined // + else @ptrCast(*Context, @alignCast(@alignOf(Context), raw_arg)).*; switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { .NoReturn => { @@ -260,7 +261,8 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF const MainFuncs = struct { fn linuxThreadMain(ctx_addr: usize) callconv(.C) u8 { - const arg = if (@sizeOf(Context) == 0) {} else @intToPtr(*const Context, ctx_addr).*; + const arg = if (@sizeOf(Context) == 0) undefined // + else @intToPtr(*Context, ctx_addr).*; switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { .NoReturn => { @@ -292,7 +294,8 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF } } fn posixThreadMain(ctx: ?*c_void) callconv(.C) ?*c_void { - const arg = if (@sizeOf(Context) == 0) {} else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*; + const arg = if (@sizeOf(Context) == 0) undefined // + else @ptrCast(*Context, @alignCast(@alignOf(Context), ctx)).*; switch (@typeInfo(@typeInfo(@TypeOf(startFn)).Fn.return_type.?)) { .NoReturn => { From 82f1d592fae021fcfc737e3cb6c107b325fcf1ee Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Sun, 25 Apr 2021 19:42:59 +0200 Subject: [PATCH 22/33] stage1: Use correct alignment for asyncCall frame --- src/stage1/analyze.cpp | 2 +- src/stage1/analyze.hpp | 1 + src/stage1/ir.cpp | 8 ++++++-- test/compile_errors.zig | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index e1e0c496f6..f76ea85ca9 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -4769,7 +4769,7 @@ 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 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] diff --git a/src/stage1/analyze.hpp b/src/stage1/analyze.hpp index cea48b893c..2815274f63 100644 --- a/src/stage1/analyze.hpp +++ b/src/stage1/analyze.hpp @@ -47,6 +47,7 @@ ZigType *get_test_fn_type(CodeGen *g); ZigType *get_any_frame_type(CodeGen *g, ZigType *result_type); bool handle_is_ptr(CodeGen *g, ZigType *type_entry); Error emit_error_unless_callconv_allowed_for_target(CodeGen *g, AstNode *source_node, CallingConvention cc); +uint32_t get_async_frame_align_bytes(CodeGen *g); bool type_has_bits(CodeGen *g, ZigType *type_entry); Error type_has_bits2(CodeGen *g, ZigType *type_entry, bool *result); diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 176d50dc72..7edfe37c92 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -20659,8 +20659,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); @@ -30095,7 +30099,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/test/compile_errors.zig b/test/compile_errors.zig index bfa9b592b4..5b36027248 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2136,7 +2136,9 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\} \\fn func() callconv(.Async) void {} , &[_][]const u8{ - "tmp.zig:4:21: error: expected type '[]align(16) u8', found '*[64]u8'", + // Split the check in two as the alignment value is target dependent. + "tmp.zig:4:21: error: expected type '[]align(", + ") u8', found '*[64]u8'", }); cases.add("atomic orderings of fence Acquire or stricter", From bf67a3fdc9efea7126058b21e687c24868bc1268 Mon Sep 17 00:00:00 2001 From: jumpnbrownweasel <49791153+jumpnbrownweasel@users.noreply.github.com> Date: Sun, 25 Apr 2021 16:16:24 -0700 Subject: [PATCH 23/33] #8454 Fix for std.mem.replacementSize adjacent matches bug. (#8455) * #8454 Fix for std.mem.replacementSize adjacent matches bug. When two 'needle' values are adjacent in the 'input' slice, the size is not counted correctly. The 2nd 'needle' value is not matched because the index is incremented by one after changing the index to account for the first value. The impact is the the size returned is incorrect, and could cause UB when this amount is used to size of the buffer passed to std.mem.replace. * Apply changes from PR review: - Add assert checking that the needle is non-empty and doc for this. - Add minimal test that an empty input works. - Use testing.expectEqualStrings. --- lib/std/mem.zig | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 66505f5d29..274da3b8f1 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1876,7 +1876,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; @@ -1899,22 +1903,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; } } @@ -1923,9 +1953,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. From 77cb45f59f7b37c316af1762298d6032e2b130b5 Mon Sep 17 00:00:00 2001 From: Vincent Rischmann Date: Mon, 26 Apr 2021 18:03:32 +0200 Subject: [PATCH 24/33] thread: simplify and remove useless return in spawn (#8621) --- lib/std/Thread.zig | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 01405b104f..92221e8786 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -360,15 +360,13 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF MainFuncs.posixThreadMain, thread_obj.data.memory.ptr, ); - switch (err) { - 0 => return thread_obj, - os.EAGAIN => return error.SystemResources, + return switch (err) { + 0 => thread_obj, + os.EAGAIN => error.SystemResources, os.EPERM => unreachable, os.EINVAL => unreachable, - else => return os.unexpectedErrno(err), - } - - return thread_obj; + else => os.unexpectedErrno(err), + }; } var guard_end_offset: usize = undefined; From 896d93e1e6f7638afd1536c0d40d93a3faeb1356 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Mon, 26 Apr 2021 12:33:11 +0200 Subject: [PATCH 25/33] stage2: Add framework for host CPU detection on Linux Add a generic framework to parse /proc/cpuinfo and implement the model detection for 64bit SPARC targets as proof of concept. --- lib/std/zig/system.zig | 15 +++-- lib/std/zig/system/linux.zig | 118 +++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 lib/std/zig/system/linux.zig diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 485bd479bf..42099c6efe 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -14,6 +14,7 @@ const process = std.process; const Target = std.Target; const CrossTarget = std.zig.CrossTarget; const macos = @import("system/macos.zig"); +const linux = @import("system/linux.zig"); pub const windows = @import("system/windows.zig"); pub const getSDKPath = macos.getSDKPath; @@ -911,15 +912,19 @@ pub const NativeTargetInfo = struct { .x86_64, .i386 => { return @import("system/x86.zig").detectNativeCpuAndFeatures(cpu_arch, os, cross_target); }, - else => { - // This architecture does not have CPU model & feature detection yet. - // See https://github.com/ziglang/zig/issues/4591 - return null; - }, + else => {}, } + + // This architecture does not have CPU model & feature detection yet. + // See https://github.com/ziglang/zig/issues/4591 + if (std.Target.current.os.tag != .linux) + return null; + + return linux.detectNativeCpuAndFeatures(); } }; test { _ = @import("system/macos.zig"); + _ = @import("system/linux.zig"); } diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig new file mode 100644 index 0000000000..14e61a9de4 --- /dev/null +++ b/lib/std/zig/system/linux.zig @@ -0,0 +1,118 @@ +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); + + return Target.Cpu{ + .arch = arch, + .model = self.model orelse Target.Cpu.Model.generic(arch), + .features = Target.Cpu.Feature.Set.empty, + }; + } +}; + +const SparcCpuinfoParser = CpuinfoParser(SparcCpuinfoImpl); + +test "cpuinfo: SPARC" { + const mock_cpuinfo = + \\cpu : UltraSparc T2 (Niagara2) + \\fpu : UltraSparc T2 integrated FPU + \\pmu : niagara2 + \\type : sun4v + ; + + var fbs = io.fixedBufferStream(mock_cpuinfo); + + const r = SparcCpuinfoParser.parse(.sparcv9, fbs.reader()) catch unreachable; + testing.expectEqual(&Target.sparc.cpu.niagara2, r.?.model); +} + +// 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(); + + switch (std.Target.current.cpu.arch) { + .sparcv9 => return SparcCpuinfoParser.parse(.sparcv9, f.reader()) catch null, + else => {}, + } + + return null; +} From 7e6cc4c50565b6403bd2ecaabccd4ca37cb05ed2 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Mon, 26 Apr 2021 18:28:17 +0200 Subject: [PATCH 26/33] stage2: Ensure the features set is populated --- lib/std/zig/system/linux.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig index 14e61a9de4..ce802df7a8 100644 --- a/lib/std/zig/system/linux.zig +++ b/lib/std/zig/system/linux.zig @@ -53,10 +53,11 @@ const SparcCpuinfoImpl = struct { // 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 = self.model orelse Target.Cpu.Model.generic(arch), - .features = Target.Cpu.Feature.Set.empty, + .model = model, + .features = model.features, }; } }; @@ -75,6 +76,7 @@ test "cpuinfo: SPARC" { const r = SparcCpuinfoParser.parse(.sparcv9, fbs.reader()) catch unreachable; testing.expectEqual(&Target.sparc.cpu.niagara2, r.?.model); + testing.expect(Target.sparc.cpu.niagara2.features.eql(r.?.features)); } // The generic implementation of a /proc/cpuinfo parser. From ddf9ff79bd2eccadafcdf12b191c62c66b247b64 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 26 Apr 2021 10:44:40 -0700 Subject: [PATCH 27/33] Revert "thread: simplify and remove useless return in spawn (#8621)" This reverts commit 77cb45f59f7b37c316af1762298d6032e2b130b5. Zig's error return traces will point to the return token if they happen to occur, so having multiple return statements makes those stack traces really helpful. This destroys debuggability. --- lib/std/Thread.zig | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 92221e8786..01405b104f 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -360,13 +360,15 @@ pub fn spawn(comptime startFn: anytype, context: SpawnContextType(@TypeOf(startF MainFuncs.posixThreadMain, thread_obj.data.memory.ptr, ); - return switch (err) { - 0 => thread_obj, - os.EAGAIN => error.SystemResources, + switch (err) { + 0 => return thread_obj, + os.EAGAIN => return error.SystemResources, os.EPERM => unreachable, os.EINVAL => unreachable, - else => os.unexpectedErrno(err), - }; + else => return os.unexpectedErrno(err), + } + + return thread_obj; } var guard_end_offset: usize = undefined; From 030fa5e7ebc21339728c79f33bf5d5d22e0a760e Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Mon, 26 Apr 2021 22:25:48 +0200 Subject: [PATCH 28/33] 25519: remove unused const, safeguard against unreduced scalars (#8624) * 25519: remove unused const, safeguard against unreduced scalars No behavior change, but it makes the existing code better match the forthcoming code for other curves. Rename nonAdjacentForm() to slide(), remove an unneeded and confusing constant, and do a reduction in slide() if 257 bits would be required. Note that in all the high-level functions, the top bit is always cleared, so the reduction is never necessary. But since the low-level functions are public, the check is a safe thing to have. * 25519: make identityElement public, deprecate neutralElement Also fix a few comments by the way. --- lib/std/crypto/25519/edwards25519.zig | 25 +++++++++---------------- lib/std/crypto/25519/field.zig | 2 +- lib/std/crypto/25519/scalar.zig | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/lib/std/crypto/25519/edwards25519.zig b/lib/std/crypto/25519/edwards25519.zig index e8163a3960..4e3d156007 100644 --- a/lib/std/crypto/25519/edwards25519.zig +++ b/lib/std/crypto/25519/edwards25519.zig @@ -75,16 +75,8 @@ pub const Edwards25519 = struct { .is_base = true, }; - /// The edwards25519 neutral element. - pub const neutralElement = Edwards25519{ - .x = Fe{ .limbs = .{ 2251799813685229, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247 } }, - .y = Fe{ .limbs = .{ 1507481815385608, 2223447444246085, 1083941587175919, 2059929906842505, 1581435440146976 } }, - .z = Fe{ .limbs = .{ 1507481815385608, 2223447444246085, 1083941587175919, 2059929906842505, 1581435440146976 } }, - .t = Fe{ .limbs = .{ 2251799813685229, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247 } }, - .is_base = false, - }; - - const identityElement = Edwards25519{ .x = Fe.zero, .y = Fe.one, .z = Fe.one, .t = Fe.zero }; + pub const neutralElement = @compileError("deprecated: use identityElement instead"); + pub const identityElement = Edwards25519{ .x = Fe.zero, .y = Fe.one, .z = Fe.one, .t = Fe.zero }; /// Reject the neutral element. pub fn rejectIdentity(p: Edwards25519) IdentityElementError!void { @@ -160,9 +152,10 @@ pub const Edwards25519 = struct { return t; } - fn nonAdjacentForm(s: [32]u8) [2 * 32]i8 { + fn slide(s: [32]u8) [2 * 32]i8 { + const reduced = if ((s[s.len - 1] & 0x80) != 0) s else scalar.reduce(s); var e: [2 * 32]i8 = undefined; - for (s) |x, i| { + for (reduced) |x, i| { e[i * 2 + 0] = @as(i8, @truncate(u4, x)); e[i * 2 + 1] = @as(i8, @truncate(u4, x >> 4)); } @@ -185,7 +178,7 @@ pub const Edwards25519 = struct { // avoid these to keep the standard library lightweight. fn pcMul(pc: [9]Edwards25519, s: [32]u8, comptime vartime: bool) IdentityElementError!Edwards25519 { std.debug.assert(vartime); - const e = nonAdjacentForm(s); + const e = slide(s); var q = Edwards25519.identityElement; var pos: usize = 2 * 32 - 1; while (true) : (pos -= 1) { @@ -280,8 +273,8 @@ pub const Edwards25519 = struct { xpc[4].rejectIdentity() catch return error.WeakPublicKey; break :pc xpc; }; - const e1 = nonAdjacentForm(s1); - const e2 = nonAdjacentForm(s2); + const e1 = slide(s1); + const e2 = slide(s2); var q = Edwards25519.identityElement; var pos: usize = 2 * 32 - 1; while (true) : (pos -= 1) { @@ -318,7 +311,7 @@ pub const Edwards25519 = struct { } var es: [count][2 * 32]i8 = undefined; for (ss) |s, i| { - es[i] = nonAdjacentForm(s); + es[i] = slide(s); } var q = Edwards25519.identityElement; var pos: usize = 2 * 32 - 1; diff --git a/lib/std/crypto/25519/field.zig b/lib/std/crypto/25519/field.zig index 5ac184080c..aae53e9081 100644 --- a/lib/std/crypto/25519/field.zig +++ b/lib/std/crypto/25519/field.zig @@ -355,7 +355,7 @@ pub const Fe = struct { return fe; } - /// Compute the inverse of a field element + /// Return the inverse of a field element, or 0 if a=0. pub fn invert(a: Fe) Fe { var t0 = a.sq(); var t1 = t0.sqn(2).mul(a); diff --git a/lib/std/crypto/25519/scalar.zig b/lib/std/crypto/25519/scalar.zig index 21578486e8..e8564ff577 100644 --- a/lib/std/crypto/25519/scalar.zig +++ b/lib/std/crypto/25519/scalar.zig @@ -98,7 +98,7 @@ pub fn sub(a: [32]u8, b: [32]u8) [32]u8 { return add(a, neg(b)); } -/// A scalar in unpacked reprentation +/// A scalar in unpacked representation pub const Scalar = struct { const Limbs = [5]u64; limbs: Limbs = undefined, From d3361c41db638dc606c2e7259900cd281c5e95e4 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 26 Apr 2021 22:32:22 +0200 Subject: [PATCH 29/33] Change timingSafeCompare() to accept slices --- lib/std/crypto/utils.zig | 80 +++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/lib/std/crypto/utils.zig b/lib/std/crypto/utils.zig index d5d5d9ade3..ca86601bf9 100644 --- a/lib/std/crypto/utils.zig +++ b/lib/std/crypto/utils.zig @@ -1,4 +1,5 @@ const std = @import("../std.zig"); +const debug = std.debug; const mem = std.mem; const testing = std.testing; @@ -43,44 +44,37 @@ pub fn timingSafeEql(comptime T: type, a: T, b: T) bool { /// Compare two integers serialized as arrays of the same size, in constant time. /// Returns .lt if ab and .eq if a=b -pub fn timingSafeCompare(comptime T: type, a: T, b: T, endian: Endian) Order { - switch (@typeInfo(T)) { - .Array => |info| { - const C = info.child; - const bits = switch (@typeInfo(C)) { - .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: C = 0; - var eq: C = 1; - if (endian == .Little) { - var i = a.len; - while (i != 0) { - i -= 1; - const x1 = a[i]; - const x2 = b[i]; - gt |= @truncate(C, (@as(Cext, x2) -% @as(Cext, x1)) >> bits) & eq; - eq &= @truncate(C, (@as(Cext, (x2 ^ x1)) -% 1) >> bits); - } - } else { - for (a) |x1, i| { - const x2 = b[i]; - gt |= @truncate(C, (@as(Cext, x2) -% @as(Cext, x1)) >> bits) & eq; - eq &= @truncate(C, (@as(Cext, (x2 ^ x1)) -% 1) >> bits); - } - } - if (gt != 0) { - return Order.gt; - } else if (eq != 0) { - return Order.eq; - } - return Order.lt; - }, - else => { - @compileError("Only arrays can be compared"); - }, +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. @@ -118,14 +112,14 @@ test "crypto.utils.timingSafeEql (vectors)" { test "crypto.utils.timingSafeCompare" { var a = [_]u8{10} ** 32; var b = [_]u8{10} ** 32; - testing.expectEqual(timingSafeCompare([32]u8, a, b, .Big), .eq); - testing.expectEqual(timingSafeCompare([32]u8, a, b, .Little), .eq); + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Big), .eq); + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .eq); a[31] = 1; - testing.expectEqual(timingSafeCompare([32]u8, a, b, .Big), .lt); - testing.expectEqual(timingSafeCompare([32]u8, a, b, .Little), .lt); + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Big), .lt); + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .lt); a[0] = 20; - testing.expectEqual(timingSafeCompare([32]u8, a, b, .Big), .gt); - testing.expectEqual(timingSafeCompare([32]u8, a, b, .Little), .lt); + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Big), .gt); + testing.expectEqual(timingSafeCompare(u8, &a, &b, .Little), .lt); } test "crypto.utils.secureZero" { From da9da76e3f91bf1b7e0dc89cb6d1b79dd0f0ad4b Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Tue, 27 Apr 2021 15:46:42 +0200 Subject: [PATCH 30/33] stage1: Implement CPU host detection for PowerPC targets Untested but should work well on both 32 and 64 bit systems. --- lib/std/zig/system/linux.zig | 95 +++++++++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 8 deletions(-) diff --git a/lib/std/zig/system/linux.zig b/lib/std/zig/system/linux.zig index ce802df7a8..dc0d3c7f6a 100644 --- a/lib/std/zig/system/linux.zig +++ b/lib/std/zig/system/linux.zig @@ -65,18 +65,91 @@ const SparcCpuinfoImpl = struct { const SparcCpuinfoParser = CpuinfoParser(SparcCpuinfoImpl); test "cpuinfo: SPARC" { - const mock_cpuinfo = + try testParser(SparcCpuinfoParser, &Target.sparc.cpu.niagara2, \\cpu : UltraSparc T2 (Niagara2) \\fpu : UltraSparc T2 integrated FPU \\pmu : niagara2 \\type : sun4v - ; + ); +} - var fbs = io.fixedBufferStream(mock_cpuinfo); +const PowerpcCpuinfoImpl = struct { + model: ?*const Target.Cpu.Model = null, - const r = SparcCpuinfoParser.parse(.sparcv9, fbs.reader()) catch unreachable; - testing.expectEqual(&Target.sparc.cpu.niagara2, r.?.model); - testing.expect(Target.sparc.cpu.niagara2.features.eql(r.?.features)); + 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. @@ -111,8 +184,14 @@ pub fn detectNativeCpuAndFeatures() ?Target.Cpu { }; defer f.close(); - switch (std.Target.current.cpu.arch) { - .sparcv9 => return SparcCpuinfoParser.parse(.sparcv9, f.reader()) catch null, + 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 => {}, } From bc06e19828428ed9c2fc3698f418d46e62e327f5 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Tue, 27 Apr 2021 16:12:59 +0800 Subject: [PATCH 31/33] stage2 riscv64: cleanup code and add tests --- src/codegen/riscv64.zig | 57 +++++++++++++++++++++++++++++++++-------- test/stage2/riscv64.zig | 45 ++++++++++++++++++++++++++++++++ test/stage2/test.zig | 46 +++------------------------------ 3 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 test/stage2/riscv64.zig diff --git a/src/codegen/riscv64.zig b/src/codegen/riscv64.zig index 96b9c58f9c..a01f38289a 100644 --- a/src/codegen/riscv64.zig +++ b/src/codegen/riscv64.zig @@ -1,5 +1,7 @@ const std = @import("std"); const DW = std.dwarf; +const assert = std.debug.assert; +const testing = std.testing; // TODO: this is only tagged to facilitate the monstrosity. // Once packed structs work make it packed. @@ -110,7 +112,7 @@ pub const Instruction = union(enum) { // -- less burden on callsite, bonus semantic checking fn bType(op: u7, fn3: u3, r1: Register, r2: Register, imm: i13) Instruction { const umm = @bitCast(u13, imm); - if (umm % 2 != 0) @panic("Internal error: misaligned branch target"); + assert(umm % 2 == 0); // misaligned branch target return Instruction{ .B = .{ @@ -140,15 +142,15 @@ pub const Instruction = union(enum) { } fn jType(op: u7, rd: Register, imm: i21) Instruction { - const umm = @bitcast(u21, imm); - if (umm % 2 != 0) @panic("Internal error: misaligned jump target"); + const umm = @bitCast(u21, imm); + assert(umm % 2 == 0); // misaligned jump target return Instruction{ .J = .{ .opcode = op, .rd = @enumToInt(rd), .imm1_10 = @truncate(u10, umm >> 1), - .imm11 = @truncate(u1, umm >> 1), + .imm11 = @truncate(u1, umm >> 11), .imm12_19 = @truncate(u8, umm >> 12), .imm20 = @truncate(u1, umm >> 20), }, @@ -340,27 +342,27 @@ pub const Instruction = union(enum) { // Branch - pub fn beq(r1: Register, r2: Register, offset: u13) Instruction { + pub fn beq(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b000, r1, r2, offset); } - pub fn bne(r1: Register, r2: Register, offset: u13) Instruction { + pub fn bne(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b001, r1, r2, offset); } - pub fn blt(r1: Register, r2: Register, offset: u13) Instruction { + pub fn blt(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b100, r1, r2, offset); } - pub fn bge(r1: Register, r2: Register, offset: u13) Instruction { + pub fn bge(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b101, r1, r2, offset); } - pub fn bltu(r1: Register, r2: Register, offset: u13) Instruction { + pub fn bltu(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b110, r1, r2, offset); } - pub fn bgeu(r1: Register, r2: Register, offset: u13) Instruction { + pub fn bgeu(r1: Register, r2: Register, offset: i13) Instruction { return bType(0b1100011, 0b111, r1, r2, offset); } @@ -431,3 +433,38 @@ pub const Register = enum(u5) { pub const callee_preserved_regs = [_]Register{ .s0, .s1, .s2, .s3, .s4, .s5, .s6, .s7, .s8, .s9, .s10, .s11, }; + +test "serialize instructions" { + const Testcase = struct { + inst: Instruction, + expected: u32, + }; + + const testcases = [_]Testcase{ + .{ // add t6, zero, zero + .inst = Instruction.add(.t6, .zero, .zero), + .expected = 0b0000000_00000_00000_000_11111_0110011, + }, + .{ // sd s0, 0x7f(s0) + .inst = Instruction.sd(.s0, 0x7f, .s0), + .expected = 0b0000011_01000_01000_011_11111_0100011, + }, + .{ // bne s0, s1, 0x42 + .inst = Instruction.bne(.s0, .s1, 0x42), + .expected = 0b0_000010_01001_01000_001_0001_0_1100011, + }, + .{ // j 0x1a + .inst = Instruction.jal(.zero, 0x1a), + .expected = 0b0_0000001101_0_00000000_00000_1101111, + }, + .{ // ebreak + .inst = Instruction.ebreak, + .expected = 0b000000000001_00000_000_00000_1110011, + }, + }; + + for (testcases) |case| { + const actual = case.inst.toU32(); + testing.expectEqual(case.expected, actual); + } +} diff --git a/test/stage2/riscv64.zig b/test/stage2/riscv64.zig new file mode 100644 index 0000000000..e2035be47a --- /dev/null +++ b/test/stage2/riscv64.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const TestContext = @import("../../src/test.zig").TestContext; + +const linux_riscv64 = std.zig.CrossTarget{ + .cpu_arch = .riscv64, + .os_tag = .linux, +}; + +pub fn addCases(ctx: *TestContext) !void { + { + var case = ctx.exe("riscv64 hello world", linux_riscv64); + // Regular old hello world + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("ecall" + \\ : + \\ : [number] "{a7}" (64), + \\ [arg1] "{a0}" (1), + \\ [arg2] "{a1}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{a2}" ("Hello, World!\n".len) + \\ : "rcx", "r11", "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("ecall" + \\ : + \\ : [number] "{a7}" (94), + \\ [arg1] "{a0}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + } +} diff --git a/test/stage2/test.zig b/test/stage2/test.zig index b4bc1a413e..c8b9b0cc96 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -11,11 +11,6 @@ const linux_x64 = std.zig.CrossTarget{ .os_tag = .linux, }; -const linux_riscv64 = std.zig.CrossTarget{ - .cpu_arch = .riscv64, - .os_tag = .linux, -}; - pub fn addCases(ctx: *TestContext) !void { try @import("cbe.zig").addCases(ctx); try @import("spu-ii.zig").addCases(ctx); @@ -24,6 +19,7 @@ pub fn addCases(ctx: *TestContext) !void { try @import("llvm.zig").addCases(ctx); try @import("wasm.zig").addCases(ctx); try @import("darwin.zig").addCases(ctx); + try @import("riscv64.zig").addCases(ctx); { var case = ctx.exe("hello world with updates", linux_x64); @@ -137,42 +133,6 @@ pub fn addCases(ctx: *TestContext) !void { ); } - { - var case = ctx.exe("riscv64 hello world", linux_riscv64); - // Regular old hello world - case.addCompareOutput( - \\export fn _start() noreturn { - \\ print(); - \\ - \\ exit(); - \\} - \\ - \\fn print() void { - \\ asm volatile ("ecall" - \\ : - \\ : [number] "{a7}" (64), - \\ [arg1] "{a0}" (1), - \\ [arg2] "{a1}" (@ptrToInt("Hello, World!\n")), - \\ [arg3] "{a2}" ("Hello, World!\n".len) - \\ : "rcx", "r11", "memory" - \\ ); - \\ return; - \\} - \\ - \\fn exit() noreturn { - \\ asm volatile ("ecall" - \\ : - \\ : [number] "{a7}" (94), - \\ [arg1] "{a0}" (0) - \\ : "rcx", "r11", "memory" - \\ ); - \\ unreachable; - \\} - , - "Hello, World!\n", - ); - } - { var case = ctx.exe("adding numbers at comptime", linux_x64); case.addCompareOutput( @@ -1048,7 +1008,7 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); try case.files.append(.{ - .src = + .src = \\pub fn print() void { \\ asm volatile ("syscall" \\ : @@ -1085,7 +1045,7 @@ pub fn addCases(ctx: *TestContext) !void { &.{":2:25: error: 'print' is private"}, ); try case.files.append(.{ - .src = + .src = \\fn print() void { \\ asm volatile ("syscall" \\ : From 86e129eff65f209285e3a3c8430beb41c5acb6cd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Apr 2021 10:26:47 -0700 Subject: [PATCH 32/33] std.Target: bump freebsd known maximum version --- lib/std/target.zig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 40a5d14f2a..e9185ef345 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -211,8 +211,9 @@ pub const Target = struct { /// If neither of these cases apply, a runtime check should be used to determine if the /// target supports a given OS feature. /// - /// Binaries built with a given maximum version will continue to function on newer operating system - /// versions. However, such a binary may not take full advantage of the newer operating system APIs. + /// Binaries built with a given maximum version will continue to function on newer + /// operating system versions. However, such a binary may not take full advantage of the + /// newer operating system APIs. /// /// See `Os.isAtLeast`. pub const VersionRange = union { @@ -260,7 +261,7 @@ pub const Target = struct { .freebsd => return .{ .semver = Version.Range{ .min = .{ .major = 12, .minor = 0 }, - .max = .{ .major = 12, .minor = 1 }, + .max = .{ .major = 13, .minor = 0 }, }, }, .macos => return .{ From 55c58f226d06d3708a82878a67f3800e0ce5810b Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 28 Apr 2021 21:01:15 +0200 Subject: [PATCH 33/33] std: Add a parser for hexadecimal floating point numbers Should be good enough to unblock progress on the stage2 compiler. Unifying this parser and the regular one (and perhaps rewrite it, #2207) is left as an exercise for the reader. --- lib/std/fmt.zig | 4 +- lib/std/fmt/parse_hex_float.zig | 352 ++++++++++++++++++++++++++++++++ 2 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 lib/std/fmt/parse_hex_float.zig diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index bfe28ef203..491a1d0726 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1506,9 +1506,11 @@ test "parseUnsigned" { } pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; +pub const parseHexFloat = @import("fmt/parse_hex_float.zig").parseHexFloat; -test "parseFloat" { +test { _ = @import("fmt/parse_float.zig"); + _ = @import("fmt/parse_hex_float.zig"); } pub fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) { diff --git a/lib/std/fmt/parse_hex_float.zig b/lib/std/fmt/parse_hex_float.zig new file mode 100644 index 0000000000..4bff267a82 --- /dev/null +++ b/lib/std/fmt/parse_hex_float.zig @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2015-2021 Zig Contributors +// This file is part of [zig](https://ziglang.org/), which is MIT licensed. +// The MIT license requires this copyright notice to be included in all copies +// and substantial portions of the software.const std = @import("std"); +// +// The rounding logic is inspired by LLVM's APFloat and Go's atofHex +// implementation. + +const std = @import("std"); +const ascii = std.ascii; +const fmt = std.fmt; +const math = std.math; +const testing = std.testing; + +const assert = std.debug.assert; + +pub fn parseHexFloat(comptime T: type, s: []const u8) !T { + assert(@typeInfo(T) == .Float); + + const IntT = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); + + const mantissa_bits = math.floatMantissaBits(T); + const exponent_bits = math.floatExponentBits(T); + + const sign_shift = mantissa_bits + exponent_bits; + + const exponent_bias = (1 << (exponent_bits - 1)) - 1; + const exponent_min = 1 - exponent_bias; + const exponent_max = exponent_bias; + + if (s.len == 0) + return error.InvalidCharacter; + + if (ascii.eqlIgnoreCase(s, "nan")) { + return math.nan(T); + } else if (ascii.eqlIgnoreCase(s, "inf") or ascii.eqlIgnoreCase(s, "+inf")) { + return math.inf(T); + } else if (ascii.eqlIgnoreCase(s, "-inf")) { + return -math.inf(T); + } + + var negative: bool = false; + var exp_negative: bool = false; + + var mantissa: u128 = 0; + var exponent: i16 = 0; + var frac_scale: i16 = 0; + + const State = enum { + MaybeSign, + Prefix, + LeadingIntegerDigit, + IntegerDigit, + MaybeDot, + LeadingFractionDigit, + FractionDigit, + ExpPrefix, + MaybeExpSign, + ExpDigit, + }; + + var state = State.MaybeSign; + + var i: usize = 0; + while (i < s.len) { + const c = s[i]; + + switch (state) { + .MaybeSign => { + state = .Prefix; + + if (c == '+') { + i += 1; + } else if (c == '-') { + negative = true; + i += 1; + } + }, + .Prefix => { + state = .LeadingIntegerDigit; + + // Match both 0x and 0X. + if (i + 2 > s.len or s[i] != '0' or s[i + 1] | 32 != 'x') + return error.InvalidCharacter; + i += 2; + }, + .LeadingIntegerDigit => { + if (c == '0') { + // Skip leading zeros. + i += 1; + } else if (c == '_') { + return error.InvalidCharacter; + } else { + state = .IntegerDigit; + } + }, + .IntegerDigit => { + if (ascii.isXDigit(c)) { + if (mantissa >= math.maxInt(u128) / 16) + return error.Overflow; + mantissa *%= 16; + mantissa += try fmt.charToDigit(c, 16); + i += 1; + } else if (c == '_') { + i += 1; + } else { + state = .MaybeDot; + } + }, + .MaybeDot => { + if (c == '.') { + state = .LeadingFractionDigit; + i += 1; + } else state = .ExpPrefix; + }, + .LeadingFractionDigit => { + if (c == '_') { + return error.InvalidCharacter; + } else state = .FractionDigit; + }, + .FractionDigit => { + if (ascii.isXDigit(c)) { + if (mantissa < math.maxInt(u128) / 16) { + mantissa *%= 16; + mantissa +%= try fmt.charToDigit(c, 16); + frac_scale += 1; + } else if (c != '0') { + return error.Overflow; + } + i += 1; + } else if (c == '_') { + i += 1; + } else { + state = .ExpPrefix; + } + }, + .ExpPrefix => { + state = .MaybeExpSign; + // Match both p and P. + if (c | 32 != 'p') + return error.InvalidCharacter; + i += 1; + }, + .MaybeExpSign => { + state = .ExpDigit; + + if (c == '+') { + i += 1; + } else if (c == '-') { + exp_negative = true; + i += 1; + } + }, + .ExpDigit => { + if (ascii.isXDigit(c)) { + if (exponent >= math.maxInt(i16) / 10) + return error.Overflow; + exponent *%= 10; + exponent +%= try fmt.charToDigit(c, 10); + i += 1; + } else if (c == '_') { + i += 1; + } else { + return error.InvalidCharacter; + } + }, + } + } + + if (exp_negative) + exponent *= -1; + + // Bring the decimal part to the left side of the decimal dot. + exponent -= frac_scale * 4; + + if (mantissa == 0) { + // Signed zero. + return if (negative) -0.0 else 0.0; + } + + // Divide by 2^mantissa_bits to right-align the mantissa in the fractional + // part. + exponent += mantissa_bits; + + // Keep around two extra bits to correctly round any value that doesn't fit + // the available mantissa bits. The result LSB serves as Guard bit, the + // following one is the Round bit and the last one is the Sticky bit, + // computed by OR-ing all the dropped bits. + + // Normalize by aligning the implicit one bit. + while (mantissa >> (mantissa_bits + 2) == 0) { + mantissa <<= 1; + exponent -= 1; + } + + // Normalize again by dropping the excess precision. + // Note that the discarded bits are folded into the Sticky bit. + while (mantissa >> (mantissa_bits + 2 + 1) != 0) { + mantissa = mantissa >> 1 | (mantissa & 1); + exponent += 1; + } + + // Very small numbers can be possibly represented as denormals, reduce the + // exponent as much as possible. + while (mantissa != 0 and exponent < exponent_min - 2) { + mantissa = mantissa >> 1 | (mantissa & 1); + exponent += 1; + } + + // There are two cases to handle: + // - We've truncated more than 0.5ULP (R=S=1), increase the mantissa. + // - We've truncated exactly 0.5ULP (R=1 S=0), increase the mantissa if the + // result is odd (G=1). + // The two checks can be neatly folded as follows. + mantissa |= @boolToInt(mantissa & 0b100 != 0); + mantissa += 1; + + mantissa >>= 2; + exponent += 2; + + if (mantissa & (1 << (mantissa_bits + 1)) != 0) { + // Renormalize, if the exponent overflows we'll catch that below. + mantissa >>= 1; + exponent += 1; + } + + if (mantissa >> mantissa_bits == 0) { + // This is a denormal number, the biased exponent is zero. + exponent = -exponent_bias; + } + + if (exponent > exponent_max) { + // Overflow, return +inf. + return math.inf(T); + } + + // Remove the implicit bit. + mantissa &= @as(u128, (1 << mantissa_bits) - 1); + + const raw: IntT = + (if (negative) @as(IntT, 1) << sign_shift else 0) | + @as(IntT, @bitCast(u16, exponent + exponent_bias)) << mantissa_bits | + @truncate(IntT, mantissa); + + return @bitCast(T, raw); +} + +test "special" { + testing.expect(math.isNan(try parseHexFloat(f32, "nAn"))); + testing.expect(math.isPositiveInf(try parseHexFloat(f32, "iNf"))); + testing.expect(math.isPositiveInf(try parseHexFloat(f32, "+Inf"))); + testing.expect(math.isNegativeInf(try parseHexFloat(f32, "-iNf"))); +} +test "zero" { + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0")); + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "-0x0")); + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0p42")); + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "-0x0.00000p42")); + testing.expectEqual(@as(f32, 0.0), try parseHexFloat(f32, "0x0.00000p666")); +} + +test "f16" { + const Case = struct { s: []const u8, v: f16 }; + const cases: []const Case = &[_]Case{ + .{ .s = "0x1p0", .v = 1.0 }, + .{ .s = "-0x1p-1", .v = -0.5 }, + .{ .s = "0x10p+10", .v = 16384.0 }, + .{ .s = "0x10p-10", .v = 0.015625 }, + // Max normalized value. + .{ .s = "0x1.ffcp+15", .v = math.f16_max }, + .{ .s = "-0x1.ffcp+15", .v = -math.f16_max }, + // Min normalized value. + .{ .s = "0x1p-14", .v = math.f16_min }, + .{ .s = "-0x1p-14", .v = -math.f16_min }, + // Min denormal value. + .{ .s = "0x1p-24", .v = math.f16_true_min }, + .{ .s = "-0x1p-24", .v = -math.f16_true_min }, + }; + + for (cases) |case| { + testing.expectEqual(case.v, try parseHexFloat(f16, case.s)); + } +} +test "f32" { + const Case = struct { s: []const u8, v: f32 }; + const cases: []const Case = &[_]Case{ + .{ .s = "0x1p0", .v = 1.0 }, + .{ .s = "-0x1p-1", .v = -0.5 }, + .{ .s = "0x10p+10", .v = 16384.0 }, + .{ .s = "0x10p-10", .v = 0.015625 }, + .{ .s = "0x0.ffffffp128", .v = 0x0.ffffffp128 }, + .{ .s = "0x0.1234570p-125", .v = 0x0.1234570p-125 }, + // Max normalized value. + .{ .s = "0x1.fffffeP+127", .v = math.f32_max }, + .{ .s = "-0x1.fffffeP+127", .v = -math.f32_max }, + // Min normalized value. + .{ .s = "0x1p-126", .v = math.f32_min }, + .{ .s = "-0x1p-126", .v = -math.f32_min }, + // Min denormal value. + .{ .s = "0x1P-149", .v = math.f32_true_min }, + .{ .s = "-0x1P-149", .v = -math.f32_true_min }, + }; + + for (cases) |case| { + testing.expectEqual(case.v, try parseHexFloat(f32, case.s)); + } +} +test "f64" { + const Case = struct { s: []const u8, v: f64 }; + const cases: []const Case = &[_]Case{ + .{ .s = "0x1p0", .v = 1.0 }, + .{ .s = "-0x1p-1", .v = -0.5 }, + .{ .s = "0x10p+10", .v = 16384.0 }, + .{ .s = "0x10p-10", .v = 0.015625 }, + // Max normalized value. + .{ .s = "0x1.fffffffffffffp+1023", .v = math.f64_max }, + .{ .s = "-0x1.fffffffffffffp1023", .v = -math.f64_max }, + // Min normalized value. + .{ .s = "0x1p-1022", .v = math.f64_min }, + .{ .s = "-0x1p-1022", .v = -math.f64_min }, + // Min denormalized value. + .{ .s = "0x1p-1074", .v = math.f64_true_min }, + .{ .s = "-0x1p-1074", .v = -math.f64_true_min }, + }; + + for (cases) |case| { + testing.expectEqual(case.v, try parseHexFloat(f64, case.s)); + } +} +test "f128" { + const Case = struct { s: []const u8, v: f128 }; + const cases: []const Case = &[_]Case{ + .{ .s = "0x1p0", .v = 1.0 }, + .{ .s = "-0x1p-1", .v = -0.5 }, + .{ .s = "0x10p+10", .v = 16384.0 }, + .{ .s = "0x10p-10", .v = 0.015625 }, + // Max normalized value. + .{ .s = "0xf.fffffffffffffffffffffffffff8p+16380", .v = math.f128_max }, + .{ .s = "-0xf.fffffffffffffffffffffffffff8p+16380", .v = -math.f128_max }, + // Min normalized value. + .{ .s = "0x1p-16382", .v = math.f128_min }, + .{ .s = "-0x1p-16382", .v = -math.f128_min }, + // // Min denormalized value. + .{ .s = "0x1p-16494", .v = math.f128_true_min }, + .{ .s = "-0x1p-16494", .v = -math.f128_true_min }, + }; + + for (cases) |case| { + testing.expectEqual(@bitCast(u128, case.v), @bitCast(u128, try parseHexFloat(f128, case.s))); + } +}