diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 919f45e730..3d103d6d06 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -289,8 +289,6 @@ pub const TypeInfo = union(enum) { /// therefore must be kept in sync with the compiler implementation. pub const Error = struct { name: []const u8, - /// This field is ignored when using @Type(). - value: comptime_int, }; /// This data structure is used by the Zig language code generation and diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 08a34075b0..ed1ddb7d91 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -11,6 +11,7 @@ const macho = std.macho; usingnamespace @import("../os/bits.zig"); extern "c" fn __error() *c_int; +pub extern "c" fn NSVersionOfRunTimeLibrary(library_name: [*:0]const u8) u32; pub extern "c" fn _NSGetExecutablePath(buf: [*]u8, bufsize: *u32) c_int; pub extern "c" fn _dyld_image_count() u32; pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header; diff --git a/lib/std/cache_hash.zig b/lib/std/cache_hash.zig index edd0ebf126..5cd8194e21 100644 --- a/lib/std/cache_hash.zig +++ b/lib/std/cache_hash.zig @@ -4,7 +4,8 @@ // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. const std = @import("std.zig"); -const Blake3 = std.crypto.hash.Blake3; +const crypto = std.crypto; +const Hasher = crypto.auth.siphash.SipHash128(1, 3); // provides enough collision resistance for the CacheHash use cases, while being one of our fastest options right now const fs = std.fs; const base64 = std.base64; const ArrayList = std.ArrayList; @@ -16,9 +17,8 @@ const Allocator = std.mem.Allocator; const base64_encoder = fs.base64_encoder; const base64_decoder = fs.base64_decoder; -/// This is 70 more bits than UUIDs. For an analysis of probability of collisions, see: -/// https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions -const BIN_DIGEST_LEN = 24; +/// This is 128 bits - Even with 2^54 cache entries, the probably of a collision would be under 10^-6 +const BIN_DIGEST_LEN = 16; const BASE64_DIGEST_LEN = base64.Base64Encoder.calcSize(BIN_DIGEST_LEN); const MANIFEST_FILE_SIZE_MAX = 50 * 1024 * 1024; @@ -43,9 +43,13 @@ pub const File = struct { } }; +/// CacheHash manages project-local `zig-cache` directories. +/// This is not a general-purpose cache. +/// It was designed to be fast and simple, not to withstand attacks using specially-crafted input. pub const CacheHash = struct { allocator: *Allocator, - blake3: Blake3, + hasher_init: Hasher, // initial state, that can be copied + hasher: Hasher, // current state for incremental hashing manifest_dir: fs.Dir, manifest_file: ?fs.File, manifest_dirty: bool, @@ -54,9 +58,11 @@ pub const CacheHash = struct { /// Be sure to call release after successful initialization. pub fn init(allocator: *Allocator, dir: fs.Dir, manifest_dir_path: []const u8) !CacheHash { + const hasher_init = Hasher.init(&[_]u8{0} ** Hasher.minimum_key_length); return CacheHash{ .allocator = allocator, - .blake3 = Blake3.init(.{}), + .hasher_init = hasher_init, + .hasher = hasher_init, .manifest_dir = try dir.makeOpenPath(manifest_dir_path, .{}), .manifest_file = null, .manifest_dirty = false, @@ -69,8 +75,8 @@ pub const CacheHash = struct { pub fn addSlice(self: *CacheHash, val: []const u8) void { assert(self.manifest_file == null); - self.blake3.update(val); - self.blake3.update(&[_]u8{0}); + self.hasher.update(val); + self.hasher.update(&[_]u8{0}); } /// Convert the input value into bytes and record it as a dependency of the @@ -133,12 +139,12 @@ pub const CacheHash = struct { assert(self.manifest_file == null); var bin_digest: [BIN_DIGEST_LEN]u8 = undefined; - self.blake3.final(&bin_digest); + self.hasher.final(&bin_digest); base64_encoder.encode(self.b64_digest[0..], &bin_digest); - self.blake3 = Blake3.init(.{}); - self.blake3.update(&bin_digest); + self.hasher = self.hasher_init; + self.hasher.update(&bin_digest); const manifest_file_path = try fmt.allocPrint(self.allocator, "{}.txt", .{self.b64_digest}); defer self.allocator.free(manifest_file_path); @@ -238,7 +244,7 @@ pub const CacheHash = struct { } var actual_digest: [BIN_DIGEST_LEN]u8 = undefined; - try hashFile(this_file, &actual_digest); + try hashFile(this_file, &actual_digest, self.hasher_init); if (!mem.eql(u8, &cache_hash_file.bin_digest, &actual_digest)) { cache_hash_file.bin_digest = actual_digest; @@ -248,7 +254,7 @@ pub const CacheHash = struct { } if (!any_file_changed) { - self.blake3.update(&cache_hash_file.bin_digest); + self.hasher.update(&cache_hash_file.bin_digest); } } @@ -256,8 +262,8 @@ pub const CacheHash = struct { // cache miss // keep the manifest file open // reset the hash - self.blake3 = Blake3.init(.{}); - self.blake3.update(&bin_digest); + self.hasher = self.hasher_init; + self.hasher.update(&bin_digest); // Remove files not in the initial hash for (self.files.items[input_file_count..]) |*file| { @@ -266,7 +272,7 @@ pub const CacheHash = struct { self.files.shrink(input_file_count); for (self.files.items) |file| { - self.blake3.update(&file.bin_digest); + self.hasher.update(&file.bin_digest); } return null; } @@ -304,23 +310,23 @@ pub const CacheHash = struct { // Hash while reading from disk, to keep the contents in the cpu cache while // doing hashing. - var blake3 = Blake3.init(.{}); + var hasher = self.hasher_init; var off: usize = 0; while (true) { // give me everything you've got, captain const bytes_read = try file.read(contents[off..]); if (bytes_read == 0) break; - blake3.update(contents[off..][0..bytes_read]); + hasher.update(contents[off..][0..bytes_read]); off += bytes_read; } - blake3.final(&ch_file.bin_digest); + hasher.final(&ch_file.bin_digest); ch_file.contents = contents; } else { - try hashFile(file, &ch_file.bin_digest); + try hashFile(file, &ch_file.bin_digest, self.hasher_init); } - self.blake3.update(&ch_file.bin_digest); + self.hasher.update(&ch_file.bin_digest); } /// Add a file as a dependency of process being cached, after the initial hash has been @@ -382,7 +388,7 @@ pub const CacheHash = struct { // the artifacts to cache. var bin_digest: [BIN_DIGEST_LEN]u8 = undefined; - self.blake3.final(&bin_digest); + self.hasher.final(&bin_digest); var out_digest: [BASE64_DIGEST_LEN]u8 = undefined; base64_encoder.encode(&out_digest, &bin_digest); @@ -433,17 +439,17 @@ pub const CacheHash = struct { } }; -fn hashFile(file: fs.File, bin_digest: []u8) !void { - var blake3 = Blake3.init(.{}); +fn hashFile(file: fs.File, bin_digest: []u8, hasher_init: anytype) !void { var buf: [1024]u8 = undefined; + var hasher = hasher_init; while (true) { const bytes_read = try file.read(&buf); if (bytes_read == 0) break; - blake3.update(buf[0..bytes_read]); + hasher.update(buf[0..bytes_read]); } - blake3.final(bin_digest); + hasher.final(bin_digest); } /// If the wall clock time, rounded to the same precision as the @@ -507,7 +513,7 @@ test "cache file and then recall it" { _ = try ch.addFile(temp_file, null); // There should be nothing in the cache - testing.expectEqual(@as(?[32]u8, null), try ch.hit()); + testing.expectEqual(@as(?[BASE64_DIGEST_LEN]u8, null), try ch.hit()); digest1 = ch.final(); } @@ -575,7 +581,7 @@ test "check that changing a file makes cache fail" { const temp_file_idx = try ch.addFile(temp_file, 100); // There should be nothing in the cache - testing.expectEqual(@as(?[32]u8, null), try ch.hit()); + testing.expectEqual(@as(?[BASE64_DIGEST_LEN]u8, null), try ch.hit()); testing.expect(mem.eql(u8, original_temp_file_contents, ch.files.items[temp_file_idx].contents.?)); @@ -592,7 +598,7 @@ test "check that changing a file makes cache fail" { const temp_file_idx = try ch.addFile(temp_file, 100); // A file that we depend on has been updated, so the cache should not contain an entry for it - testing.expectEqual(@as(?[32]u8, null), try ch.hit()); + testing.expectEqual(@as(?[BASE64_DIGEST_LEN]u8, null), try ch.hit()); // The cache system does not keep the contents of re-hashed input files. testing.expect(ch.files.items[temp_file_idx].contents == null); @@ -625,7 +631,7 @@ test "no file inputs" { ch.add("1234"); // There should be nothing in the cache - testing.expectEqual(@as(?[32]u8, null), try ch.hit()); + testing.expectEqual(@as(?[BASE64_DIGEST_LEN]u8, null), try ch.hit()); digest1 = ch.final(); } @@ -672,7 +678,7 @@ test "CacheHashes with files added after initial hash work" { _ = try ch.addFile(temp_file1, null); // There should be nothing in the cache - testing.expectEqual(@as(?[32]u8, null), try ch.hit()); + testing.expectEqual(@as(?[BASE64_DIGEST_LEN]u8, null), try ch.hit()); _ = try ch.addFilePost(temp_file2); @@ -705,7 +711,7 @@ test "CacheHashes with files added after initial hash work" { _ = try ch.addFile(temp_file1, null); // A file that we depend on has been updated, so the cache should not contain an entry for it - testing.expectEqual(@as(?[32]u8, null), try ch.hit()); + testing.expectEqual(@as(?[BASE64_DIGEST_LEN]u8, null), try ch.hit()); _ = try ch.addFilePost(temp_file2); diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 32ab5071c5..5de2f13896 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -18,6 +18,7 @@ pub const hash = struct { /// Authentication (MAC) functions. pub const auth = struct { pub const hmac = @import("crypto/hmac.zig"); + pub const siphash = @import("crypto/siphash.zig"); }; /// Authenticated Encryption with Associated Data @@ -80,6 +81,7 @@ test "crypto" { _ = @import("crypto/sha1.zig"); _ = @import("crypto/sha2.zig"); _ = @import("crypto/sha3.zig"); + _ = @import("crypto/siphash.zig"); _ = @import("crypto/25519/curve25519.zig"); _ = @import("crypto/25519/ed25519.zig"); _ = @import("crypto/25519/edwards25519.zig"); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 70bd30d6bb..d9c83992c9 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -60,6 +60,10 @@ const macs = [_]Crypto{ Crypto{ .ty = crypto.auth.hmac.HmacSha1, .name = "hmac-sha1" }, Crypto{ .ty = crypto.auth.hmac.sha2.HmacSha256, .name = "hmac-sha256" }, Crypto{ .ty = crypto.auth.hmac.sha2.HmacSha512, .name = "hmac-sha512" }, + Crypto{ .ty = crypto.auth.siphash.SipHash64(2, 4), .name = "siphash-2-4" }, + Crypto{ .ty = crypto.auth.siphash.SipHash64(1, 3), .name = "siphash-1-3" }, + Crypto{ .ty = crypto.auth.siphash.SipHash128(2, 4), .name = "siphash128-2-4" }, + Crypto{ .ty = crypto.auth.siphash.SipHash128(1, 3), .name = "siphash128-1-3" }, }; pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 { diff --git a/lib/std/hash/siphash.zig b/lib/std/crypto/siphash.zig similarity index 79% rename from lib/std/hash/siphash.zig rename to lib/std/crypto/siphash.zig index 107ea19728..26c892fdd7 100644 --- a/lib/std/hash/siphash.zig +++ b/lib/std/crypto/siphash.zig @@ -3,25 +3,44 @@ // 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. -// Siphash // -// SipHash is a moderately fast, non-cryptographic keyed hash function designed for resistance -// against hash flooding DoS attacks. +// SipHash is a moderately fast pseudorandom function, returning a 64-bit or 128-bit tag for an arbitrary long input. +// +// Typical use cases include: +// - protection against against DoS attacks for hash tables and bloom filters +// - authentication of short-lived messages in online protocols // // https://131002.net/siphash/ - const std = @import("../std.zig"); const assert = std.debug.assert; const testing = std.testing; const math = std.math; const mem = std.mem; -const Endian = std.builtin.Endian; - +/// SipHash function with 64-bit output. +/// +/// Recommended parameters are: +/// - (c_rounds=4, d_rounds=8) for conservative security; regular hash functions such as BLAKE2 or BLAKE3 are usually a better alternative. +/// - (c_rounds=2, d_rounds=4) standard parameters. +/// - (c_rounds=1, d_rounds=3) reduced-round function. Faster, no known implications on its practical security level. +/// - (c_rounds=1, d_rounds=2) fastest option, but the output may be distinguishable from random data with related keys or non-uniform input - not suitable as a PRF. +/// +/// SipHash is not a traditional hash function. If the input includes untrusted content, a secret key is absolutely necessary. +/// And due to its small output size, collisions in SipHash64 can be found with an exhaustive search. pub fn SipHash64(comptime c_rounds: usize, comptime d_rounds: usize) type { return SipHash(u64, c_rounds, d_rounds); } +/// SipHash function with 128-bit output. +/// +/// Recommended parameters are: +/// - (c_rounds=4, d_rounds=8) for conservative security; regular hash functions such as BLAKE2 or BLAKE3 are usually a better alternative. +/// - (c_rounds=2, d_rounds=4) standard parameters. +/// - (c_rounds=1, d_rounds=4) reduced-round function. Recommended to hash very short, similar strings, when a 128-bit PRF output is still required. +/// - (c_rounds=1, d_rounds=3) reduced-round function. Faster, no known implications on its practical security level. +/// - (c_rounds=1, d_rounds=2) fastest option, but the output may be distinguishable from random data with related keys or non-uniform input - not suitable as a PRF. +/// +/// SipHash is not a traditional hash function. If the input includes untrusted content, a secret key is absolutely necessary. pub fn SipHash128(comptime c_rounds: usize, comptime d_rounds: usize) type { return SipHash(u128, c_rounds, d_rounds); } @@ -146,30 +165,31 @@ fn SipHashStateless(comptime T: type, comptime c_rounds: usize, comptime d_round d.v2 = math.rotl(u64, d.v2, @as(u64, 32)); } - pub fn hash(key: []const u8, input: []const u8) T { - const aligned_len = input.len - (input.len % 8); - + pub fn hash(msg: []const u8, key: []const u8) T { + const aligned_len = msg.len - (msg.len % 8); var c = Self.init(key); - @call(.{ .modifier = .always_inline }, c.update, .{input[0..aligned_len]}); - return @call(.{ .modifier = .always_inline }, c.final, .{input[aligned_len..]}); + @call(.{ .modifier = .always_inline }, c.update, .{msg[0..aligned_len]}); + return @call(.{ .modifier = .always_inline }, c.final, .{msg[aligned_len..]}); } }; } -pub fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) type { +fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: usize) type { assert(T == u64 or T == u128); assert(c_rounds > 0 and d_rounds > 0); return struct { const State = SipHashStateless(T, c_rounds, d_rounds); const Self = @This(); - const digest_size = 64; - const block_size = 64; + pub const minimum_key_length = 16; + pub const mac_length = @sizeOf(T); + pub const block_length = 8; state: State, buf: [8]u8, buf_len: usize, + /// Initialize a state for a SipHash function pub fn init(key: []const u8) Self { return Self{ .state = State.init(key), @@ -178,6 +198,7 @@ pub fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: us }; } + /// Add data to the state pub fn update(self: *Self, b: []const u8) void { var off: usize = 0; @@ -196,12 +217,27 @@ pub fn SipHash(comptime T: type, comptime c_rounds: usize, comptime d_rounds: us self.buf_len += @intCast(u8, b[off + aligned_len ..].len); } - pub fn final(self: *Self) T { + /// Return an authentication tag for the current state + pub fn final(self: *Self, out: []u8) void { + std.debug.assert(out.len >= mac_length); + mem.writeIntLittle(T, out[0..mac_length], self.state.final(self.buf[0..self.buf_len])); + } + + /// Return an authentication tag for a message and a key + pub fn create(out: []u8, msg: []const u8, key: []const u8) void { + var ctx = Self.init(key); + ctx.update(msg); + ctx.final(out[0..]); + } + + /// Return an authentication tag for the current state, as an integer + pub fn finalInt(self: *Self) T { return self.state.final(self.buf[0..self.buf_len]); } - pub fn hash(key: []const u8, input: []const u8) T { - return State.hash(key, input); + /// Return an authentication tag for a message and a key, as an integer + pub fn toInt(msg: []const u8, key: []const u8) T { + return State.hash(msg, key); } }; } @@ -284,8 +320,9 @@ test "siphash64-2-4 sanity" { for (vectors) |vector, i| { buffer[i] = @intCast(u8, i); - const expected = mem.readIntLittle(u64, &vector); - testing.expectEqual(siphash.hash(test_key, buffer[0..i]), expected); + var out: [siphash.mac_length]u8 = undefined; + siphash.create(&out, buffer[0..i], test_key); + testing.expectEqual(out, vector); } } @@ -363,8 +400,9 @@ test "siphash128-2-4 sanity" { for (vectors) |vector, i| { buffer[i] = @intCast(u8, i); - const expected = mem.readIntLittle(u128, &vector); - testing.expectEqual(siphash.hash(test_key, buffer[0..i]), expected); + var out: [siphash.mac_length]u8 = undefined; + siphash.create(&out, buffer[0..i], test_key[0..]); + testing.expectEqual(out, vector); } } @@ -379,14 +417,14 @@ test "iterative non-divisible update" { var end: usize = 9; while (end < buf.len) : (end += 9) { - const non_iterative_hash = Siphash.hash(key, buf[0..end]); + const non_iterative_hash = Siphash.toInt(buf[0..end], key[0..]); - var wy = Siphash.init(key); + var siphash = Siphash.init(key); var i: usize = 0; while (i < end) : (i += 7) { - wy.update(buf[i..std.math.min(i + 7, end)]); + siphash.update(buf[i..std.math.min(i + 7, end)]); } - const iterative_hash = wy.final(); + const iterative_hash = siphash.finalInt(); std.testing.expectEqual(iterative_hash, non_iterative_hash); } diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 79303e7163..cd2b5fcd06 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -976,6 +976,9 @@ pub const EM = extern enum(u16) { /// MIPS RS3000 Little-endian _MIPS_RS3_LE = 10, + /// SPU Mark II + _SPU_2 = 13, + /// Hewlett-Packard PA-RISC _PARISC = 15, diff --git a/lib/std/fs.zig b/lib/std/fs.zig index df368e070b..21a00eeb1d 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -686,21 +686,28 @@ pub const Dir = struct { return self.openFileW(path_w.span(), flags); } + var os_flags: u32 = os.O_CLOEXEC; // Use the O_ locking flags if the os supports them // (Or if it's darwin, as darwin's `open` doesn't support the O_SYNC flag) const has_flock_open_flags = @hasDecl(os, "O_EXLOCK") and !is_darwin; - const nonblocking_lock_flag = if (has_flock_open_flags and flags.lock_nonblocking) - os.O_NONBLOCK | os.O_SYNC - else - @as(u32, 0); - const lock_flag: u32 = if (has_flock_open_flags) switch (flags.lock) { - .None => @as(u32, 0), - .Shared => os.O_SHLOCK | nonblocking_lock_flag, - .Exclusive => os.O_EXLOCK | nonblocking_lock_flag, - } else 0; - - const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; - const os_flags = lock_flag | O_LARGEFILE | os.O_CLOEXEC | if (flags.write and flags.read) + if (has_flock_open_flags) { + const nonblocking_lock_flag = if (flags.lock_nonblocking) + os.O_NONBLOCK | os.O_SYNC + else + @as(u32, 0); + os_flags |= switch (flags.lock) { + .None => @as(u32, 0), + .Shared => os.O_SHLOCK | nonblocking_lock_flag, + .Exclusive => os.O_EXLOCK | nonblocking_lock_flag, + }; + } + if (@hasDecl(os, "O_LARGEFILE")) { + os_flags |= os.O_LARGEFILE; + } + if (!flags.allow_ctty) { + os_flags |= os.O_NOCTTY; + } + os_flags |= if (flags.write and flags.read) @as(u32, os.O_RDWR) else if (flags.write) @as(u32, os.O_WRONLY) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 3e22528ea9..6fb2385a85 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -101,6 +101,10 @@ pub const File = struct { /// if `std.io.is_async`. It allows the use of `nosuspend` when calling functions /// related to opening the file, reading, writing, and locking. intended_io_mode: io.ModeOverride = io.default_mode, + + /// Set this to allow the opened file to automatically become the + /// controlling TTY for the current process. + allow_ctty: bool = false, }; /// TODO https://github.com/ziglang/zig/issues/3802 diff --git a/lib/std/hash.zig b/lib/std/hash.zig index 1cc078959c..7bac378316 100644 --- a/lib/std/hash.zig +++ b/lib/std/hash.zig @@ -20,7 +20,7 @@ pub const Fnv1a_32 = fnv.Fnv1a_32; pub const Fnv1a_64 = fnv.Fnv1a_64; pub const Fnv1a_128 = fnv.Fnv1a_128; -const siphash = @import("hash/siphash.zig"); +const siphash = @import("crypto/siphash.zig"); pub const SipHash64 = siphash.SipHash64; pub const SipHash128 = siphash.SipHash128; @@ -42,7 +42,6 @@ test "hash" { _ = @import("hash/auto_hash.zig"); _ = @import("hash/crc.zig"); _ = @import("hash/fnv.zig"); - _ = @import("hash/siphash.zig"); _ = @import("hash/murmur.zig"); _ = @import("hash/cityhash.zig"); _ = @import("hash/wyhash.zig"); diff --git a/lib/std/hash/benchmark.zig b/lib/std/hash/benchmark.zig index c23743160b..f0cafa9971 100644 --- a/lib/std/hash/benchmark.zig +++ b/lib/std/hash/benchmark.zig @@ -25,24 +25,12 @@ const Hash = struct { init_u64: ?u64 = null, }; -const siphash_key = "0123456789abcdef"; - const hashes = [_]Hash{ Hash{ .ty = hash.Wyhash, .name = "wyhash", .init_u64 = 0, }, - Hash{ - .ty = hash.SipHash64(1, 3), - .name = "siphash(1,3)", - .init_u8s = siphash_key, - }, - Hash{ - .ty = hash.SipHash64(2, 4), - .name = "siphash(2,4)", - .init_u8s = siphash_key, - }, Hash{ .ty = hash.Fnv1a_64, .name = "fnv1a", diff --git a/lib/std/heap/general_purpose_allocator.zig b/lib/std/heap/general_purpose_allocator.zig index 5d8de5845d..ba710059aa 100644 --- a/lib/std/heap/general_purpose_allocator.zig +++ b/lib/std/heap/general_purpose_allocator.zig @@ -433,8 +433,7 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { const bucket_slice = @ptrCast([*]align(@alignOf(BucketHeader)) u8, bucket)[0..bucket_size]; self.backing_allocator.free(bucket_slice); } else { - // TODO Set the slot data to undefined. - // Related: https://github.com/ziglang/zig/issues/4298 + @memset(bucket.page + slot_index * size_class, undefined, size_class); } } @@ -567,6 +566,9 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type { const new_aligned_size = math.max(new_size, old_align); const new_size_class = math.ceilPowerOfTwoAssert(usize, new_aligned_size); if (new_size_class <= size_class) { + if (old_mem.len > new_size) { + @memset(old_mem.ptr + new_size, undefined, old_mem.len - new_size); + } return new_size; } return error.OutOfMemory; diff --git a/lib/std/linked_list.zig b/lib/std/linked_list.zig index 00e678e0ac..870b823aac 100644 --- a/lib/std/linked_list.zig +++ b/lib/std/linked_list.zig @@ -28,12 +28,6 @@ pub fn SinglyLinkedList(comptime T: type) type { pub const Data = T; - pub fn init(data: T) Node { - return Node{ - .data = data, - }; - } - /// Insert a new node after the current one. /// /// Arguments: @@ -175,12 +169,6 @@ pub fn TailQueue(comptime T: type) type { prev: ?*Node = null, next: ?*Node = null, data: T, - - pub fn init(data: T) Node { - return Node{ - .data = data, - }; - } }; first: ?*Node = null, diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 99a8cd776c..4057d3dc99 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -40,6 +40,24 @@ pub const uuid_command = extern struct { uuid: [16]u8, }; +/// The entry_point_command is a replacement for thread_command. +/// It is used for main executables to specify the location (file offset) +/// of main(). If -stack_size was used at link time, the stacksize +/// field will contain the stack size needed for the main thread. +pub const entry_point_command = struct { + /// LC_MAIN only used in MH_EXECUTE filetypes + cmd: u32, + + /// sizeof(struct entry_point_command) + cmdsize: u32, + + /// file (__TEXT) offset of main() + entryoff: u64, + + /// if not zero, initial stack size + stacksize: u64, +}; + /// The symtab_command contains the offsets and sizes of the link-edit 4.3BSD /// "stab" style symbol table information as described in the header files /// and . @@ -65,7 +83,7 @@ pub const symtab_command = extern struct { /// The linkedit_data_command contains the offsets and sizes of a blob /// of data in the __LINKEDIT segment. -const linkedit_data_command = extern struct { +pub const linkedit_data_command = extern struct { /// LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, LC_DYLIB_CODE_SIGN_DRS or LC_LINKER_OPTIMIZATION_HINT. cmd: u32, @@ -79,6 +97,65 @@ const linkedit_data_command = extern struct { datasize: u32, }; +/// A program that uses a dynamic linker contains a dylinker_command to identify +/// the name of the dynamic linker (LC_LOAD_DYLINKER). And a dynamic linker +/// contains a dylinker_command to identify the dynamic linker (LC_ID_DYLINKER). +/// A file can have at most one of these. +/// This struct is also used for the LC_DYLD_ENVIRONMENT load command and contains +/// string for dyld to treat like an environment variable. +pub const dylinker_command = extern struct { + /// LC_ID_DYLINKER, LC_LOAD_DYLINKER, or LC_DYLD_ENVIRONMENT + cmd: u32, + + /// includes pathname string + cmdsize: u32, + + /// A variable length string in a load command is represented by an lc_str + /// union. The strings are stored just after the load command structure and + /// the offset is from the start of the load command structure. The size + /// of the string is reflected in the cmdsize field of the load command. + /// Once again any padded bytes to bring the cmdsize field to a multiple + /// of 4 bytes must be zero. + name: u32, +}; + +/// A dynamically linked shared library (filetype == MH_DYLIB in the mach header) +/// contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library. +/// An object that uses a dynamically linked shared library also contains a +/// dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or +/// LC_REEXPORT_DYLIB) for each library it uses. +pub const dylib_command = extern struct { + /// LC_ID_DYLIB, LC_LOAD_WEAK_DYLIB, LC_LOAD_DYLIB, LC_REEXPORT_DYLIB + cmd: u32, + + /// includes pathname string + cmdsize: u32, + + /// the library identification + dylib: dylib, +}; + +/// Dynamicaly linked shared libraries are identified by two things. The +/// pathname (the name of the library as found for execution), and the +/// compatibility version number. The pathname must match and the compatibility +/// number in the user of the library must be greater than or equal to the +/// library being used. The time stamp is used to record the time a library was +/// built and copied into user so it can be use to determined if the library used +/// at runtime is exactly the same as used to built the program. +pub const dylib = extern struct { + /// library's pathname (offset pointing at the end of dylib_command) + name: u32, + + /// library's build timestamp + timestamp: u32, + + /// library's current version number + current_version: u32, + + /// library's compatibility version number + compatibility_version: u32, +}; + /// The segment load command indicates that a part of this file is to be /// mapped into the task's address space. The size of this segment in memory, /// vmsize, maybe equal to or larger than the amount to map from this file, diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig index 133b903110..1327eaa330 100644 --- a/lib/std/os/bits/linux.zig +++ b/lib/std/os/bits/linux.zig @@ -24,7 +24,6 @@ pub usingnamespace switch (builtin.arch) { }; pub usingnamespace @import("linux/netlink.zig"); -pub const BPF = @import("linux/bpf.zig"); const is_mips = builtin.arch.isMIPS(); diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index c5edacfab1..13094b3a3a 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -29,6 +29,7 @@ pub usingnamespace switch (builtin.arch) { }; pub usingnamespace @import("bits.zig"); pub const tls = @import("linux/tls.zig"); +pub const BPF = @import("linux/bpf.zig"); /// Set by startup code, used by `getauxval`. pub var elf_aux_maybe: ?[*]std.elf.Auxv = null; diff --git a/lib/std/os/bits/linux/bpf.zig b/lib/std/os/linux/bpf.zig similarity index 99% rename from lib/std/os/bits/linux/bpf.zig rename to lib/std/os/linux/bpf.zig index 5517e2ae80..928c157c42 100644 --- a/lib/std/os/bits/linux/bpf.zig +++ b/lib/std/os/linux/bpf.zig @@ -4,10 +4,8 @@ // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. usingnamespace std.os; -const std = @import("../../../std.zig"); +const std = @import("../../std.zig"); const expectEqual = std.testing.expectEqual; -const fd_t = std.os.fd_t; -const pid_t = std.os.pid_t; // instruction classes pub const LD = 0x00; diff --git a/lib/std/special/init-exe/build.zig b/lib/std/special/init-exe/build.zig index db818cbe92..fd71588c5f 100644 --- a/lib/std/special/init-exe/build.zig +++ b/lib/std/special/init-exe/build.zig @@ -1,8 +1,3 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 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 Builder = @import("std").build.Builder; pub fn build(b: *Builder) void { diff --git a/lib/std/special/init-exe/src/main.zig b/lib/std/special/init-exe/src/main.zig index e4b2e7d8ec..d29869ff88 100644 --- a/lib/std/special/init-exe/src/main.zig +++ b/lib/std/special/init-exe/src/main.zig @@ -1,8 +1,3 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 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"); pub fn main() anyerror!void { diff --git a/lib/std/special/init-lib/build.zig b/lib/std/special/init-lib/build.zig index edc8a0215d..558e447c15 100644 --- a/lib/std/special/init-lib/build.zig +++ b/lib/std/special/init-lib/build.zig @@ -1,8 +1,3 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 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 Builder = @import("std").build.Builder; pub fn build(b: *Builder) void { diff --git a/lib/std/special/init-lib/src/main.zig b/lib/std/special/init-lib/src/main.zig index 9a82bcc26b..747bb08573 100644 --- a/lib/std/special/init-lib/src/main.zig +++ b/lib/std/special/init-lib/src/main.zig @@ -1,8 +1,3 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2015-2020 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"); const testing = std.testing; diff --git a/lib/std/target.zig b/lib/std/target.zig index 3cc8897d51..3c206516b1 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -96,8 +96,12 @@ pub const Target = struct { win10_rs4 = 0x0A000005, win10_rs5 = 0x0A000006, win10_19h1 = 0x0A000007, + win10_20h1 = 0x0A000008, _, + /// Latest Windows version that the Zig Standard Library is aware of + pub const latest = WindowsVersion.win10_20h1; + pub const Range = struct { min: WindowsVersion, max: WindowsVersion, @@ -124,18 +128,17 @@ pub const Target = struct { out_stream: anytype, ) !void { if (fmt.len > 0 and fmt[0] == 's') { - if (@enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.win10_19h1)) { + if (@enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.latest)) { try std.fmt.format(out_stream, ".{}", .{@tagName(self)}); } else { - try std.fmt.format(out_stream, "@intToEnum(Target.Os.WindowsVersion, {})", .{@enumToInt(self)}); + // TODO this code path breaks zig triples, but it is used in `builtin` + try std.fmt.format(out_stream, "@intToEnum(Target.Os.WindowsVersion, 0x{X:0>8})", .{@enumToInt(self)}); } } else { - if (@enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.win10_19h1)) { + if (@enumToInt(self) >= @enumToInt(WindowsVersion.nt4) and @enumToInt(self) <= @enumToInt(WindowsVersion.latest)) { try std.fmt.format(out_stream, "WindowsVersion.{}", .{@tagName(self)}); } else { - try std.fmt.format(out_stream, "WindowsVersion(", .{@typeName(@This())}); - try std.fmt.format(out_stream, "{}", .{@enumToInt(self)}); - try out_stream.writeAll(")"); + try std.fmt.format(out_stream, "WindowsVersion(0x{X:0>8})", .{@enumToInt(self)}); } } } @@ -280,7 +283,7 @@ pub const Target = struct { .windows => return .{ .windows = .{ .min = .win8_1, - .max = .win10_19h1, + .max = WindowsVersion.latest, }, }, } @@ -663,6 +666,9 @@ pub const Target = struct { renderscript32, renderscript64, ve, + // Stage1 currently assumes that architectures above this comment + // map one-to-one with the ZigLLVM_ArchType enum. + spu_2, pub fn isARM(arch: Arch) bool { return switch (arch) { @@ -761,6 +767,7 @@ pub const Target = struct { .sparcv9 => ._SPARCV9, .s390x => ._S390, .ve => ._NONE, + .spu_2 => ._SPU_2, }; } @@ -803,6 +810,7 @@ pub const Target = struct { .renderscript64, .shave, .ve, + .spu_2, => .Little, .arc, @@ -827,6 +835,7 @@ pub const Target = struct { switch (arch) { .avr, .msp430, + .spu_2, => return 16, .arc, @@ -1317,12 +1326,13 @@ pub const Target = struct { .bpfeb, .nvptx, .nvptx64, + .spu_2, + .avr, => return result, // TODO go over each item in this list and either move it to the above list, or // implement the standard dynamic linker path code for it. .arc, - .avr, .hexagon, .msp430, .r600, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 175b0e4555..d24e8a57e9 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -249,7 +249,7 @@ pub const NativeTargetInfo = struct { // values const known_build_numbers = [_]u32{ 10240, 10586, 14393, 15063, 16299, 17134, 17763, - 18362, 18363, + 18362, 19041, }; var last_idx: usize = 0; for (known_build_numbers) |build, i| { diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index c0d1d0d654..82029c1e9f 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -80,6 +80,9 @@ deletion_set: std.ArrayListUnmanaged(*Decl) = .{}, root_name: []u8, keep_source_files_loaded: bool, +/// Error tags and their values, tag names are duped with mod.gpa. +global_error_set: std.StringHashMapUnmanaged(u16) = .{}, + pub const InnerError = error{ OutOfMemory, AnalysisFail }; const WorkItem = union(enum) { @@ -928,6 +931,11 @@ pub fn deinit(self: *Module) void { self.symbol_exports.deinit(gpa); self.root_scope.destroy(gpa); + + for (self.global_error_set.items()) |entry| { + gpa.free(entry.key); + } + self.global_error_set.deinit(gpa); self.* = undefined; } @@ -2072,6 +2080,18 @@ fn createNewDecl( return new_decl; } +/// Get error value for error tag `name`. +pub fn getErrorValue(self: *Module, name: []const u8) !std.StringHashMapUnmanaged(u16).Entry { + const gop = try self.global_error_set.getOrPut(self.gpa, name); + if (gop.found_existing) + return gop.entry.*; + errdefer self.global_error_set.removeAssertDiscard(name); + + gop.entry.key = try self.gpa.dupe(u8, name); + gop.entry.value = @intCast(u16, self.global_error_set.items().len - 1); + return gop.entry.*; +} + /// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites. pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { return scope.cast(Scope.Block) orelse @@ -3309,6 +3329,28 @@ pub fn arrayType(self: *Module, scope: *Scope, len: u64, sentinel: ?Value, elem_ return Type.initPayload(&payload.base); } +pub fn errorUnionType(self: *Module, scope: *Scope, error_set: Type, payload: Type) Allocator.Error!Type { + assert(error_set.zigTypeTag() == .ErrorSet); + if (error_set.eql(Type.initTag(.anyerror)) and payload.eql(Type.initTag(.void))) { + return Type.initTag(.anyerror_void_error_union); + } + + const result = try scope.arena().create(Type.Payload.ErrorUnion); + result.* = .{ + .error_set = error_set, + .payload = payload, + }; + return Type.initPayload(&result.base); +} + +pub fn anyframeType(self: *Module, scope: *Scope, return_type: Type) Allocator.Error!Type { + const result = try scope.arena().create(Type.Payload.AnyFrame); + result.* = .{ + .return_type = return_type, + }; + return Type.initPayload(&result.base); +} + pub fn dumpInst(self: *Module, scope: *Scope, inst: *Inst) void { const zir_module = scope.namespace(); const source = zir_module.getSource(self) catch @panic("dumpInst failed to get source"); diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 8276b191cb..0d8e0dc874 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -18,9 +18,7 @@ pub const ResultLoc = union(enum) { /// The expression has an inferred type, and it will be evaluated as an rvalue. none, /// The expression must generate a pointer rather than a value. For example, the left hand side - /// of an assignment uses an "LValue" result location. - lvalue, - /// The expression must generate a pointer + /// of an assignment uses this kind of result location. ref, /// The expression will be type coerced into this type, but it will be evaluated as an rvalue. ty: *zir.Inst, @@ -46,134 +44,136 @@ pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*z return expr(mod, scope, type_rl, type_node); } +fn lvalExpr(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst { + switch (node.tag) { + .Root => unreachable, + .Use => unreachable, + .TestDecl => unreachable, + .DocComment => unreachable, + .VarDecl => unreachable, + .SwitchCase => unreachable, + .SwitchElse => unreachable, + .Else => unreachable, + .Payload => unreachable, + .PointerPayload => unreachable, + .PointerIndexPayload => unreachable, + .ErrorTag => unreachable, + .FieldInitializer => unreachable, + .ContainerField => unreachable, + + .Assign, + .AssignBitAnd, + .AssignBitOr, + .AssignBitShiftLeft, + .AssignBitShiftRight, + .AssignBitXor, + .AssignDiv, + .AssignSub, + .AssignSubWrap, + .AssignMod, + .AssignAdd, + .AssignAddWrap, + .AssignMul, + .AssignMulWrap, + .Add, + .AddWrap, + .Sub, + .SubWrap, + .Mul, + .MulWrap, + .Div, + .Mod, + .BitAnd, + .BitOr, + .BitShiftLeft, + .BitShiftRight, + .BitXor, + .BangEqual, + .EqualEqual, + .GreaterThan, + .GreaterOrEqual, + .LessThan, + .LessOrEqual, + .ArrayCat, + .ArrayMult, + .BoolAnd, + .BoolOr, + .Asm, + .StringLiteral, + .IntegerLiteral, + .Call, + .Unreachable, + .Return, + .If, + .While, + .BoolNot, + .AddressOf, + .FloatLiteral, + .UndefinedLiteral, + .BoolLiteral, + .NullLiteral, + .OptionalType, + .Block, + .LabeledBlock, + .Break, + .PtrType, + .GroupedExpression, + .ArrayType, + .ArrayTypeSentinel, + .EnumLiteral, + .MultilineStringLiteral, + .CharLiteral, + .Defer, + .Catch, + .ErrorUnion, + .MergeErrorSets, + .Range, + .OrElse, + .Await, + .BitNot, + .Negation, + .NegationWrap, + .Resume, + .Try, + .SliceType, + .Slice, + .ArrayInitializer, + .ArrayInitializerDot, + .StructInitializer, + .StructInitializerDot, + .Switch, + .For, + .Suspend, + .Continue, + .AnyType, + .ErrorType, + .FnProto, + .AnyFrameType, + .ErrorSetDecl, + .ContainerDecl, + .Comptime, + .Nosuspend, + => return mod.failNode(scope, node, "invalid left-hand side to assignment", .{}), + + // @field can be assigned to + .BuiltinCall => { + const call = node.castTag(.BuiltinCall).?; + const tree = scope.tree(); + const builtin_name = tree.tokenSlice(call.builtin_token); + + if (!mem.eql(u8, builtin_name, "@field")) { + return mod.failNode(scope, node, "invalid left-hand side to assignment", .{}); + } + }, + + // can be assigned to + .UnwrapOptional, .Deref, .Period, .ArrayAccess, .Identifier => {}, + } + return expr(mod, scope, .ref, node); +} + /// Turn Zig AST into untyped ZIR istructions. pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerError!*zir.Inst { - if (rl == .lvalue) { - switch (node.tag) { - .Root => unreachable, - .Use => unreachable, - .TestDecl => unreachable, - .DocComment => unreachable, - .VarDecl => unreachable, - .SwitchCase => unreachable, - .SwitchElse => unreachable, - .Else => unreachable, - .Payload => unreachable, - .PointerPayload => unreachable, - .PointerIndexPayload => unreachable, - .ErrorTag => unreachable, - .FieldInitializer => unreachable, - .ContainerField => unreachable, - - .Assign, - .AssignBitAnd, - .AssignBitOr, - .AssignBitShiftLeft, - .AssignBitShiftRight, - .AssignBitXor, - .AssignDiv, - .AssignSub, - .AssignSubWrap, - .AssignMod, - .AssignAdd, - .AssignAddWrap, - .AssignMul, - .AssignMulWrap, - .Add, - .AddWrap, - .Sub, - .SubWrap, - .Mul, - .MulWrap, - .Div, - .Mod, - .BitAnd, - .BitOr, - .BitShiftLeft, - .BitShiftRight, - .BitXor, - .BangEqual, - .EqualEqual, - .GreaterThan, - .GreaterOrEqual, - .LessThan, - .LessOrEqual, - .ArrayCat, - .ArrayMult, - .BoolAnd, - .BoolOr, - .Asm, - .StringLiteral, - .IntegerLiteral, - .Call, - .Unreachable, - .Return, - .If, - .While, - .BoolNot, - .AddressOf, - .FloatLiteral, - .UndefinedLiteral, - .BoolLiteral, - .NullLiteral, - .OptionalType, - .Block, - .LabeledBlock, - .Break, - .PtrType, - .GroupedExpression, - .ArrayType, - .ArrayTypeSentinel, - .EnumLiteral, - .MultilineStringLiteral, - .CharLiteral, - .Defer, - .Catch, - .ErrorUnion, - .MergeErrorSets, - .Range, - .OrElse, - .Await, - .BitNot, - .Negation, - .NegationWrap, - .Resume, - .Try, - .SliceType, - .Slice, - .ArrayInitializer, - .ArrayInitializerDot, - .StructInitializer, - .StructInitializerDot, - .Switch, - .For, - .Suspend, - .Continue, - .AnyType, - .ErrorType, - .FnProto, - .AnyFrameType, - .ErrorSetDecl, - .ContainerDecl, - .Comptime, - .Nosuspend, - => return mod.failNode(scope, node, "invalid left-hand side to assignment", .{}), - - // @field can be assigned to - .BuiltinCall => { - const call = node.castTag(.BuiltinCall).?; - const tree = scope.tree(); - const builtin_name = tree.tokenSlice(call.builtin_token); - - if (!mem.eql(u8, builtin_name, "@field")) { - return mod.failNode(scope, node, "invalid left-hand side to assignment", .{}); - } - }, - - // can be assigned to - .UnwrapOptional, .Deref, .Period, .ArrayAccess, .Identifier => {}, - } - } switch (node.tag) { .Root => unreachable, // Top-level declaration. .Use => unreachable, // Top-level declaration. @@ -232,6 +232,11 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .BoolAnd => return boolBinOp(mod, scope, rl, node.castTag(.BoolAnd).?), .BoolOr => return boolBinOp(mod, scope, rl, node.castTag(.BoolOr).?), + .BoolNot => return rlWrap(mod, scope, rl, try boolNot(mod, scope, node.castTag(.BoolNot).?)), + .BitNot => return rlWrap(mod, scope, rl, try bitNot(mod, scope, node.castTag(.BitNot).?)), + .Negation => return rlWrap(mod, scope, rl, try negation(mod, scope, node.castTag(.Negation).?, .sub)), + .NegationWrap => return rlWrap(mod, scope, rl, try negation(mod, scope, node.castTag(.NegationWrap).?, .subwrap)), + .Identifier => return try identifier(mod, scope, rl, node.castTag(.Identifier).?), .Asm => return rlWrap(mod, scope, rl, try assembly(mod, scope, node.castTag(.Asm).?)), .StringLiteral => return rlWrap(mod, scope, rl, try stringLiteral(mod, scope, node.castTag(.StringLiteral).?)), @@ -242,9 +247,8 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .Return => return ret(mod, scope, node.castTag(.Return).?), .If => return ifExpr(mod, scope, rl, node.castTag(.If).?), .While => return whileExpr(mod, scope, rl, node.castTag(.While).?), - .Period => return rlWrap(mod, scope, rl, try field(mod, scope, node.castTag(.Period).?)), + .Period => return field(mod, scope, rl, node.castTag(.Period).?), .Deref => return rlWrap(mod, scope, rl, try deref(mod, scope, node.castTag(.Deref).?)), - .BoolNot => return rlWrap(mod, scope, rl, try boolNot(mod, scope, node.castTag(.BoolNot).?)), .AddressOf => return rlWrap(mod, scope, rl, try addressOf(mod, scope, node.castTag(.AddressOf).?)), .FloatLiteral => return rlWrap(mod, scope, rl, try floatLiteral(mod, scope, node.castTag(.FloatLiteral).?)), .UndefinedLiteral => return rlWrap(mod, scope, rl, try undefLiteral(mod, scope, node.castTag(.UndefinedLiteral).?)), @@ -263,17 +267,17 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .MultilineStringLiteral => return rlWrap(mod, scope, rl, try multilineStrLiteral(mod, scope, node.castTag(.MultilineStringLiteral).?)), .CharLiteral => return rlWrap(mod, scope, rl, try charLiteral(mod, scope, node.castTag(.CharLiteral).?)), .SliceType => return rlWrap(mod, scope, rl, try sliceType(mod, scope, node.castTag(.SliceType).?)), + .ErrorUnion => return rlWrap(mod, scope, rl, try typeInixOp(mod, scope, node.castTag(.ErrorUnion).?, .error_union_type)), + .MergeErrorSets => return rlWrap(mod, scope, rl, try typeInixOp(mod, scope, node.castTag(.MergeErrorSets).?, .merge_error_sets)), + .AnyFrameType => return rlWrap(mod, scope, rl, try anyFrameType(mod, scope, node.castTag(.AnyFrameType).?)), + .ErrorSetDecl => return errorSetDecl(mod, scope, rl, node.castTag(.ErrorSetDecl).?), + .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)), .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), .Catch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Catch", .{}), - .ErrorUnion => return mod.failNode(scope, node, "TODO implement astgen.expr for .ErrorUnion", .{}), - .MergeErrorSets => return mod.failNode(scope, node, "TODO implement astgen.expr for .MergeErrorSets", .{}), .Range => return mod.failNode(scope, node, "TODO implement astgen.expr for .Range", .{}), .OrElse => return mod.failNode(scope, node, "TODO implement astgen.expr for .OrElse", .{}), .Await => return mod.failNode(scope, node, "TODO implement astgen.expr for .Await", .{}), - .BitNot => return mod.failNode(scope, node, "TODO implement astgen.expr for .BitNot", .{}), - .Negation => return mod.failNode(scope, node, "TODO implement astgen.expr for .Negation", .{}), - .NegationWrap => return mod.failNode(scope, node, "TODO implement astgen.expr for .NegationWrap", .{}), .Resume => return mod.failNode(scope, node, "TODO implement astgen.expr for .Resume", .{}), .Try => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}), .Slice => return mod.failNode(scope, node, "TODO implement astgen.expr for .Slice", .{}), @@ -287,10 +291,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .Suspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Suspend", .{}), .Continue => return mod.failNode(scope, node, "TODO implement astgen.expr for .Continue", .{}), .AnyType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyType", .{}), - .ErrorType => return mod.failNode(scope, node, "TODO implement astgen.expr for .ErrorType", .{}), .FnProto => return mod.failNode(scope, node, "TODO implement astgen.expr for .FnProto", .{}), - .AnyFrameType => return mod.failNode(scope, node, "TODO implement astgen.expr for .AnyFrameType", .{}), - .ErrorSetDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ErrorSetDecl", .{}), .ContainerDecl => return mod.failNode(scope, node, "TODO implement astgen.expr for .ContainerDecl", .{}), .Comptime => return mod.failNode(scope, node, "TODO implement astgen.expr for .Comptime", .{}), .Nosuspend => return mod.failNode(scope, node, "TODO implement astgen.expr for .Nosuspend", .{}), @@ -316,7 +317,7 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpr // proper type inference requires peer type resolution on the block's // break operand expressions. const branch_rl: ResultLoc = switch (label.result_loc) { - .discard, .none, .ty, .ptr, .lvalue, .ref => label.result_loc, + .discard, .none, .ty, .ptr, .ref => label.result_loc, .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = label.block_inst }, }; const operand = try expr(mod, parent_scope, branch_rl, rhs); @@ -458,7 +459,9 @@ fn varDecl( const tree = scope.tree(); const name_src = tree.token_locs[node.name_token].start; const ident_name = try identifierTokenString(mod, scope, node.name_token); - const init_node = node.getTrailer("init_node").?; + const init_node = node.getTrailer("init_node") orelse + return mod.fail(scope, name_src, "variables must be initialized", .{}); + switch (tree.token_ids[node.mut_token]) { .Keyword_const => { // Depending on the type of AST the initialization expression is, we may need an lvalue @@ -521,7 +524,7 @@ fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) Inne return; } } - const lvalue = try expr(mod, scope, .lvalue, infix_node.lhs); + const lvalue = try lvalExpr(mod, scope, infix_node.lhs); _ = try expr(mod, scope, .{ .ptr = lvalue }, infix_node.rhs); } @@ -531,7 +534,7 @@ fn assignOp( infix_node: *ast.Node.SimpleInfixOp, op_inst_tag: zir.Inst.Tag, ) InnerError!void { - const lhs_ptr = try expr(mod, scope, .lvalue, infix_node.lhs); + const lhs_ptr = try lvalExpr(mod, scope, infix_node.lhs); const lhs = try addZIRUnOp(mod, scope, lhs_ptr.src, .deref, lhs_ptr); const lhs_type = try addZIRUnOp(mod, scope, lhs_ptr.src, .typeof, lhs); const rhs = try expr(mod, scope, .{ .ty = lhs_type }, infix_node.rhs); @@ -554,6 +557,26 @@ fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerErr return addZIRUnOp(mod, scope, src, .boolnot, operand); } +fn bitNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + const operand = try expr(mod, scope, .none, node.rhs); + return addZIRUnOp(mod, scope, src, .bitnot, operand); +} + +fn negation(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp, op_inst_tag: zir.Inst.Tag) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + + const lhs = try addZIRInstConst(mod, scope, src, .{ + .ty = Type.initTag(.comptime_int), + .val = Value.initTag(.zero), + }); + const rhs = try expr(mod, scope, .none, node.rhs); + + return addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs); +} + fn addressOf(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { return expr(mod, scope, .ref, node.rhs); } @@ -561,11 +584,7 @@ fn addressOf(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerE fn optionalType(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); - const operand = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + const operand = try typeExpr(mod, scope, node.rhs); return addZIRUnOp(mod, scope, src, .optional_type, operand); } @@ -590,18 +609,13 @@ fn ptrType(mod: *Module, scope: *Scope, node: *ast.Node.PtrType) InnerError!*zir } fn ptrSliceType(mod: *Module, scope: *Scope, src: usize, ptr_info: *ast.PtrInfo, rhs: *ast.Node, size: std.builtin.TypeInfo.Pointer.Size) InnerError!*zir.Inst { - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); - const simple = ptr_info.allowzero_token == null and ptr_info.align_info == null and ptr_info.volatile_token == null and ptr_info.sentinel == null; if (simple) { - const child_type = try expr(mod, scope, .{ .ty = meta_type }, rhs); + const child_type = try typeExpr(mod, scope, rhs); const mutable = ptr_info.const_token == null; // TODO stage1 type inference bug const T = zir.Inst.Tag; @@ -629,7 +643,7 @@ fn ptrSliceType(mod: *Module, scope: *Scope, src: usize, ptr_info: *ast.PtrInfo, kw_args.sentinel = try expr(mod, scope, .none, some); } - const child_type = try expr(mod, scope, .{ .ty = meta_type }, rhs); + const child_type = try typeExpr(mod, scope, rhs); if (kw_args.sentinel) |some| { kw_args.sentinel = try addZIRBinOp(mod, scope, some.src, .as, child_type, some); } @@ -640,10 +654,6 @@ fn ptrSliceType(mod: *Module, scope: *Scope, src: usize, ptr_info: *ast.PtrInfo, fn arrayType(mod: *Module, scope: *Scope, node: *ast.Node.ArrayType) !*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); const usize_type = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.usize_type), @@ -651,18 +661,14 @@ fn arrayType(mod: *Module, scope: *Scope, node: *ast.Node.ArrayType) !*zir.Inst // TODO check for [_]T const len = try expr(mod, scope, .{ .ty = usize_type }, node.len_expr); - const child_type = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + const elem_type = try typeExpr(mod, scope, node.rhs); - return addZIRBinOp(mod, scope, src, .array_type, len, child_type); + return addZIRBinOp(mod, scope, src, .array_type, len, elem_type); } fn arrayTypeSentinel(mod: *Module, scope: *Scope, node: *ast.Node.ArrayTypeSentinel) !*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const meta_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); const usize_type = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.usize_type), @@ -671,7 +677,7 @@ fn arrayTypeSentinel(mod: *Module, scope: *Scope, node: *ast.Node.ArrayTypeSenti // TODO check for [_]T const len = try expr(mod, scope, .{ .ty = usize_type }, node.len_expr); const sentinel_uncasted = try expr(mod, scope, .none, node.sentinel); - const elem_type = try expr(mod, scope, .{ .ty = meta_type }, node.rhs); + const elem_type = try typeExpr(mod, scope, node.rhs); const sentinel = try addZIRBinOp(mod, scope, src, .as, elem_type, sentinel_uncasted); return addZIRInst(mod, scope, src, zir.Inst.ArrayTypeSentinel, .{ @@ -681,6 +687,28 @@ fn arrayTypeSentinel(mod: *Module, scope: *Scope, node: *ast.Node.ArrayTypeSenti }, .{}); } +fn anyFrameType(mod: *Module, scope: *Scope, node: *ast.Node.AnyFrameType) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.anyframe_token].start; + if (node.result) |some| { + const return_type = try typeExpr(mod, scope, some.return_type); + return addZIRUnOp(mod, scope, src, .anyframe_type, return_type); + } else { + return addZIRInstConst(mod, scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.anyframe_type), + }); + } +} + +fn typeInixOp(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp, op_inst_tag: zir.Inst.Tag) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + const error_set = try typeExpr(mod, scope, node.lhs); + const payload = try typeExpr(mod, scope, node.rhs); + return addZIRBinOp(mod, scope, src, op_inst_tag, error_set, payload); +} + fn enumLiteral(mod: *Module, scope: *Scope, node: *ast.Node.EnumLiteral) !*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.name].start; @@ -694,10 +722,31 @@ fn unwrapOptional(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Si const src = tree.token_locs[node.rtoken].start; const operand = try expr(mod, scope, .ref, node.lhs); - const unwrapped_ptr = try addZIRUnOp(mod, scope, src, .unwrap_optional_safe, operand); - if (rl == .lvalue or rl == .ref) return unwrapped_ptr; + return rlWrapPtr(mod, scope, rl, try addZIRUnOp(mod, scope, src, .unwrap_optional_safe, operand)); +} - return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, unwrapped_ptr)); +fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.ErrorSetDecl) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.error_token].start; + const decls = node.decls(); + const fields = try scope.arena().alloc([]const u8, decls.len); + + for (decls) |decl, i| { + const tag = decl.castTag(.ErrorTag).?; + fields[i] = try identifierTokenString(mod, scope, tag.name_token); + } + + // analyzing the error set results in a decl ref, so we might need to dereference it + return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{})); +} + +fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.token].start; + return addZIRInstConst(mod, scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.anyerror_type), + }); } /// Return whether the identifier names of two tokens are equal. Resolves @"" tokens without allocating. @@ -737,16 +786,16 @@ pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToke return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{}); } -fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { - // TODO introduce lvalues +fn field(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const lhs = try expr(mod, scope, .none, node.lhs); + const lhs = try expr(mod, scope, .ref, node.lhs); const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?); const pointer = try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{}); - return addZIRUnOp(mod, scope, src, .deref, pointer); + if (rl == .ref) return pointer; + return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, pointer)); } fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { @@ -971,7 +1020,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn // proper type inference requires peer type resolution on the if's // branches. const branch_rl: ResultLoc = switch (rl) { - .discard, .none, .ty, .ptr, .lvalue, .ref => rl, + .discard, .none, .ty, .ptr, .ref => rl, .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, }; @@ -1101,7 +1150,7 @@ fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.W // proper type inference requires peer type resolution on the while's // branches. const branch_rl: ResultLoc = switch (rl) { - .discard, .none, .ty, .ptr, .lvalue, .ref => rl, + .discard, .none, .ty, .ptr, .ref => rl, .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = while_block }, }; @@ -1232,12 +1281,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo .local_ptr => { const local_ptr = s.cast(Scope.LocalPtr).?; if (mem.eql(u8, local_ptr.name, ident_name)) { - if (rl == .lvalue or rl == .ref) { - return local_ptr.ptr; - } else { - const result = try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr); - return rlWrap(mod, scope, rl, result); - } + return rlWrapPtr(mod, scope, rl, local_ptr.ptr); } s = local_ptr.parent; }, @@ -1247,10 +1291,7 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo } if (mod.lookupDeclName(scope, ident_name)) |decl| { - const result = try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); - if (rl == .lvalue or rl == .ref) - return result; - return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, src, .deref, result)); + return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{})); } return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name}); @@ -1466,12 +1507,8 @@ fn simpleCast( try ensureBuiltinParamCount(mod, scope, call, 2); const tree = scope.tree(); const src = tree.token_locs[call.builtin_token].start; - const type_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); const params = call.params(); - const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]); + const dest_type = try typeExpr(mod, scope, params[0]); const rhs = try expr(mod, scope, .none, params[1]); const result = try addZIRBinOp(mod, scope, src, inst_tag, dest_type, rhs); return rlWrap(mod, scope, rl, result); @@ -1498,7 +1535,6 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); return result; }, - .lvalue => unreachable, .ref => { const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); return addZIRUnOp(mod, scope, result.src, .ref, result); @@ -1533,12 +1569,8 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa try ensureBuiltinParamCount(mod, scope, call, 2); const tree = scope.tree(); const src = tree.token_locs[call.builtin_token].start; - const type_type = try addZIRInstConst(mod, scope, src, .{ - .ty = Type.initTag(.type), - .val = Value.initTag(.type_type), - }); const params = call.params(); - const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]); + const dest_type = try typeExpr(mod, scope, params[0]); switch (rl) { .none => { const operand = try expr(mod, scope, .none, params[1]); @@ -1550,7 +1582,6 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); return result; }, - .lvalue => unreachable, .ref => { const operand = try expr(mod, scope, .ref, params[1]); const result = try addZIRBinOp(mod, scope, src, .bitcast_ref, dest_type, operand); @@ -1818,7 +1849,7 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); return result; }, - .lvalue, .ref => { + .ref => { // We need a pointer but we have a value. return addZIRUnOp(mod, scope, result.src, .ref, result); }, @@ -1852,6 +1883,12 @@ fn rlWrapVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, resul return rlWrap(mod, scope, rl, void_inst); } +fn rlWrapPtr(mod: *Module, scope: *Scope, rl: ResultLoc, ptr: *zir.Inst) InnerError!*zir.Inst { + if (rl == .ref) return ptr; + + return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, ptr.src, .deref, ptr)); +} + pub fn addZIRInstSpecial( mod: *Module, scope: *Scope, diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 36bebe1ca5..cb12211206 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -14,6 +14,7 @@ const Allocator = mem.Allocator; const trace = @import("tracy.zig").trace; const DW = std.dwarf; const leb128 = std.debug.leb; +const log = std.log.scoped(.codegen); // TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. // zig fmt: off @@ -75,8 +76,8 @@ pub fn generateSymbol( switch (bin_file.options.target.cpu.arch) { .wasm32 => unreachable, // has its own code path .wasm64 => unreachable, // has its own code path - //.arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), - //.armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + .arm => return Function(.arm).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + .armeb => return Function(.armeb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.aarch64 => return Function(.aarch64).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.aarch64_be => return Function(.aarch64_be).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.aarch64_32 => return Function(.aarch64_32).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), @@ -101,6 +102,7 @@ pub fn generateSymbol( //.sparcv9 => return Function(.sparcv9).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.sparcel => return Function(.sparcel).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.s390x => return Function(.s390x).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), + .spu_2 => return Function(.spu_2).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.tce => return Function(.tce).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.tcele => return Function(.tcele).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), //.thumb => return Function(.thumb).generateSymbol(bin_file, src, typed_value, code, dbg_line, dbg_info, dbg_info_type_relocs), @@ -344,6 +346,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const Branch = struct { inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, + /// The key must be canonical register. registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{}, free_registers: FreeRegInt = math.maxInt(FreeRegInt), @@ -381,9 +384,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); const reg = callee_preserved_regs[free_index]; self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); + log.debug("alloc {} => {*}", .{reg, inst}); return reg; } + /// Does not track the register. + fn findUnusedReg(self: *Branch) ?Register { + const free_index = @ctz(FreeRegInt, self.free_registers); + if (free_index >= callee_preserved_regs.len) { + return null; + } + return callee_preserved_regs[free_index]; + } + fn deinit(self: *Branch, gpa: *Allocator) void { self.inst_table.deinit(gpa); self.registers.deinit(gpa); @@ -570,8 +583,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; const inst_table = &branch.inst_table; for (body.instructions) |inst| { - const new_inst = try self.genFuncInst(inst); - try inst_table.putNoClobber(self.gpa, inst, new_inst); + const mcv = try self.genFuncInst(inst); + log.debug("{*} => {}", .{inst, mcv}); + // TODO don't put void or dead things in here + try inst_table.putNoClobber(self.gpa, inst, mcv); var i: ir.Inst.DeathsBitIndex = 0; while (inst.getOperand(i)) |operand| : (i += 1) { @@ -714,7 +729,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.allocMem(inst, abi_size, abi_align); } - fn allocRegOrMem(self: *Self, inst: *ir.Inst) !MCValue { + fn allocRegOrMem(self: *Self, inst: *ir.Inst, reg_ok: bool) !MCValue { const elem_ty = inst.ty; const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch { return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty}); @@ -724,30 +739,73 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { self.stack_align = abi_align; const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; - // Make sure the type can fit in a register before we try to allocate one. - const ptr_bits = arch.ptrBitWidth(); - const ptr_bytes: u64 = @divExact(ptr_bits, 8); - if (abi_size <= ptr_bytes) { - try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); - if (branch.allocReg(inst)) |reg| { - return MCValue{ .register = registerAlias(reg, abi_size) }; + if (reg_ok) { + // Make sure the type can fit in a register before we try to allocate one. + const ptr_bits = arch.ptrBitWidth(); + const ptr_bytes: u64 = @divExact(ptr_bits, 8); + if (abi_size <= ptr_bytes) { + try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); + if (branch.allocReg(inst)) |reg| { + return MCValue{ .register = registerAlias(reg, abi_size) }; + } } } const stack_offset = try self.allocMem(inst, abi_size, abi_align); return MCValue{ .stack_offset = stack_offset }; } - /// Does not "move" the instruction. - fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { + /// Copies a value to a register without tracking the register. The register is not considered + /// allocated. A second call to `copyToTmpRegister` may return the same register. + /// This can have a side effect of spilling instructions to the stack to free up a register. + fn copyToTmpRegister(self: *Self, src: usize, mcv: MCValue) !Register { + const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; + + const reg = branch.findUnusedReg() 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 = branch.registers.remove(reg).?; + const spilled_inst = regs_entry.value.inst; + + const stack_mcv = try self.allocRegOrMem(spilled_inst, false); + const inst_entry = branch.inst_table.getEntry(spilled_inst).?; + const reg_mcv = inst_entry.value; + assert(reg == toCanonicalReg(reg_mcv.register)); + inst_entry.value = stack_mcv; + try self.genSetStack(src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv); + + break :b reg; + }; + try self.genSetReg(src, reg, mcv); + return reg; + } + + /// Allocates a new register and copies `mcv` into it. + /// `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 { const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); - const reg = branch.allocReg(inst) orelse - return self.fail(inst.src, "TODO implement spilling register to stack", .{}); - const old_mcv = branch.inst_table.get(inst).?; - const new_mcv: MCValue = .{ .register = reg }; - try self.genSetReg(inst.src, reg, old_mcv); - return new_mcv; + const reg = branch.allocReg(reg_owner) 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 = branch.registers.getEntry(reg).?; + const spilled_inst = regs_entry.value.inst; + regs_entry.value = .{ .inst = reg_owner }; + + const stack_mcv = try self.allocRegOrMem(spilled_inst, false); + const inst_entry = branch.inst_table.getEntry(spilled_inst).?; + const reg_mcv = inst_entry.value; + assert(reg == toCanonicalReg(reg_mcv.register)); + inst_entry.value = stack_mcv; + try self.genSetStack(reg_owner.src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv); + + break :b reg; + }; + try self.genSetReg(reg_owner.src, reg, mcv); + return MCValue{ .register = reg }; } fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue { @@ -868,13 +926,30 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } - fn reuseOperand(inst: *ir.Inst, op_index: ir.Inst.DeathsBitIndex, mcv: MCValue) bool { - if (!inst.operandDies(op_index) or !mcv.isMutable()) + fn reuseOperand(self: *Self, inst: *ir.Inst, op_index: ir.Inst.DeathsBitIndex, mcv: MCValue) bool { + if (!inst.operandDies(op_index)) return false; - // OK we're going to do it, but we need to clear the operand death bit so that - // it stays allocated. + switch (mcv) { + .register => |reg| { + // If it's in the registers table, need to associate the register with the + // new instruction. + const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; + if (branch.registers.getEntry(toCanonicalReg(reg))) |entry| { + entry.value = .{ .inst = inst }; + } + log.debug("reusing {} => {*}", .{reg, inst}); + }, + .stack_offset => |off| { + log.debug("reusing stack offset {} => {*}", .{off, inst}); + return true; + }, + else => return false, + } + + // Prevent the operand deaths processing code from deallocating it. inst.clearOperandDeath(op_index); + return true; } @@ -887,11 +962,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (inst.base.isUnused() and !is_volatile) return MCValue.dead; const dst_mcv: MCValue = blk: { - if (reuseOperand(&inst.base, 0, ptr)) { + if (self.reuseOperand(&inst.base, 0, ptr)) { // The MCValue that holds the pointer can be re-used as the value. break :blk ptr; } else { - break :blk try self.allocRegOrMem(&inst.base); + break :blk try self.allocRegOrMem(&inst.base, true); } }; switch (ptr) { @@ -985,23 +1060,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { var dst_mcv: MCValue = undefined; var src_mcv: MCValue = undefined; var src_inst: *ir.Inst = undefined; - if (reuseOperand(inst, 0, lhs)) { + if (self.reuseOperand(inst, 0, lhs)) { // LHS dies; use it as the destination. // Both operands cannot be memory. src_inst = op_rhs; if (lhs.isMemory() and rhs.isMemory()) { - dst_mcv = try self.copyToNewRegister(op_lhs); + dst_mcv = try self.copyToNewRegister(inst, lhs); src_mcv = rhs; } else { dst_mcv = lhs; src_mcv = rhs; } - } else if (reuseOperand(inst, 1, rhs)) { + } else if (self.reuseOperand(inst, 1, rhs)) { // RHS dies; use it as the destination. // Both operands cannot be memory. src_inst = op_lhs; if (lhs.isMemory() and rhs.isMemory()) { - dst_mcv = try self.copyToNewRegister(op_rhs); + dst_mcv = try self.copyToNewRegister(inst, rhs); src_mcv = lhs; } else { dst_mcv = rhs; @@ -1009,11 +1084,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } } else { if (lhs.isMemory()) { - dst_mcv = try self.copyToNewRegister(op_lhs); + dst_mcv = try self.copyToNewRegister(inst, lhs); src_mcv = rhs; src_inst = op_rhs; } else { - dst_mcv = try self.copyToNewRegister(op_rhs); + dst_mcv = try self.copyToNewRegister(inst, rhs); src_mcv = lhs; src_inst = op_lhs; } @@ -1026,18 +1101,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (src_mcv) { .immediate => |imm| { if (imm > math.maxInt(u31)) { - src_mcv = try self.copyToNewRegister(src_inst); + src_mcv = MCValue{ .register = try self.copyToTmpRegister(src_inst.src, src_mcv) }; } }, else => {}, } - try self.genX8664BinMathCode(inst.src, dst_mcv, src_mcv, opx, mr); + try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, opx, mr); return dst_mcv; } - fn genX8664BinMathCode(self: *Self, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void { + fn genX8664BinMathCode( + self: *Self, + src: usize, + dst_ty: Type, + dst_mcv: MCValue, + src_mcv: MCValue, + opx: u8, + mr: u8, + ) !void { switch (dst_mcv) { .none => unreachable, .undef => unreachable, @@ -1087,12 +1170,60 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, } }, - .embedded_in_code, .memory, .stack_offset => { + .stack_offset => |off| { + switch (src_mcv) { + .none => unreachable, + .undef => return self.genSetStack(src, dst_ty, off, .undef), + .dead, .unreach => unreachable, + .ptr_stack_offset => unreachable, + .ptr_embedded_in_code => unreachable, + .register => |src_reg| { + try self.genX8664ModRMRegToStack(src, dst_ty, off, src_reg, mr + 0x1); + }, + .immediate => |imm| { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source immediate", .{}); + }, + .embedded_in_code, .memory, .stack_offset => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{}); + }, + .compare_flags_unsigned => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{}); + }, + .compare_flags_signed => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{}); + }, + } + }, + .embedded_in_code, .memory => { return self.fail(src, "TODO implement x86 ADD/SUB/CMP destination memory", .{}); }, } } + fn genX8664ModRMRegToStack(self: *Self, src: usize, ty: Type, off: u32, reg: Register, opcode: u8) !void { + const abi_size = ty.abiSize(self.target.*); + const adj_off = off + abi_size; + try self.code.ensureCapacity(self.code.items.len + 7); + self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); + const reg_id: u8 = @truncate(u3, reg.id()); + if (adj_off <= 128) { + // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx + const RM = @as(u8, 0b01_000_101) | (reg_id << 3); + const negative_offset = @intCast(i8, -@intCast(i32, adj_off)); + const twos_comp = @bitCast(u8, negative_offset); + self.code.appendSliceAssumeCapacity(&[_]u8{ opcode, RM, twos_comp }); + } else if (adj_off <= 2147483648) { + // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx + const RM = @as(u8, 0b10_000_101) | (reg_id << 3); + const negative_offset = @intCast(i32, -@intCast(i33, adj_off)); + const twos_comp = @bitCast(u32, negative_offset); + self.code.appendSliceAssumeCapacity(&[_]u8{ opcode, RM }); + mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); + } else { + return self.fail(src, "stack offset too large", .{}); + } + } + fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue { if (FreeRegInt == u0) { return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch}); @@ -1109,7 +1240,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1]; switch (result) { .register => |reg| { - branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = &inst.base }); + branch.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), .{ .inst = &inst.base }); branch.markRegUsed(reg); try self.dbg_info.ensureCapacity(self.dbg_info.items.len + 8 + name_with_null.len); @@ -1134,6 +1265,17 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .riscv64 => { mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ebreak.toU32()); }, + .spu_2 => { + try self.code.resize(self.code.items.len + 2); + var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined1 }; + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr)); + }, + .arm => { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bkpt(0).toU32()); + }, + .armeb => { + mem.writeIntBig(u32, try self.code.addManyAsArray(4), Instruction.bkpt(0).toU32()); + }, else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}), } return .none; @@ -1219,10 +1361,77 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); } }, + .spu_2 => { + if (inst.func.cast(ir.Inst.Constant)) |func_inst| { + if (info.args.len != 0) { + return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{}); + } + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const func = func_val.func; + const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; + const got_addr = @intCast(u16, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * 2); + const return_type = func.owner_decl.typed_value.most_recent.typed_value.ty.fnReturnType(); + // First, push the return address, then jump; if noreturn, don't bother with the first step + // TODO: implement packed struct -> u16 at comptime and move the bitcast here + var instr = Instruction{ .condition = .always, .input0 = .immediate, .input1 = .zero, .modify_flags = false, .output = .jump, .command = .load16 }; + if (return_type.zigTypeTag() == .NoReturn) { + try self.code.resize(self.code.items.len + 4); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 4 ..][0..2], @bitCast(u16, instr)); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], got_addr); + return MCValue.unreach; + } else { + try self.code.resize(self.code.items.len + 8); + var push = Instruction{ .condition = .always, .input0 = .immediate, .input1 = .zero, .modify_flags = false, .output = .push, .command = .ipget }; + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 8 ..][0..2], @bitCast(u16, push)); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 6 ..][0..2], @as(u16, 4)); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 4 ..][0..2], @bitCast(u16, instr)); + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], got_addr); + switch (return_type.zigTypeTag()) { + .Void => return MCValue{ .none = {} }, + .NoReturn => unreachable, + else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}), + } + } + } else { + return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); + } + } else { + return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); + } + }, + .arm => { + if (info.args.len > 0) return self.fail(inst.base.src, "TODO implement fn args for {}", .{self.target.cpu.arch}); + + if (inst.func.cast(ir.Inst.Constant)) |func_inst| { + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const func = func_val.func; + const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; + const ptr_bits = self.target.cpu.arch.ptrBitWidth(); + const ptr_bytes: u64 = @divExact(ptr_bits, 8); + const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.elf.offset_table_index * ptr_bytes); + + // TODO only works with leaf functions + // at the moment, which works fine for + // Hello World, but not for real code + // of course. Add pushing lr to stack + // and popping after call + try self.genSetReg(inst.base.src, .lr, .{ .memory = got_addr }); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.blx(.al, .lr).toU32()); + } else { + return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); + } + } else { + return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); + } + }, else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), } } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { - return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO", .{}); + switch (arch) { + .x86_64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for x86_64 arch", .{}), + .aarch64 => return self.fail(inst.base.src, "TODO implement codegen for call when linking with MachO for aarch64 arch", .{}), + else => unreachable, + } } else { unreachable; } @@ -1275,6 +1484,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .riscv64 => { mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.jalr(.zero, 0, .ra).toU32()); }, + .arm => { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.bx(.al, .lr).toU32()); + }, else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), } return .unreach; @@ -1304,13 +1516,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // Either one, but not both, can be a memory operand. // Source operand can be an immediate, 8 bits or 32 bits. const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory())) - try self.copyToNewRegister(inst.lhs) + try self.copyToNewRegister(&inst.base, lhs) else lhs; // This instruction supports only signed 32-bit immediates at most. const src_mcv = try self.limitImmediateType(inst.rhs, i32); - try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38); + try self.genX8664BinMathCode(inst.base.src, inst.base.ty, dst_mcv, src_mcv, 7, 0x38); const info = inst.lhs.ty.intInfo(self.target.*); if (info.signed) { return MCValue{ .compare_flags_signed = op }; @@ -1512,6 +1724,49 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { if (!inst.is_volatile and inst.base.isUnused()) return MCValue.dead; switch (arch) { + .spu_2 => { + if (inst.inputs.len > 0 or inst.output != null) { + return self.fail(inst.base.src, "TODO implement inline asm inputs / outputs for SPU Mark II", .{}); + } + if (mem.eql(u8, inst.asm_source, "undefined0")) { + try self.code.resize(self.code.items.len + 2); + var instr = Instruction{ .condition = .always, .input0 = .zero, .input1 = .zero, .modify_flags = false, .output = .discard, .command = .undefined0 }; + mem.writeIntLittle(u16, self.code.items[self.code.items.len - 2 ..][0..2], @bitCast(u16, instr)); + return MCValue.none; + } else { + return self.fail(inst.base.src, "TODO implement support for more SPU II assembly instructions", .{}); + } + }, + .arm => { + for (inst.inputs) |input, i| { + if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { + return self.fail(inst.base.src, "unrecognized asm input constraint: '{}'", .{input}); + } + const reg_name = input[1 .. input.len - 1]; + const reg = parseRegName(reg_name) orelse + return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + const arg = try self.resolveInst(inst.args[i]); + try self.genSetReg(inst.base.src, reg, arg); + } + + if (mem.eql(u8, inst.asm_source, "svc #0")) { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.svc(.al, 0).toU32()); + } else { + return self.fail(inst.base.src, "TODO implement support for more arm assembly instructions", .{}); + } + + if (inst.output) |output| { + if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') { + return self.fail(inst.base.src, "unrecognized asm output constraint: '{}'", .{output}); + } + const reg_name = output[2 .. output.len - 1]; + const reg = parseRegName(reg_name) orelse + return self.fail(inst.base.src, "unrecognized register: '{}'", .{reg_name}); + return MCValue{ .register = reg }; + } else { + return MCValue.none; + } + }, .riscv64 => { for (inst.inputs) |input, i| { if (input.len < 3 or input[0] != '{' or input[input.len - 1] != '}') { @@ -1584,7 +1839,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { /// resulting REX is meaningful, but will remain the same if it is not. /// * Deliberately inserting a "meaningless REX" requires explicit usage of /// 0x40, and cannot be done via this function. + /// W => 64 bit mode + /// R => extension to the MODRM.reg field + /// X => extension to the SIB.index field + /// B => extension to the MODRM.rm field or the SIB.base field fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void { + comptime assert(arch == .x86_64); // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. var value: u8 = 0x40; if (arg.b) { @@ -1681,27 +1941,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{}); }, .register => |reg| { - const abi_size = ty.abiSize(self.target.*); - const adj_off = stack_offset + abi_size; - try self.code.ensureCapacity(self.code.items.len + 7); - self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); - const reg_id: u8 = @truncate(u3, reg.id()); - if (adj_off <= 128) { - // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx - const RM = @as(u8, 0b01_000_101) | (reg_id << 3); - const negative_offset = @intCast(i8, -@intCast(i32, adj_off)); - const twos_comp = @bitCast(u8, negative_offset); - self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM, twos_comp }); - } else if (adj_off <= 2147483648) { - // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx - const RM = @as(u8, 0b10_000_101) | (reg_id << 3); - const negative_offset = @intCast(i32, -@intCast(i33, adj_off)); - const twos_comp = @bitCast(u32, negative_offset); - self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM }); - mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); - } else { - return self.fail(src, "stack offset too large", .{}); - } + try self.genX8664ModRMRegToStack(src, ty, stack_offset, reg, 0x89); }, .memory => |vaddr| { return self.fail(src, "TODO implement set stack variable from memory vaddr", .{}); @@ -1709,7 +1949,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .stack_offset => |off| { if (stack_offset == off) return; // Copy stack variable to itself; nothing to do. - return self.fail(src, "TODO implement copy stack variable to stack variable", .{}); + + const reg = try self.copyToTmpRegister(src, mcv); + return self.genSetStack(src, ty, stack_offset, MCValue{ .register = reg }); }, }, else => return self.fail(src, "TODO implement getSetStack for {}", .{self.target.cpu.arch}), @@ -1718,6 +1960,58 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) InnerError!void { switch (arch) { + .arm => switch (mcv) { + .dead => unreachable, + .ptr_stack_offset => unreachable, + .ptr_embedded_in_code => unreachable, + .unreach, .none => return, // Nothing to do. + .undef => { + if (!self.wantSafety()) + return; // The already existing value will do just fine. + // Write the debug undefined value. + return self.genSetReg(src, reg, .{ .immediate = 0xaaaaaaaa }); + }, + .immediate => |x| { + // TODO better analysis of x to determine the + // least amount of necessary instructions (use + // more intelligent rotating) + if (x <= math.maxInt(u8)) { + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); + return; + } else if (x <= math.maxInt(u16)) { + // TODO Use movw Note: Not supported on + // all ARM targets! + + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); + } else if (x <= math.maxInt(u32)) { + // TODO Use movw and movt Note: Not + // supported on all ARM targets! Also TODO + // write constant to code and load + // relative to pc + + // immediate: 0xaabbccdd + // mov reg, #0xaa + // orr reg, reg, #0xbb, 24 + // orr reg, reg, #0xcc, 16 + // orr reg, reg, #0xdd, 8 + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.mov(.al, 0, reg, Instruction.Operand.imm(@truncate(u8, x), 0)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 8), 12)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 16), 8)).toU32()); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(.al, 0, reg, reg, Instruction.Operand.imm(@truncate(u8, x >> 24), 4)).toU32()); + return; + } else { + return self.fail(src, "ARM registers are 32-bit wide", .{}); + } + }, + .memory => |addr| { + // The value is in memory at a hard-coded address. + // If the type is a pointer, it means the pointer address is at this memory location. + try self.genSetReg(src, reg, .{ .immediate = addr }); + mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.al, reg, reg, Instruction.Offset.none).toU32()); + }, + else => return self.fail(src, "TODO implement getSetReg for arm {}", .{mcv}), + }, .riscv64 => switch (mcv) { .dead => unreachable, .ptr_stack_offset => unreachable, @@ -2027,7 +2321,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, }); if (imm >= math.maxInt(U)) { - return self.copyToNewRegister(inst); + return MCValue{ .register = try self.copyToTmpRegister(inst.src, mcv) }; } }, else => {}, @@ -2150,7 +2444,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { result.stack_byte_count = next_stack_offset; result.stack_align = 16; }, - else => return self.fail(src, "TODO implement function parameters for {}", .{cc}), + else => return self.fail(src, "TODO implement function parameters for {} on x86_64", .{cc}), } }, else => if (param_types.len != 0) @@ -2197,6 +2491,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .i386 => @import("codegen/x86.zig"), .x86_64 => @import("codegen/x86_64.zig"), .riscv64 => @import("codegen/riscv64.zig"), + .spu_2 => @import("codegen/spu-mk2.zig"), + .arm => @import("codegen/arm.zig"), + .armeb => @import("codegen/arm.zig"), else => struct { pub const Register = enum { dummy, diff --git a/src-self-hosted/codegen/arm.zig b/src-self-hosted/codegen/arm.zig new file mode 100644 index 0000000000..05178ea7d3 --- /dev/null +++ b/src-self-hosted/codegen/arm.zig @@ -0,0 +1,607 @@ +const std = @import("std"); +const DW = std.dwarf; +const testing = std.testing; + +/// The condition field specifies the flags neccessary for an +/// Instruction to be executed +pub const Condition = enum(u4) { + /// equal + eq, + /// not equal + ne, + /// unsigned higher or same + cs, + /// unsigned lower + cc, + /// negative + mi, + /// positive or zero + pl, + /// overflow + vs, + /// no overflow + vc, + /// unsigned higer + hi, + /// unsigned lower or same + ls, + /// greater or equal + ge, + /// less than + lt, + /// greater than + gt, + /// less than or equal + le, + /// always + al, +}; + +/// Represents a register in the ARM instruction set architecture +pub const Register = enum(u5) { + r0, + r1, + r2, + r3, + r4, + r5, + r6, + r7, + r8, + r9, + r10, + r11, + r12, + r13, + r14, + r15, + + /// Argument / result / scratch register 1 + a1, + /// Argument / result / scratch register 2 + a2, + /// Argument / scratch register 3 + a3, + /// Argument / scratch register 4 + a4, + /// Variable-register 1 + v1, + /// Variable-register 2 + v2, + /// Variable-register 3 + v3, + /// Variable-register 4 + v4, + /// Variable-register 5 + v5, + /// Platform register + v6, + /// Variable-register 7 + v7, + /// Frame pointer or Variable-register 8 + fp, + /// Intra-Procedure-call scratch register + ip, + /// Stack pointer + sp, + /// Link register + lr, + /// Program counter + pc, + + /// Returns the unique 4-bit ID of this register which is used in + /// the machine code + pub fn id(self: Register) u4 { + return @truncate(u4, @enumToInt(self)); + } + + /// Returns the index into `callee_preserved_regs`. + pub fn allocIndex(self: Register) ?u4 { + inline for (callee_preserved_regs) |cpreg, i| { + if (self.id() == cpreg.id()) return i; + } + return null; + } + + pub fn dwarfLocOp(self: Register) u8 { + return @as(u8, self.id()) + DW.OP_reg0; + } +}; + +test "Register.id" { + testing.expectEqual(@as(u4, 15), Register.r15.id()); + testing.expectEqual(@as(u4, 15), Register.pc.id()); +} + +pub const callee_preserved_regs = [_]Register{ .r0, .r1, .r2, .r3, .r4, .r5, .r6, .r7, .r8, .r10 }; +pub const c_abi_int_param_regs = [_]Register{ .r0, .r1, .r2, .r3 }; +pub const c_abi_int_return_regs = [_]Register{ .r0, .r1 }; + +/// Represents an instruction in the ARM instruction set architecture +pub const Instruction = union(enum) { + DataProcessing: packed struct { + // Note to self: The order of the fields top-to-bottom is + // right-to-left in the actual 32-bit int representation + op2: u12, + rd: u4, + rn: u4, + s: u1, + opcode: u4, + i: u1, + fixed: u2 = 0b00, + cond: u4, + }, + SingleDataTransfer: packed struct { + offset: u12, + rd: u4, + rn: u4, + l: u1, + w: u1, + b: u1, + u: u1, + p: u1, + i: u1, + fixed: u2 = 0b01, + cond: u4, + }, + Branch: packed struct { + offset: u24, + link: u1, + fixed: u3 = 0b101, + cond: u4, + }, + BranchExchange: packed struct { + rn: u4, + fixed_1: u1 = 0b1, + link: u1, + fixed_2: u22 = 0b0001_0010_1111_1111_1111_00, + cond: u4, + }, + SupervisorCall: packed struct { + comment: u24, + fixed: u4 = 0b1111, + cond: u4, + }, + Breakpoint: packed struct { + imm4: u4, + fixed_1: u4 = 0b0111, + imm12: u12, + fixed_2_and_cond: u12 = 0b1110_0001_0010, + }, + + /// Represents the possible operations which can be performed by a + /// DataProcessing instruction + const Opcode = enum(u4) { + // Rd := Op1 AND Op2 + @"and", + // Rd := Op1 EOR Op2 + eor, + // Rd := Op1 - Op2 + sub, + // Rd := Op2 - Op1 + rsb, + // Rd := Op1 + Op2 + add, + // Rd := Op1 + Op2 + C + adc, + // Rd := Op1 - Op2 + C - 1 + sbc, + // Rd := Op2 - Op1 + C - 1 + rsc, + // set condition codes on Op1 AND Op2 + tst, + // set condition codes on Op1 EOR Op2 + teq, + // set condition codes on Op1 - Op2 + cmp, + // set condition codes on Op1 + Op2 + cmn, + // Rd := Op1 OR Op2 + orr, + // Rd := Op2 + mov, + // Rd := Op1 AND NOT Op2 + bic, + // Rd := NOT Op2 + mvn, + }; + + /// Represents the second operand to a data processing instruction + /// which can either be content from a register or an immediate + /// value + pub const Operand = union(enum) { + Register: packed struct { + rm: u4, + shift: u8, + }, + Immediate: packed struct { + imm: u8, + rotate: u4, + }, + + /// Represents multiple ways a register can be shifted. A + /// register can be shifted by a specific immediate value or + /// by the contents of another register + pub const Shift = union(enum) { + Immediate: packed struct { + fixed: u1 = 0b0, + typ: u2, + amount: u5, + }, + Register: packed struct { + fixed_1: u1 = 0b1, + typ: u2, + fixed_2: u1 = 0b0, + rs: u4, + }, + + const Type = enum(u2) { + LogicalLeft, + LogicalRight, + ArithmeticRight, + RotateRight, + }; + + const none = Shift{ + .Immediate = .{ + .amount = 0, + .typ = 0, + }, + }; + + pub fn toU8(self: Shift) u8 { + return switch (self) { + .Register => |v| @bitCast(u8, v), + .Immediate => |v| @bitCast(u8, v), + }; + } + + pub fn reg(rs: Register, typ: Type) Shift { + return Shift{ + .Register = .{ + .rs = rs.id(), + .typ = @enumToInt(typ), + }, + }; + } + + pub fn imm(amount: u5, typ: Type) Shift { + return Shift{ + .Immediate = .{ + .amount = amount, + .typ = @enumToInt(typ), + }, + }; + } + }; + + pub fn toU12(self: Operand) u12 { + return switch (self) { + .Register => |v| @bitCast(u12, v), + .Immediate => |v| @bitCast(u12, v), + }; + } + + pub fn reg(rm: Register, shift: Shift) Operand { + return Operand{ + .Register = .{ + .rm = rm.id(), + .shift = shift.toU8(), + }, + }; + } + + pub fn imm(immediate: u8, rotate: u4) Operand { + return Operand{ + .Immediate = .{ + .imm = immediate, + .rotate = rotate, + }, + }; + } + }; + + /// Represents the offset operand of a load or store + /// instruction. Data can be loaded from memory with either an + /// immediate offset or an offset that is stored in some register. + pub const Offset = union(enum) { + Immediate: u12, + Register: packed struct { + rm: u4, + shift: u8, + }, + + pub const none = Offset{ + .Immediate = 0, + }; + + pub fn toU12(self: Offset) u12 { + return switch (self) { + .Register => |v| @bitCast(u12, v), + .Immediate => |v| v, + }; + } + + pub fn reg(rm: Register, shift: u8) Offset { + return Offset{ + .Register = .{ + .rm = rm.id(), + .shift = shift, + }, + }; + } + + pub fn imm(immediate: u8) Offset { + return Offset{ + .Immediate = immediate, + }; + } + }; + + pub fn toU32(self: Instruction) u32 { + return switch (self) { + .DataProcessing => |v| @bitCast(u32, v), + .SingleDataTransfer => |v| @bitCast(u32, v), + .Branch => |v| @bitCast(u32, v), + .BranchExchange => |v| @bitCast(u32, v), + .SupervisorCall => |v| @bitCast(u32, v), + .Breakpoint => |v| @intCast(u32, v.imm4) | (@intCast(u32, v.fixed_1) << 4) | (@intCast(u32, v.imm12) << 8) | (@intCast(u32, v.fixed_2_and_cond) << 20), + }; + } + + // Helper functions for the "real" functions below + + fn dataProcessing( + cond: Condition, + opcode: Opcode, + s: u1, + rd: Register, + rn: Register, + op2: Operand, + ) Instruction { + return Instruction{ + .DataProcessing = .{ + .cond = @enumToInt(cond), + .i = if (op2 == .Immediate) 1 else 0, + .opcode = @enumToInt(opcode), + .s = s, + .rn = rn.id(), + .rd = rd.id(), + .op2 = op2.toU12(), + }, + }; + } + + fn singleDataTransfer( + cond: Condition, + rd: Register, + rn: Register, + offset: Offset, + pre_post: u1, + up_down: u1, + byte_word: u1, + writeback: u1, + load_store: u1, + ) Instruction { + return Instruction{ + .SingleDataTransfer = .{ + .cond = @enumToInt(cond), + .rn = rn.id(), + .rd = rd.id(), + .offset = offset.toU12(), + .l = load_store, + .w = writeback, + .b = byte_word, + .u = up_down, + .p = pre_post, + .i = if (offset == .Immediate) 0 else 1, + }, + }; + } + + fn branch(cond: Condition, offset: i24, link: u1) Instruction { + return Instruction{ + .Branch = .{ + .cond = @enumToInt(cond), + .link = link, + .offset = @bitCast(u24, offset), + }, + }; + } + + fn branchExchange(cond: Condition, rn: Register, link: u1) Instruction { + return Instruction{ + .BranchExchange = .{ + .cond = @enumToInt(cond), + .link = link, + .rn = rn.id(), + }, + }; + } + + fn supervisorCall(cond: Condition, comment: u24) Instruction { + return Instruction{ + .SupervisorCall = .{ + .cond = @enumToInt(cond), + .comment = comment, + }, + }; + } + + fn breakpoint(imm: u16) Instruction { + return Instruction{ + .Breakpoint = .{ + .imm12 = @truncate(u12, imm >> 4), + .imm4 = @truncate(u4, imm), + }, + }; + } + + // Public functions replicating assembler syntax as closely as + // possible + + // Data processing + + pub fn @"and"(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .@"and", s, rd, rn, op2); + } + + pub fn eor(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .eor, s, rd, rn, op2); + } + + pub fn sub(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sub, s, rd, rn, op2); + } + + pub fn rsb(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsb, s, rd, rn, op2); + } + + pub fn add(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .add, s, rd, rn, op2); + } + + pub fn adc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .adc, s, rd, rn, op2); + } + + pub fn sbc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .sbc, s, rd, rn, op2); + } + + pub fn rsc(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .rsc, s, rd, rn, op2); + } + + pub fn tst(cond: Condition, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .tst, 1, .r0, rn, op2); + } + + pub fn teq(cond: Condition, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .teq, 1, .r0, rn, op2); + } + + pub fn cmp(cond: Condition, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .cmp, 1, .r0, rn, op2); + } + + pub fn cmn(cond: Condition, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .cmn, 1, .r0, rn, op2); + } + + pub fn orr(cond: Condition, s: u1, rd: Register, rn: Register, op2: Operand) Instruction { + return dataProcessing(cond, .orr, s, rd, rn, op2); + } + + pub fn mov(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mov, s, rd, .r0, op2); + } + + pub fn bic(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .bic, s, rd, rn, op2); + } + + pub fn mvn(cond: Condition, s: u1, rd: Register, op2: Operand) Instruction { + return dataProcessing(cond, .mvn, s, rd, .r0, op2); + } + + // Single data transfer + + pub fn ldr(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction { + return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 1); + } + + pub fn str(cond: Condition, rd: Register, rn: Register, offset: Offset) Instruction { + return singleDataTransfer(cond, rd, rn, offset, 1, 1, 0, 0, 0); + } + + // Branch + + pub fn b(cond: Condition, offset: i24) Instruction { + return branch(cond, offset, 0); + } + + pub fn bl(cond: Condition, offset: i24) Instruction { + return branch(cond, offset, 1); + } + + // Branch and exchange + + pub fn bx(cond: Condition, rn: Register) Instruction { + return branchExchange(cond, rn, 0); + } + + pub fn blx(cond: Condition, rn: Register) Instruction { + return branchExchange(cond, rn, 1); + } + + // Supervisor Call + + pub const swi = svc; + + pub fn svc(cond: Condition, comment: u24) Instruction { + return supervisorCall(cond, comment); + } + + // Breakpoint + + pub fn bkpt(imm: u16) Instruction { + return breakpoint(imm); + } +}; + +test "serialize instructions" { + const Testcase = struct { + inst: Instruction, + expected: u32, + }; + + const testcases = [_]Testcase{ + .{ // add r0, r0, r0 + .inst = Instruction.add(.al, 0, .r0, .r0, Instruction.Operand.reg(.r0, Instruction.Operand.Shift.none)), + .expected = 0b1110_00_0_0100_0_0000_0000_00000000_0000, + }, + .{ // mov r4, r2 + .inst = Instruction.mov(.al, 0, .r4, Instruction.Operand.reg(.r2, Instruction.Operand.Shift.none)), + .expected = 0b1110_00_0_1101_0_0000_0100_00000000_0010, + }, + .{ // mov r0, #42 + .inst = Instruction.mov(.al, 0, .r0, Instruction.Operand.imm(42, 0)), + .expected = 0b1110_00_1_1101_0_0000_0000_0000_00101010, + }, + .{ // ldr r0, [r2, #42] + .inst = Instruction.ldr(.al, .r0, .r2, Instruction.Offset.imm(42)), + .expected = 0b1110_01_0_1_1_0_0_1_0010_0000_000000101010, + }, + .{ // str r0, [r3] + .inst = Instruction.str(.al, .r0, .r3, Instruction.Offset.none), + .expected = 0b1110_01_0_1_1_0_0_0_0011_0000_000000000000, + }, + .{ // b #12 + .inst = Instruction.b(.al, 12), + .expected = 0b1110_101_0_0000_0000_0000_0000_0000_1100, + }, + .{ // bl #-4 + .inst = Instruction.bl(.al, -4), + .expected = 0b1110_101_1_1111_1111_1111_1111_1111_1100, + }, + .{ // bx lr + .inst = Instruction.bx(.al, .lr), + .expected = 0b1110_0001_0010_1111_1111_1111_0001_1110, + }, + .{ // svc #0 + .inst = Instruction.svc(.al, 0), + .expected = 0b1110_1111_0000_0000_0000_0000_0000_0000, + }, + .{ // bkpt #42 + .inst = Instruction.bkpt(42), + .expected = 0b1110_0001_0010_000000000010_0111_1010, + }, + }; + + for (testcases) |case| { + const actual = case.inst.toU32(); + testing.expectEqual(case.expected, actual); + } +} diff --git a/src-self-hosted/codegen/spu-mk2.zig b/src-self-hosted/codegen/spu-mk2.zig new file mode 100644 index 0000000000..542862caca --- /dev/null +++ b/src-self-hosted/codegen/spu-mk2.zig @@ -0,0 +1,170 @@ +const std = @import("std"); + +pub const Interpreter = @import("spu-mk2/interpreter.zig").Interpreter; + +pub const ExecutionCondition = enum(u3) { + always = 0, + when_zero = 1, + not_zero = 2, + greater_zero = 3, + less_than_zero = 4, + greater_or_equal_zero = 5, + less_or_equal_zero = 6, + overflow = 7, +}; + +pub const InputBehaviour = enum(u2) { + zero = 0, + immediate = 1, + peek = 2, + pop = 3, +}; + +pub const OutputBehaviour = enum(u2) { + discard = 0, + push = 1, + jump = 2, + jump_relative = 3, +}; + +pub const Command = enum(u5) { + copy = 0, + ipget = 1, + get = 2, + set = 3, + store8 = 4, + store16 = 5, + load8 = 6, + load16 = 7, + undefined0 = 8, + undefined1 = 9, + frget = 10, + frset = 11, + bpget = 12, + bpset = 13, + spget = 14, + spset = 15, + add = 16, + sub = 17, + mul = 18, + div = 19, + mod = 20, + @"and" = 21, + @"or" = 22, + xor = 23, + not = 24, + signext = 25, + rol = 26, + ror = 27, + bswap = 28, + asr = 29, + lsl = 30, + lsr = 31, +}; + +pub const Instruction = packed struct { + condition: ExecutionCondition, + input0: InputBehaviour, + input1: InputBehaviour, + modify_flags: bool, + output: OutputBehaviour, + command: Command, + reserved: u1 = 0, + + pub fn format(instr: Instruction, comptime fmt: []const u8, options: std.fmt.FormatOptions, out: anytype) !void { + try std.fmt.format(out, "0x{x:0<4} ", .{@bitCast(u16, instr)}); + try out.writeAll(switch (instr.condition) { + .always => " ", + .when_zero => "== 0", + .not_zero => "!= 0", + .greater_zero => " > 0", + .less_than_zero => " < 0", + .greater_or_equal_zero => ">= 0", + .less_or_equal_zero => "<= 0", + .overflow => "ovfl", + }); + try out.writeAll(" "); + try out.writeAll(switch (instr.input0) { + .zero => "zero", + .immediate => "imm ", + .peek => "peek", + .pop => "pop ", + }); + try out.writeAll(" "); + try out.writeAll(switch (instr.input1) { + .zero => "zero", + .immediate => "imm ", + .peek => "peek", + .pop => "pop ", + }); + try out.writeAll(" "); + try out.writeAll(switch (instr.command) { + .copy => "copy ", + .ipget => "ipget ", + .get => "get ", + .set => "set ", + .store8 => "store8 ", + .store16 => "store16 ", + .load8 => "load8 ", + .load16 => "load16 ", + .undefined0 => "undefined", + .undefined1 => "undefined", + .frget => "frget ", + .frset => "frset ", + .bpget => "bpget ", + .bpset => "bpset ", + .spget => "spget ", + .spset => "spset ", + .add => "add ", + .sub => "sub ", + .mul => "mul ", + .div => "div ", + .mod => "mod ", + .@"and" => "and ", + .@"or" => "or ", + .xor => "xor ", + .not => "not ", + .signext => "signext ", + .rol => "rol ", + .ror => "ror ", + .bswap => "bswap ", + .asr => "asr ", + .lsl => "lsl ", + .lsr => "lsr ", + }); + try out.writeAll(" "); + try out.writeAll(switch (instr.output) { + .discard => "discard", + .push => "push ", + .jump => "jmp ", + .jump_relative => "rjmp ", + }); + try out.writeAll(" "); + try out.writeAll(if (instr.modify_flags) + "+ flags" + else + " "); + } +}; + +pub const FlagRegister = packed struct { + zero: bool, + negative: bool, + carry: bool, + carry_enabled: bool, + interrupt0_enabled: bool, + interrupt1_enabled: bool, + interrupt2_enabled: bool, + interrupt3_enabled: bool, + reserved: u8 = 0, +}; + +pub const Register = enum { + dummy, + + pub fn allocIndex(self: Register) ?u4 { + return null; + } +}; + +pub const callee_preserved_regs = [_]Register{}; diff --git a/src-self-hosted/codegen/spu-mk2/interpreter.zig b/src-self-hosted/codegen/spu-mk2/interpreter.zig new file mode 100644 index 0000000000..1ec99546c6 --- /dev/null +++ b/src-self-hosted/codegen/spu-mk2/interpreter.zig @@ -0,0 +1,166 @@ +const std = @import("std"); +const log = std.log.scoped(.SPU_2_Interpreter); +const spu = @import("../spu-mk2.zig"); +const FlagRegister = spu.FlagRegister; +const Instruction = spu.Instruction; +const ExecutionCondition = spu.ExecutionCondition; + +pub fn Interpreter(comptime Bus: type) type { + return struct { + ip: u16 = 0, + sp: u16 = undefined, + bp: u16 = undefined, + fr: FlagRegister = @bitCast(FlagRegister, @as(u16, 0)), + /// This is set to true when we hit an undefined0 instruction, allowing it to + /// be used as a trap for testing purposes + undefined0: bool = false, + /// This is set to true when we hit an undefined1 instruction, allowing it to + /// be used as a trap for testing purposes. undefined1 is used as a breakpoint. + undefined1: bool = false, + bus: Bus, + + pub fn ExecuteBlock(self: *@This(), comptime size: ?u32) !void { + var count: usize = 0; + while (size == null or count < size.?) { + count += 1; + var instruction = @bitCast(Instruction, self.bus.read16(self.ip)); + + log.debug("Executing {}\n", .{instruction}); + + self.ip +%= 2; + + const execute = switch (instruction.condition) { + .always => true, + .not_zero => !self.fr.zero, + .when_zero => self.fr.zero, + .overflow => self.fr.carry, + ExecutionCondition.greater_or_equal_zero => !self.fr.negative, + else => return error.Unimplemented, + }; + + if (execute) { + const val0 = switch (instruction.input0) { + .zero => @as(u16, 0), + .immediate => i: { + const val = self.bus.read16(@intCast(u16, self.ip)); + self.ip +%= 2; + break :i val; + }, + else => |e| e: { + // peek or pop; show value at current SP, and if pop, increment sp + const val = self.bus.read16(self.sp); + if (e == .pop) { + self.sp +%= 2; + } + break :e val; + }, + }; + const val1 = switch (instruction.input1) { + .zero => @as(u16, 0), + .immediate => i: { + const val = self.bus.read16(@intCast(u16, self.ip)); + self.ip +%= 2; + break :i val; + }, + else => |e| e: { + // peek or pop; show value at current SP, and if pop, increment sp + const val = self.bus.read16(self.sp); + if (e == .pop) { + self.sp +%= 2; + } + break :e val; + }, + }; + + const output: u16 = switch (instruction.command) { + .get => self.bus.read16(self.bp +% (2 *% val0)), + .set => a: { + self.bus.write16(self.bp +% 2 *% val0, val1); + break :a val1; + }, + .load8 => self.bus.read8(val0), + .load16 => self.bus.read16(val0), + .store8 => a: { + const val = @truncate(u8, val1); + self.bus.write8(val0, val); + break :a val; + }, + .store16 => a: { + self.bus.write16(val0, val1); + break :a val1; + }, + .copy => val0, + .add => a: { + var val: u16 = undefined; + self.fr.carry = @addWithOverflow(u16, val0, val1, &val); + break :a val; + }, + .sub => a: { + var val: u16 = undefined; + self.fr.carry = @subWithOverflow(u16, val0, val1, &val); + break :a val; + }, + .spset => a: { + self.sp = val0; + break :a val0; + }, + .bpset => a: { + self.bp = val0; + break :a val0; + }, + .frset => a: { + const val = (@bitCast(u16, self.fr) & val1) | (val0 & ~val1); + self.fr = @bitCast(FlagRegister, val); + break :a val; + }, + .bswap => (val0 >> 8) | (val0 << 8), + .bpget => self.bp, + .spget => self.sp, + .ipget => self.ip +% (2 *% val0), + .lsl => val0 << 1, + .lsr => val0 >> 1, + .@"and" => val0 & val1, + .@"or" => val0 | val1, + .xor => val0 ^ val1, + .not => ~val0, + .undefined0 => { + self.undefined0 = true; + // Break out of the loop, and let the caller decide what to do + return; + }, + .undefined1 => { + self.undefined1 = true; + // Break out of the loop, and let the caller decide what to do + return; + }, + .signext => if ((val0 & 0x80) != 0) + (val0 & 0xFF) | 0xFF00 + else + (val0 & 0xFF), + else => return error.Unimplemented, + }; + + switch (instruction.output) { + .discard => {}, + .push => { + self.sp -%= 2; + self.bus.write16(self.sp, output); + }, + .jump => { + self.ip = output; + }, + else => return error.Unimplemented, + } + if (instruction.modify_flags) { + self.fr.negative = (output & 0x8000) != 0; + self.fr.zero = (output == 0x0000); + } + } else { + if (instruction.input0 == .immediate) self.ip +%= 2; + if (instruction.input1 == .immediate) self.ip +%= 2; + break; + } + } + } + }; +} diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 1e650d5e2c..7a5680dfbf 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -5,6 +5,9 @@ const fs = std.fs; const trace = @import("tracy.zig").trace; const Package = @import("Package.zig"); const Type = @import("type.zig").Type; +const build_options = @import("build_options"); + +pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version; pub const Options = struct { target: std.Target, @@ -20,6 +23,7 @@ pub const Options = struct { /// Used for calculating how much space to reserve for executable program code in case /// the binary file deos not already have such a section. program_code_size_hint: u64 = 256 * 1024, + entry_addr: ?u64 = null, }; pub const File = struct { diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 3751411297..1d18344cbb 100644 --- a/src-self-hosted/link/Elf.zig +++ b/src-self-hosted/link/Elf.zig @@ -14,12 +14,10 @@ const leb128 = std.debug.leb; const Package = @import("../Package.zig"); const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; -const build_options = @import("build_options"); const link = @import("../link.zig"); const File = link.File; const Elf = @This(); -const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version; const default_entry_addr = 0x8000000; // TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented. @@ -249,8 +247,8 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf { .allocator = allocator, }, .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { - 32 => .p32, - 64 => .p64, + 0 ... 32 => .p32, + 33 ... 64 => .p64, else => return error.UnsupportedELFArchitecture, }, }; @@ -278,8 +276,8 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf .file = file, }, .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { - 32 => .p32, - 64 => .p64, + 0 ... 32 => .p32, + 33 ... 64 => .p64, else => return error.UnsupportedELFArchitecture, }, .shdr_table_dirty = true, @@ -346,7 +344,7 @@ fn getDebugLineProgramEnd(self: Elf) u32 { /// Returns end pos of collision, if any. fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 { - const small_ptr = self.base.options.target.cpu.arch.ptrBitWidth() == 32; + const small_ptr = self.ptr_width == .p32; const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr); if (start < ehdr_size) return ehdr_size; @@ -462,12 +460,13 @@ pub fn populateMissingMetadata(self: *Elf) !void { const p_align = 0x1000; const off = self.findFreeSpace(file_size, p_align); log.debug("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); + const entry_addr: u64 = self.entry_addr orelse if (self.base.options.target.cpu.arch == .spu_2) @as(u64, 0) else default_entry_addr; try self.program_headers.append(self.base.allocator, .{ .p_type = elf.PT_LOAD, .p_offset = off, .p_filesz = file_size, - .p_vaddr = default_entry_addr, - .p_paddr = default_entry_addr, + .p_vaddr = entry_addr, + .p_paddr = entry_addr, .p_memsz = file_size, .p_align = p_align, .p_flags = elf.PF_X | elf.PF_R, @@ -486,13 +485,13 @@ pub fn populateMissingMetadata(self: *Elf) !void { // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at. // we'll need to re-use that function anyway, in case the GOT grows and overlaps something // else in virtual memory. - const default_got_addr = if (ptr_size == 2) @as(u32, 0x8000) else 0x4000000; + const got_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0x4000000 else 0x8000; try self.program_headers.append(self.base.allocator, .{ .p_type = elf.PT_LOAD, .p_offset = off, .p_filesz = file_size, - .p_vaddr = default_got_addr, - .p_paddr = default_got_addr, + .p_vaddr = got_addr, + .p_paddr = got_addr, .p_memsz = file_size, .p_align = p_align, .p_flags = elf.PF_R, @@ -863,7 +862,7 @@ pub fn flush(self: *Elf, module: *Module) !void { // Write the form for the compile unit, which must match the abbrev table above. const name_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_path); const comp_dir_strp = try self.makeDebugString(self.base.options.root_pkg.root_src_dir_path); - const producer_strp = try self.makeDebugString(producer_string); + const producer_strp = try self.makeDebugString(link.producer_string); // Currently only one compilation unit is supported, so the address range is simply // identical to the main program header virtual address and memory size. const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?]; @@ -1349,6 +1348,7 @@ fn freeTextBlock(self: *Elf, text_block: *TextBlock) void { var already_have_free_list_node = false; { var i: usize = 0; + // TODO turn text_block_free_list into a hash map while (i < self.text_block_free_list.items.len) { if (self.text_block_free_list.items[i] == text_block) { _ = self.text_block_free_list.swapRemove(i); @@ -1360,11 +1360,19 @@ fn freeTextBlock(self: *Elf, text_block: *TextBlock) void { i += 1; } } + // TODO process free list for dbg info just like we do above for vaddrs if (self.last_text_block == text_block) { // TODO shrink the .text section size here self.last_text_block = text_block.prev; } + if (self.dbg_info_decl_first == text_block) { + self.dbg_info_decl_first = text_block.dbg_info_next; + } + if (self.dbg_info_decl_last == text_block) { + // TODO shrink the .debug_info section size here + self.dbg_info_decl_last = text_block.dbg_info_prev; + } if (text_block.prev) |prev| { prev.next = text_block.next; @@ -1383,6 +1391,20 @@ fn freeTextBlock(self: *Elf, text_block: *TextBlock) void { } else { text_block.next = null; } + + if (text_block.dbg_info_prev) |prev| { + prev.dbg_info_next = text_block.dbg_info_next; + + // TODO the free list logic like we do for text blocks above + } else { + text_block.dbg_info_prev = null; + } + + if (text_block.dbg_info_next) |next| { + next.dbg_info_prev = text_block.dbg_info_prev; + } else { + text_block.dbg_info_next = null; + } } fn shrinkTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64) void { @@ -1584,10 +1606,10 @@ pub fn freeDecl(self: *Elf, decl: *Module.Decl) void { next.prev = null; } if (self.dbg_line_fn_first == &decl.fn_link.elf) { - self.dbg_line_fn_first = null; + self.dbg_line_fn_first = decl.fn_link.elf.next; } if (self.dbg_line_fn_last == &decl.fn_link.elf) { - self.dbg_line_fn_last = null; + self.dbg_line_fn_last = decl.fn_link.elf.prev; } } @@ -2151,29 +2173,28 @@ pub fn deleteExport(self: *Elf, exp: Export) void { fn writeProgHeader(self: *Elf, index: usize) !void { const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); const offset = self.program_headers.items[index].p_offset; - switch (self.base.options.target.cpu.arch.ptrBitWidth()) { - 32 => { + switch (self.ptr_width) { + .p32 => { var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])}; if (foreign_endian) { bswapAllFields(elf.Elf32_Phdr, &phdr[0]); } return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); }, - 64 => { + .p64 => { var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]}; if (foreign_endian) { bswapAllFields(elf.Elf64_Phdr, &phdr[0]); } return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset); }, - else => return error.UnsupportedArchitecture, } } fn writeSectHeader(self: *Elf, index: usize) !void { const foreign_endian = self.base.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); - switch (self.base.options.target.cpu.arch.ptrBitWidth()) { - 32 => { + switch (self.ptr_width) { + .p32 => { var shdr: [1]elf.Elf32_Shdr = undefined; shdr[0] = sectHeaderTo32(self.sections.items[index]); if (foreign_endian) { @@ -2182,7 +2203,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void { const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr); return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); }, - 64 => { + .p64 => { var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]}; if (foreign_endian) { bswapAllFields(elf.Elf64_Shdr, &shdr[0]); @@ -2190,14 +2211,13 @@ fn writeSectHeader(self: *Elf, index: usize) !void { const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr); return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset); }, - else => return error.UnsupportedArchitecture, } } fn writeOffsetTableEntry(self: *Elf, index: usize) !void { const shdr = &self.sections.items[self.got_section_index.?]; const phdr = &self.program_headers.items[self.phdr_got_index.?]; - const entry_size: u16 = self.ptrWidthBytes(); + const entry_size: u16 = self.archPtrWidthBytes(); if (self.offset_table_count_dirty) { // TODO Also detect virtual address collisions. const allocated_size = self.allocatedSize(shdr.sh_offset); @@ -2221,17 +2241,23 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void { } const endian = self.base.options.target.cpu.arch.endian(); const off = shdr.sh_offset + @as(u64, entry_size) * index; - switch (self.ptr_width) { - .p32 => { + switch (entry_size) { + 2 => { + var buf: [2]u8 = undefined; + mem.writeInt(u16, &buf, @intCast(u16, self.offset_table.items[index]), endian); + try self.base.file.?.pwriteAll(&buf, off); + }, + 4 => { var buf: [4]u8 = undefined; mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian); try self.base.file.?.pwriteAll(&buf, off); }, - .p64 => { + 8 => { var buf: [8]u8 = undefined; mem.writeInt(u64, &buf, self.offset_table.items[index], endian); try self.base.file.?.pwriteAll(&buf, off); }, + else => unreachable, } } @@ -2344,6 +2370,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void { } } +/// Always 4 or 8 depending on whether this is 32-bit ELF or 64-bit ELF. fn ptrWidthBytes(self: Elf) u8 { return switch (self.ptr_width) { .p32 => 4, @@ -2351,6 +2378,12 @@ fn ptrWidthBytes(self: Elf) u8 { }; } +/// Does not necessarily match `ptrWidthBytes` for example can be 2 bytes +/// in a 32-bit ELF file. +fn archPtrWidthBytes(self: Elf) u8 { + return @intCast(u8, self.base.options.target.cpu.arch.ptrBitWidth() / 8); +} + /// The reloc offset for the virtual address of a function in its Line Number Program. /// Size is a virtual address integer. const dbg_line_vaddr_reloc_index = 3; diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index 4bcea9cfa8..a65366261a 100644 --- a/src-self-hosted/link/MachO.zig +++ b/src-self-hosted/link/MachO.zig @@ -6,29 +6,66 @@ const assert = std.debug.assert; const fs = std.fs; const log = std.log.scoped(.link); const macho = std.macho; +const codegen = @import("../codegen.zig"); const math = std.math; const mem = std.mem; +const trace = @import("../tracy.zig").trace; +const Type = @import("../type.zig").Type; const Module = @import("../Module.zig"); const link = @import("../link.zig"); const File = link.File; +const is_darwin = std.Target.current.os.tag.isDarwin(); + pub const base_tag: File.Tag = File.Tag.macho; base: File, -/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. -/// Same order as in the file. -segment_cmds: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){}, +/// List of all load command headers that are in the file. +/// We use it to track number and size of all commands needed by the header. +commands: std.ArrayListUnmanaged(macho.load_command) = std.ArrayListUnmanaged(macho.load_command){}, +command_file_offset: ?u64 = null, /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write. /// Same order as in the file. +segments: std.ArrayListUnmanaged(macho.segment_command_64) = std.ArrayListUnmanaged(macho.segment_command_64){}, sections: std.ArrayListUnmanaged(macho.section_64) = std.ArrayListUnmanaged(macho.section_64){}, +segment_table_offset: ?u64 = null, +/// Entry point load command +entry_point_cmd: ?macho.entry_point_command = null, entry_addr: ?u64 = null, +/// Default VM start address set at 4GB +vm_start_address: u64 = 0x100000000, + +seg_table_dirty: bool = false, + error_flags: File.ErrorFlags = File.ErrorFlags{}, +/// TODO ultimately this will be propagated down from main() and set (in this form or another) +/// when user links against system lib. +link_against_system: bool = false, + +/// `alloc_num / alloc_den` is the factor of padding when allocating. +const alloc_num = 4; +const alloc_den = 3; + +/// Default path to dyld +/// TODO instead of hardcoding it, we should probably look through some env vars and search paths +/// instead but this will do for now. +const DEFAULT_DYLD_PATH: [*:0]const u8 = "/usr/lib/dyld"; + +/// Default lib search path +/// TODO instead of hardcoding it, we should probably look through some env vars and search paths +/// instead but this will do for now. +const DEFAULT_LIB_SEARCH_PATH: []const u8 = "/usr/lib"; + +const LIB_SYSTEM_NAME: [*:0]const u8 = "System"; +/// TODO we should search for libSystem and fail if it doesn't exist, instead of hardcoding it +const LIB_SYSTEM_PATH: [*:0]const u8 = DEFAULT_LIB_SEARCH_PATH ++ "/libSystem.B.dylib"; + pub const TextBlock = struct { pub const empty = TextBlock{}; }; @@ -80,12 +117,6 @@ fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO /// Truncates the existing file contents and overwrites the contents. /// Returns an error if `file` is not already open with +read +write +seek abilities. fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !MachO { - switch (options.output_mode) { - .Exe => {}, - .Obj => {}, - .Lib => return error.TODOImplementWritingLibFiles, - } - var self: MachO = .{ .base = .{ .file = file, @@ -96,31 +127,35 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach }; errdefer self.deinit(); - if (options.output_mode == .Exe) { - // The first segment command for executables is always a __PAGEZERO segment. - try self.segment_cmds.append(allocator, .{ - .cmd = macho.LC_SEGMENT_64, - .cmdsize = @sizeOf(macho.segment_command_64), - .segname = self.makeString("__PAGEZERO"), - .vmaddr = 0, - .vmsize = 0, - .fileoff = 0, - .filesize = 0, - .maxprot = 0, - .initprot = 0, - .nsects = 0, - .flags = 0, - }); + switch (options.output_mode) { + .Exe => { + // The first segment command for executables is always a __PAGEZERO segment. + const pagezero = .{ + .cmd = macho.LC_SEGMENT_64, + .cmdsize = commandSize(@sizeOf(macho.segment_command_64)), + .segname = makeString("__PAGEZERO"), + .vmaddr = 0, + .vmsize = self.vm_start_address, + .fileoff = 0, + .filesize = 0, + .maxprot = 0, + .initprot = 0, + .nsects = 0, + .flags = 0, + }; + try self.commands.append(allocator, .{ + .cmd = pagezero.cmd, + .cmdsize = pagezero.cmdsize, + }); + try self.segments.append(allocator, pagezero); + }, + .Obj => return error.TODOImplementWritingObjFiles, + .Lib => return error.TODOImplementWritingLibFiles, } - return self; -} + try self.populateMissingMetadata(); -fn makeString(self: *MachO, comptime bytes: []const u8) [16]u8 { - var buf: [16]u8 = undefined; - if (bytes.len > buf.len) @compileError("MachO segment/section name too long"); - mem.copy(u8, buf[0..], bytes); - return buf; + return self; } fn writeMachOHeader(self: *MachO) !void { @@ -156,10 +191,14 @@ fn writeMachOHeader(self: *MachO) !void { }; hdr.filetype = filetype; - // TODO consider other commands - const ncmds = try math.cast(u32, self.segment_cmds.items.len); + const ncmds = try math.cast(u32, self.commands.items.len); hdr.ncmds = ncmds; - hdr.sizeofcmds = ncmds * @sizeOf(macho.segment_command_64); + + var sizeof_cmds: u32 = 0; + for (self.commands.items) |cmd| { + sizeof_cmds += cmd.cmdsize; + } + hdr.sizeofcmds = sizeof_cmds; // TODO should these be set to something else? hdr.flags = 0; @@ -169,18 +208,90 @@ fn writeMachOHeader(self: *MachO) !void { } pub fn flush(self: *MachO, module: *Module) !void { - // TODO implement flush + // Save segments first { - const buf = try self.base.allocator.alloc(macho.segment_command_64, self.segment_cmds.items.len); + const buf = try self.base.allocator.alloc(macho.segment_command_64, self.segments.items.len); defer self.base.allocator.free(buf); + self.command_file_offset = @sizeOf(macho.mach_header_64); + for (buf) |*seg, i| { - seg.* = self.segment_cmds.items[i]; + seg.* = self.segments.items[i]; + self.command_file_offset.? += self.segments.items[i].cmdsize; } try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), @sizeOf(macho.mach_header_64)); } + switch (self.base.options.output_mode) { + .Exe => { + if (self.link_against_system) { + if (is_darwin) { + { + // Specify path to dynamic linker dyld + const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylinker_command) + mem.lenZ(DEFAULT_DYLD_PATH))); + const load_dylinker = [1]macho.dylinker_command{ + .{ + .cmd = macho.LC_LOAD_DYLINKER, + .cmdsize = cmdsize, + .name = @sizeOf(macho.dylinker_command), + }, + }; + try self.commands.append(self.base.allocator, .{ + .cmd = macho.LC_LOAD_DYLINKER, + .cmdsize = cmdsize, + }); + + try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylinker[0..1]), self.command_file_offset.?); + + const file_offset = self.command_file_offset.? + @sizeOf(macho.dylinker_command); + try self.addPadding(cmdsize - @sizeOf(macho.dylinker_command), file_offset); + + try self.base.file.?.pwriteAll(mem.spanZ(DEFAULT_DYLD_PATH), file_offset); + self.command_file_offset.? += cmdsize; + } + + { + // Link against libSystem + const cmdsize = commandSize(@intCast(u32, @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH))); + // According to Apple's manual, we should obtain current libSystem version using libc call + // NSVersionOfRunTimeLibrary. + const version = std.c.NSVersionOfRunTimeLibrary(LIB_SYSTEM_NAME); + const dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files + .current_version = version, + .compatibility_version = 0x10000, // not sure why this either; value from reverse engineering + }; + const load_dylib = [1]macho.dylib_command{ + .{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = cmdsize, + .dylib = dylib, + }, + }; + try self.commands.append(self.base.allocator, .{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = cmdsize, + }); + + try self.base.file.?.pwriteAll(mem.sliceAsBytes(load_dylib[0..1]), self.command_file_offset.?); + + const file_offset = self.command_file_offset.? + @sizeOf(macho.dylib_command); + try self.addPadding(cmdsize - @sizeOf(macho.dylib_command), file_offset); + + try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), file_offset); + self.command_file_offset.? += cmdsize; + } + } else { + @panic("linking against libSystem on non-native target is unsupported"); + } + } + }, + .Obj => return error.TODOImplementWritingObjFiles, + .Lib => return error.TODOImplementWritingLibFiles, + } + if (self.entry_addr == null and self.base.options.output_mode == .Exe) { log.debug("flushing. no_entry_point_found = true\n", .{}); self.error_flags.no_entry_point_found = true; @@ -192,7 +303,8 @@ pub fn flush(self: *MachO, module: *Module) !void { } pub fn deinit(self: *MachO) void { - self.segment_cmds.deinit(self.base.allocator); + self.commands.deinit(self.base.allocator); + self.segments.deinit(self.base.allocator); self.sections.deinit(self.base.allocator); } @@ -214,3 +326,30 @@ pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { @panic("TODO implement getDeclVAddr for MachO"); } + +pub fn populateMissingMetadata(self: *MachO) !void {} + +fn makeString(comptime bytes: []const u8) [16]u8 { + var buf: [16]u8 = undefined; + if (bytes.len > buf.len) @compileError("MachO segment/section name too long"); + mem.copy(u8, buf[0..], bytes); + return buf; +} + +fn commandSize(min_size: u32) u32 { + if (min_size % @sizeOf(u64) == 0) return min_size; + + const div = min_size / @sizeOf(u64); + return (div + 1) * @sizeOf(u64); +} + +fn addPadding(self: *MachO, size: u32, file_offset: u64) !void { + if (size == 0) return; + + const buf = try self.base.allocator.alloc(u8, size); + defer self.base.allocator.free(buf); + + mem.set(u8, buf[0..], 0); + + try self.base.file.?.pwriteAll(buf, file_offset); +} diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 9e88466cf7..f9c9121817 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -583,7 +583,10 @@ pub const TestContext = struct { switch (case.target.getExternalExecutor()) { .native => try argv.append(exe_path), - .unavailable => return, // No executor available; pass test. + .unavailable => { + try self.runInterpreterIfAvailable(allocator, &exec_node, case, tmp.dir, bin_name); + return; // Pass test. + }, .qemu => |qemu_bin_name| if (enable_qemu) { // TODO Ability for test cases to specify whether to link libc. @@ -635,7 +638,6 @@ pub const TestContext = struct { var test_node = update_node.start("test", null); test_node.activate(); defer test_node.end(); - defer allocator.free(exec_result.stdout); defer allocator.free(exec_result.stderr); switch (exec_result.term) { @@ -657,4 +659,115 @@ pub const TestContext = struct { } } } + + fn runInterpreterIfAvailable( + self: *TestContext, + gpa: *Allocator, + node: *std.Progress.Node, + case: Case, + tmp_dir: std.fs.Dir, + bin_name: []const u8, + ) !void { + const arch = case.target.cpu_arch orelse return; + switch (arch) { + .spu_2 => return self.runSpu2Interpreter(gpa, node, case, tmp_dir, bin_name), + else => return, + } + } + + fn runSpu2Interpreter( + self: *TestContext, + gpa: *Allocator, + update_node: *std.Progress.Node, + case: Case, + tmp_dir: std.fs.Dir, + bin_name: []const u8, + ) !void { + const spu = @import("codegen/spu-mk2.zig"); + if (case.target.os_tag) |os| { + if (os != .freestanding) { + std.debug.panic("Only freestanding makes sense for SPU-II tests!", .{}); + } + } else { + std.debug.panic("SPU_2 has no native OS, check the test!", .{}); + } + + var interpreter = spu.Interpreter(struct { + RAM: [0x10000]u8 = undefined, + + pub fn read8(bus: @This(), addr: u16) u8 { + return bus.RAM[addr]; + } + pub fn read16(bus: @This(), addr: u16) u16 { + return std.mem.readIntLittle(u16, bus.RAM[addr..][0..2]); + } + + pub fn write8(bus: *@This(), addr: u16, val: u8) void { + bus.RAM[addr] = val; + } + + pub fn write16(bus: *@This(), addr: u16, val: u16) void { + std.mem.writeIntLittle(u16, bus.RAM[addr..][0..2], val); + } + }){ + .bus = .{}, + }; + + { + var load_node = update_node.start("load", null); + load_node.activate(); + defer load_node.end(); + + var file = try tmp_dir.openFile(bin_name, .{ .read = true }); + defer file.close(); + + const header = try std.elf.readHeader(file); + var iterator = header.program_header_iterator(file); + + var none_loaded = true; + + while (try iterator.next()) |phdr| { + if (phdr.p_type != std.elf.PT_LOAD) { + std.debug.print("Encountered unexpected ELF program header: type {}\n", .{phdr.p_type}); + std.process.exit(1); + } + if (phdr.p_paddr != phdr.p_vaddr) { + std.debug.print("Physical address does not match virtual address in ELF header!\n", .{}); + std.process.exit(1); + } + if (phdr.p_filesz != phdr.p_memsz) { + std.debug.print("Physical size does not match virtual size in ELF header!\n", .{}); + std.process.exit(1); + } + if ((try file.pread(interpreter.bus.RAM[phdr.p_paddr .. phdr.p_paddr + phdr.p_filesz], phdr.p_offset)) != phdr.p_filesz) { + std.debug.print("Read less than expected from ELF file!", .{}); + std.process.exit(1); + } + std.log.scoped(.spu2_test).debug("Loaded 0x{x} bytes to 0x{x:0<4}\n", .{ phdr.p_filesz, phdr.p_paddr }); + none_loaded = false; + } + if (none_loaded) { + std.debug.print("No data found in ELF file!\n", .{}); + std.process.exit(1); + } + } + + var exec_node = update_node.start("execute", null); + exec_node.activate(); + defer exec_node.end(); + + var blocks: u16 = 1000; + const block_size = 1000; + while (!interpreter.undefined0) { + const pre_ip = interpreter.ip; + if (blocks > 0) { + blocks -= 1; + try interpreter.ExecuteBlock(block_size); + if (pre_ip == interpreter.ip) { + std.debug.print("Infinite loop detected in SPU II test!\n", .{}); + std.process.exit(1); + } + } + } + } }; diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index 2218c49200..eb8fa2acd7 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -3,6 +3,7 @@ const Value = @import("value.zig").Value; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Target = std.Target; +const Module = @import("Module.zig"); /// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication. /// It's important for this type to be small. @@ -52,7 +53,7 @@ pub const Type = extern union { .bool => return .Bool, .void => return .Void, .type => return .Type, - .anyerror => return .ErrorSet, + .error_set, .error_set_single, .anyerror => return .ErrorSet, .comptime_int => return .ComptimeInt, .comptime_float => return .ComptimeFloat, .noreturn => return .NoReturn, @@ -84,6 +85,10 @@ pub const Type = extern union { .optional_single_mut_pointer, => return .Optional, .enum_literal => return .EnumLiteral, + + .anyerror_void_error_union, .error_union => return .ErrorUnion, + + .anyframe_T, .@"anyframe" => return .AnyFrame, } } @@ -151,6 +156,9 @@ pub const Type = extern union { .ComptimeInt => return true, .Undefined => return true, .Null => return true, + .AnyFrame => { + return a.elemType().eql(b.elemType()); + }, .Pointer => { // Hot path for common case: if (a.castPointer()) |a_payload| { @@ -225,7 +233,6 @@ pub const Type = extern union { .BoundFn, .Opaque, .Frame, - .AnyFrame, .Vector, => std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ a, b }), } @@ -343,6 +350,8 @@ pub const Type = extern union { .single_const_pointer_to_comptime_int, .const_slice_u8, .enum_literal, + .anyerror_void_error_union, + .@"anyframe", => unreachable, .array_u8_sentinel_0 => return self.copyPayloadShallow(allocator, Payload.Array_u8_Sentinel0), @@ -397,6 +406,7 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, => return self.copyPayloadSingleField(allocator, Payload.PointerSimple, "pointee_type"), + .anyframe_T => return self.copyPayloadSingleField(allocator, Payload.AnyFrame, "return_type"), .pointer => { const payload = @fieldParentPtr(Payload.Pointer, "base", self.ptr_otherwise); @@ -416,6 +426,19 @@ pub const Type = extern union { }; return Type{ .ptr_otherwise = &new_payload.base }; }, + .error_union => { + const payload = @fieldParentPtr(Payload.ErrorUnion, "base", self.ptr_otherwise); + const new_payload = try allocator.create(Payload.ErrorUnion); + new_payload.* = .{ + .base = payload.base, + + .error_set = try payload.error_set.copy(allocator), + .payload = try payload.payload.copy(allocator), + }; + return Type{ .ptr_otherwise = &new_payload.base }; + }, + .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet), + .error_set_single => return self.copyPayloadShallow(allocator, Payload.ErrorSetSingle), } } @@ -482,6 +505,8 @@ pub const Type = extern union { .@"null" => return out_stream.writeAll("@TypeOf(null)"), .@"undefined" => return out_stream.writeAll("@TypeOf(undefined)"), + .@"anyframe" => return out_stream.writeAll("anyframe"), + .anyerror_void_error_union => return out_stream.writeAll("anyerror!void"), .const_slice_u8 => return out_stream.writeAll("[]const u8"), .fn_noreturn_no_args => return out_stream.writeAll("fn() noreturn"), .fn_void_no_args => return out_stream.writeAll("fn() void"), @@ -500,6 +525,12 @@ pub const Type = extern union { continue; }, + .anyframe_T => { + const payload = @fieldParentPtr(Payload.AnyFrame, "base", ty.ptr_otherwise); + try out_stream.print("anyframe->", .{}); + ty = payload.return_type; + continue; + }, .array_u8 => { const payload = @fieldParentPtr(Payload.Array_u8, "base", ty.ptr_otherwise); return out_stream.print("[{}]u8", .{payload.len}); @@ -622,6 +653,21 @@ pub const Type = extern union { ty = payload.pointee_type; continue; }, + .error_union => { + const payload = @fieldParentPtr(Payload.ErrorUnion, "base", ty.ptr_otherwise); + try payload.error_set.format("", .{}, out_stream); + try out_stream.writeAll("!"); + ty = payload.payload; + continue; + }, + .error_set => { + const payload = @fieldParentPtr(Payload.ErrorSet, "base", ty.ptr_otherwise); + return out_stream.writeAll(std.mem.spanZ(payload.decl.name)); + }, + .error_set_single => { + const payload = @fieldParentPtr(Payload.ErrorSetSingle, "base", ty.ptr_otherwise); + return out_stream.print("error{{{}}}", .{payload.name}); + }, } unreachable; } @@ -715,6 +761,11 @@ pub const Type = extern union { .optional, .optional_single_mut_pointer, .optional_single_const_pointer, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => true, // TODO lazy types .array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0, @@ -723,6 +774,11 @@ pub const Type = extern union { .int_signed => self.cast(Payload.IntSigned).?.bits == 0, .int_unsigned => self.cast(Payload.IntUnsigned).?.bits == 0, + .error_union => { + const payload = self.cast(Payload.ErrorUnion).?; + return payload.error_set.hasCodeGenBits() or payload.payload.hasCodeGenBits(); + }, + .c_void, .void, .type, @@ -756,6 +812,7 @@ pub const Type = extern union { .fn_ccc_void_no_args, // represents machine code; not a pointer .function, // represents machine code; not a pointer => return switch (target.cpu.arch) { + .arm => 4, .riscv64 => 2, else => 1, }, @@ -778,6 +835,8 @@ pub const Type = extern union { .mut_slice, .optional_single_const_pointer, .optional_single_mut_pointer, + .@"anyframe", + .anyframe_T, => return @divExact(target.cpu.arch.ptrBitWidth(), 8), .pointer => { @@ -802,7 +861,11 @@ pub const Type = extern union { .f128 => return 16, .c_longdouble => return 16, - .anyerror => return 2, // TODO revisit this when we have the concept of the error tag type + .error_set, + .error_set_single, + .anyerror_void_error_union, + .anyerror, + => return 2, // TODO revisit this when we have the concept of the error tag type .array, .array_sentinel => return self.elemType().abiAlignment(target), @@ -828,6 +891,16 @@ pub const Type = extern union { return child_type.abiAlignment(target); }, + .error_union => { + const payload = self.cast(Payload.ErrorUnion).?; + if (!payload.error_set.hasCodeGenBits()) { + return payload.payload.abiAlignment(target); + } else if (!payload.payload.hasCodeGenBits()) { + return payload.error_set.abiAlignment(target); + } + @panic("TODO abiAlignment error union"); + }, + .c_void, .void, .type, @@ -881,12 +954,15 @@ pub const Type = extern union { .i32, .u32 => return 4, .i64, .u64 => return 8, - .isize, .usize => return @divExact(target.cpu.arch.ptrBitWidth(), 8), + .@"anyframe", .anyframe_T, .isize, .usize => return @divExact(target.cpu.arch.ptrBitWidth(), 8), .const_slice, .mut_slice, - .const_slice_u8, - => return @divExact(target.cpu.arch.ptrBitWidth(), 8) * 2, + => { + if (self.elemType().hasCodeGenBits()) return @divExact(target.cpu.arch.ptrBitWidth(), 8) * 2; + return @divExact(target.cpu.arch.ptrBitWidth(), 8); + }, + .const_slice_u8 => return @divExact(target.cpu.arch.ptrBitWidth(), 8) * 2, .optional_single_const_pointer, .optional_single_mut_pointer, @@ -922,7 +998,11 @@ pub const Type = extern union { .f128 => return 16, .c_longdouble => return 16, - .anyerror => return 2, // TODO revisit this when we have the concept of the error tag type + .error_set, + .error_set_single, + .anyerror_void_error_union, + .anyerror, + => return 2, // TODO revisit this when we have the concept of the error tag type .int_signed, .int_unsigned => { const bits: u16 = if (self.cast(Payload.IntSigned)) |pl| @@ -949,6 +1029,18 @@ pub const Type = extern union { // to the child type's ABI alignment. return child_type.abiAlignment(target) + child_type.abiSize(target); }, + + .error_union => { + const payload = self.cast(Payload.ErrorUnion).?; + if (!payload.error_set.hasCodeGenBits() and !payload.payload.hasCodeGenBits()) { + return 0; + } else if (!payload.error_set.hasCodeGenBits()) { + return payload.payload.abiSize(target); + } else if (!payload.payload.hasCodeGenBits()) { + return payload.error_set.abiSize(target); + } + @panic("TODO abiSize error union"); + }, }; } @@ -1009,6 +1101,12 @@ pub const Type = extern union { .c_mut_pointer, .const_slice, .mut_slice, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => false, .single_const_pointer, @@ -1077,6 +1175,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => false, .const_slice, @@ -1142,6 +1246,12 @@ pub const Type = extern union { .optional_single_const_pointer, .enum_literal, .mut_slice, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => false, .single_const_pointer, @@ -1216,6 +1326,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => false, .pointer => { @@ -1327,6 +1443,12 @@ pub const Type = extern union { .optional_single_const_pointer, .optional_single_mut_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, .array => self.cast(Payload.Array).?.elem_type, @@ -1448,6 +1570,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, .array => self.cast(Payload.Array).?.len, @@ -1515,6 +1643,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, .array, .array_u8 => return null, @@ -1580,6 +1714,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => false, .int_signed, @@ -1648,6 +1788,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => false, .int_unsigned, @@ -1706,6 +1852,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, .int_unsigned => .{ .signed = false, .bits = self.cast(Payload.IntUnsigned).?.bits }, @@ -1782,6 +1934,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => false, .usize, @@ -1887,6 +2045,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, }; } @@ -1958,6 +2122,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, } } @@ -2028,6 +2198,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, } } @@ -2098,6 +2274,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, }; } @@ -2165,6 +2347,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, }; } @@ -2232,6 +2420,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => unreachable, }; } @@ -2299,6 +2493,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => false, }; } @@ -2350,6 +2550,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .anyerror_void_error_union, + .anyframe_T, + .@"anyframe", + .error_union, + .error_set, + .error_set_single, => return null, .void => return Value.initTag(.void_value), @@ -2453,6 +2659,12 @@ pub const Type = extern union { .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, + .error_union, + .@"anyframe", + .anyframe_T, + .anyerror_void_error_union, + .error_set, + .error_set_single, => return false, .c_const_pointer, @@ -2510,6 +2722,8 @@ pub const Type = extern union { fn_naked_noreturn_no_args, fn_ccc_void_no_args, single_const_pointer_to_comptime_int, + anyerror_void_error_union, + @"anyframe", const_slice_u8, // See last_no_payload_tag below. // After this, the tag requires a payload. @@ -2532,6 +2746,10 @@ pub const Type = extern union { optional, optional_single_mut_pointer, optional_single_const_pointer, + error_union, + anyframe_T, + error_set, + error_set_single, pub const last_no_payload_tag = Tag.const_slice_u8; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -2613,6 +2831,32 @@ pub const Type = extern union { @"volatile": bool, size: std.builtin.TypeInfo.Pointer.Size, }; + + pub const ErrorUnion = struct { + base: Payload = .{ .tag = .error_union }, + + error_set: Type, + payload: Type, + }; + + pub const AnyFrame = struct { + base: Payload = .{ .tag = .anyframe_T }, + + return_type: Type, + }; + + pub const ErrorSet = struct { + base: Payload = .{ .tag = .error_set }, + + decl: *Module.Decl, + }; + + pub const ErrorSetSingle = struct { + base: Payload = .{ .tag = .error_set_single }, + + /// memory is owned by `Module` + name: []const u8, + }; }; }; diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 95a0746e19..f2e36b59a4 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -61,6 +61,7 @@ pub const Value = extern union { single_const_pointer_to_comptime_int_type, const_slice_u8_type, enum_literal_type, + anyframe_type, undef, zero, @@ -90,6 +91,8 @@ pub const Value = extern union { float_64, float_128, enum_literal, + error_set, + @"error", pub const last_no_payload_tag = Tag.bool_false; pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; @@ -168,6 +171,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .undef, .zero, .void_value, @@ -241,6 +245,10 @@ pub const Value = extern union { }; return Value{ .ptr_otherwise = &new_payload.base }; }, + .@"error" => return self.copyPayloadShallow(allocator, Payload.Error), + + // memory is managed by the declaration + .error_set => return self.copyPayloadShallow(allocator, Payload.ErrorSet), } } @@ -300,6 +308,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"), .const_slice_u8_type => return out_stream.writeAll("[]const u8"), .enum_literal_type => return out_stream.writeAll("@TypeOf(.EnumLiteral)"), + .anyframe_type => return out_stream.writeAll("anyframe"), .null_value => return out_stream.writeAll("null"), .undef => return out_stream.writeAll("undefined"), @@ -343,6 +352,15 @@ pub const Value = extern union { .float_32 => return out_stream.print("{}", .{val.cast(Payload.Float_32).?.val}), .float_64 => return out_stream.print("{}", .{val.cast(Payload.Float_64).?.val}), .float_128 => return out_stream.print("{}", .{val.cast(Payload.Float_128).?.val}), + .error_set => { + const error_set = val.cast(Payload.ErrorSet).?; + try out_stream.writeAll("error{"); + for (error_set.fields.items()) |entry| { + try out_stream.print("{},", .{entry.value}); + } + return out_stream.writeAll("}"); + }, + .@"error" => return out_stream.print("error.{}", .{val.cast(Payload.Error).?.name}), }; } @@ -363,11 +381,9 @@ pub const Value = extern union { } /// Asserts that the value is representable as a type. - pub fn toType(self: Value) Type { + pub fn toType(self: Value, allocator: *Allocator) !Type { return switch (self.tag()) { .ty => self.cast(Payload.Ty).?.ty, - .int_type => @panic("TODO int type to type"), - .u8_type => Type.initTag(.u8), .i8_type => Type.initTag(.i8), .u16_type => Type.initTag(.u16), @@ -408,6 +424,26 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int), .const_slice_u8_type => Type.initTag(.const_slice_u8), .enum_literal_type => Type.initTag(.enum_literal), + .anyframe_type => Type.initTag(.@"anyframe"), + + .int_type => { + const payload = self.cast(Payload.IntType).?; + if (payload.signed) { + const new = try allocator.create(Type.Payload.IntSigned); + new.* = .{ .bits = payload.bits }; + return Type.initPayload(&new.base); + } else { + const new = try allocator.create(Type.Payload.IntUnsigned); + new.* = .{ .bits = payload.bits }; + return Type.initPayload(&new.base); + } + }, + .error_set => { + const payload = self.cast(Payload.ErrorSet).?; + const new = try allocator.create(Type.Payload.ErrorSet); + new.* = .{ .decl = payload.decl }; + return Type.initPayload(&new.base); + }, .undef, .zero, @@ -433,6 +469,7 @@ pub const Value = extern union { .float_64, .float_128, .enum_literal, + .@"error", => unreachable, }; } @@ -482,6 +519,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .null_value, .function, .variable, @@ -498,6 +536,8 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, + .@"error", => unreachable, .undef => unreachable, @@ -560,6 +600,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .null_value, .function, .variable, @@ -576,6 +617,8 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, + .@"error", => unreachable, .undef => unreachable, @@ -638,6 +681,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .null_value, .function, .variable, @@ -654,6 +698,8 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, + .@"error", => unreachable, .undef => unreachable, @@ -742,6 +788,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .null_value, .function, .variable, @@ -759,6 +806,8 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, + .@"error", => unreachable, .zero, @@ -825,6 +874,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .null_value, .function, .variable, @@ -841,6 +891,8 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, + .@"error", => unreachable, .zero, @@ -988,6 +1040,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .bool_true, .bool_false, .null_value, @@ -1007,6 +1060,8 @@ pub const Value = extern union { .void_value, .unreachable_value, .enum_literal, + .error_set, + .@"error", => unreachable, .zero => false, @@ -1063,6 +1118,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .null_value, .function, .variable, @@ -1076,6 +1132,8 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, + .@"error", => unreachable, .zero, @@ -1197,6 +1255,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .zero, .bool_true, .bool_false, @@ -1218,6 +1277,8 @@ pub const Value = extern union { .unreachable_value, .empty_array, .enum_literal, + .error_set, + .@"error", => unreachable, .ref_val => self.cast(Payload.RefVal).?.val, @@ -1276,6 +1337,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .zero, .bool_true, .bool_false, @@ -1297,6 +1359,8 @@ pub const Value = extern union { .void_value, .unreachable_value, .enum_literal, + .error_set, + .@"error", => unreachable, .empty_array => unreachable, // out of bounds array index @@ -1372,6 +1436,7 @@ pub const Value = extern union { .single_const_pointer_to_comptime_int_type, .const_slice_u8_type, .enum_literal_type, + .anyframe_type, .zero, .empty_array, .bool_true, @@ -1393,6 +1458,8 @@ pub const Value = extern union { .float_128, .void_value, .enum_literal, + .error_set, + .@"error", => false, .undef => unreachable, @@ -1522,6 +1589,24 @@ pub const Value = extern union { base: Payload = .{ .tag = .float_128 }, val: f128, }; + + pub const ErrorSet = struct { + base: Payload = .{ .tag = .error_set }, + + // TODO revisit this when we have the concept of the error tag type + fields: std.StringHashMapUnmanaged(u16), + decl: *Module.Decl, + }; + + pub const Error = struct { + base: Payload = .{ .tag = .@"error" }, + + // TODO revisit this when we have the concept of the error tag type + /// `name` is owned by `Module` and will be valid for the entire + /// duration of the compilation. + name: []const u8, + value: u16, + }; }; /// Big enough to fit any non-BigInt value diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index a3ea1f11ab..3557c88f4e 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -43,6 +43,8 @@ pub const Inst = struct { alloc, /// Same as `alloc` except the type is inferred. alloc_inferred, + /// Create an `anyframe->T`. + anyframe_type, /// Array concatenation. `a ++ b` array_cat, /// Array multiplication `a ** b` @@ -70,6 +72,8 @@ pub const Inst = struct { /// A typed result location pointer is bitcasted to a new result location pointer. /// The new result location pointer has an inferred type. bitcast_result_ptr, + /// Bitwise NOT. `~` + bitnot, /// Bitwise OR. `|` bitor, /// A labeled block of code, which can return a value. @@ -133,6 +137,10 @@ pub const Inst = struct { ensure_result_used, /// Emits a compile error if an error is ignored. ensure_result_non_error, + /// Create a `E!T` type. + error_union_type, + /// Create an error set. + error_set, /// Export the provided Decl as the provided name in the compilation's output object file. @"export", /// Given a pointer to a struct or object that contains virtual fields, returns a pointer @@ -160,6 +168,8 @@ pub const Inst = struct { /// A labeled block of code that loops forever. At the end of the body it is implied /// to repeat; no explicit "repeat" instruction terminates loop bodies. loop, + /// Merge two error sets into one, `E1 || E2`. + merge_error_sets, /// Ambiguously remainder division or modulus. If the computation would possibly have /// a different value depending on whether the operation is remainder division or modulus, /// a compile error is emitted. Otherwise the computation is performed. @@ -286,6 +296,8 @@ pub const Inst = struct { .unwrap_err_safe, .unwrap_err_unsafe, .ensure_err_payload_void, + .anyframe_type, + .bitnot, => UnOp, .add, @@ -316,6 +328,8 @@ pub const Inst = struct { .bitcast, .coerce_result_ptr, .xor, + .error_union_type, + .merge_error_sets, => BinOp, .arg => Arg, @@ -347,6 +361,7 @@ pub const Inst = struct { .condbr => CondBr, .ptr_type => PtrType, .enum_literal => EnumLiteral, + .error_set => ErrorSet, }; } @@ -438,6 +453,11 @@ pub const Inst = struct { .ptr_type, .ensure_err_payload_void, .enum_literal, + .merge_error_sets, + .anyframe_type, + .error_union_type, + .bitnot, + .error_set, => false, .@"break", @@ -908,6 +928,16 @@ pub const Inst = struct { }, kw_args: struct {}, }; + + pub const ErrorSet = struct { + pub const base_tag = Tag.error_set; + base: Inst, + + positionals: struct { + fields: [][]const u8, + }, + kw_args: struct {}, + }; }; pub const ErrorMsg = struct { @@ -1142,6 +1172,16 @@ const Writer = struct { const name = self.loop_table.get(param).?; return std.zig.renderStringLiteral(name, stream); }, + [][]const u8 => { + try stream.writeByte('['); + for (param) |str, i| { + if (i != 0) { + try stream.writeAll(", "); + } + try std.zig.renderStringLiteral(str, stream); + } + try stream.writeByte(']'); + }, else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } @@ -1539,6 +1579,21 @@ const Parser = struct { const name = try self.parseStringLiteral(); return self.loop_table.get(name).?; }, + [][]const u8 => { + try requireEatBytes(self, "["); + skipSpace(self); + if (eatByte(self, ']')) return &[0][]const u8{}; + + var strings = std.ArrayList([]const u8).init(&self.arena.allocator); + while (true) { + skipSpace(self); + try strings.append(try self.parseStringLiteral()); + skipSpace(self); + if (!eatByte(self, ',')) break; + } + try requireEatBytes(self, "]"); + return strings.toOwnedSlice(); + }, else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), } return self.fail("TODO parse parameter {}", .{@typeName(T)}); @@ -1961,7 +2016,7 @@ const EmitZIR = struct { return self.emitUnnamedDecl(&as_inst.base); }, .Type => { - const ty = typed_value.val.toType(); + const ty = try typed_value.val.toType(&self.arena.allocator); return self.emitType(src, ty); }, .Fn => { diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig index 75ec7f6306..7106bda090 100644 --- a/src-self-hosted/zir_sema.zig +++ b/src-self-hosted/zir_sema.zig @@ -97,6 +97,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .array_cat => return analyzeInstArrayCat(mod, scope, old_inst.castTag(.array_cat).?), .array_mul => return analyzeInstArrayMul(mod, scope, old_inst.castTag(.array_mul).?), .bitand => return analyzeInstBitwise(mod, scope, old_inst.castTag(.bitand).?), + .bitnot => return analyzeInstBitNot(mod, scope, old_inst.castTag(.bitnot).?), .bitor => return analyzeInstBitwise(mod, scope, old_inst.castTag(.bitor).?), .xor => return analyzeInstBitwise(mod, scope, old_inst.castTag(.xor).?), .shl => return analyzeInstShl(mod, scope, old_inst.castTag(.shl).?), @@ -122,6 +123,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .array_type => return analyzeInstArrayType(mod, scope, old_inst.castTag(.array_type).?), .array_type_sentinel => return analyzeInstArrayTypeSentinel(mod, scope, old_inst.castTag(.array_type_sentinel).?), .enum_literal => return analyzeInstEnumLiteral(mod, scope, old_inst.castTag(.enum_literal).?), + .merge_error_sets => return analyzeInstMergeErrorSets(mod, scope, old_inst.castTag(.merge_error_sets).?), + .error_union_type => return analyzeInstErrorUnionType(mod, scope, old_inst.castTag(.error_union_type).?), + .anyframe_type => return analyzeInstAnyframeType(mod, scope, old_inst.castTag(.anyframe_type).?), + .error_set => return analyzeInstErrorSet(mod, scope, old_inst.castTag(.error_set).?), } } @@ -145,7 +150,7 @@ pub fn analyzeBodyValueAsType(mod: *Module, block_scope: *Scope.Block, body: zir for (block_scope.instructions.items) |inst| { if (inst.castTag(.ret)) |ret| { const val = try mod.resolveConstValue(&block_scope.base, ret.operand); - return val.toType(); + return val.toType(block_scope.base.arena()); } else { return mod.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); } @@ -270,7 +275,7 @@ fn resolveType(mod: *Module, scope: *Scope, old_inst: *zir.Inst) !Type { const wanted_type = Type.initTag(.@"type"); const coerced_inst = try mod.coerce(scope, wanted_type, new_inst); const val = try mod.resolveConstValue(scope, coerced_inst); - return val.toType(); + return val.toType(scope.arena()); } fn resolveInt(mod: *Module, scope: *Scope, old_inst: *zir.Inst, dest_type: Type) !u64 { @@ -431,6 +436,7 @@ fn analyzeInstStr(mod: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerErr // The bytes references memory inside the ZIR module, which can get deallocated // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa); + errdefer new_decl_arena.deinit(); const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); @@ -716,6 +722,54 @@ fn analyzeInstArrayTypeSentinel(mod: *Module, scope: *Scope, array: *zir.Inst.Ar return mod.constType(scope, array.base.src, try mod.arrayType(scope, len.val.toUnsignedInt(), sentinel.val, elem_type)); } +fn analyzeInstErrorUnionType(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const error_union = try resolveType(mod, scope, inst.positionals.lhs); + const payload = try resolveType(mod, scope, inst.positionals.rhs); + + if (error_union.zigTypeTag() != .ErrorSet) { + return mod.fail(scope, inst.base.src, "expected error set type, found {}", .{error_union.elemType()}); + } + + return mod.constType(scope, inst.base.src, try mod.errorUnionType(scope, error_union, payload)); +} + +fn analyzeInstAnyframeType(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const return_type = try resolveType(mod, scope, inst.positionals.operand); + + return mod.constType(scope, inst.base.src, try mod.anyframeType(scope, return_type)); +} + +fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) InnerError!*Inst { + // The declarations arena will store the hashmap. + var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa); + errdefer new_decl_arena.deinit(); + + const payload = try scope.arena().create(Value.Payload.ErrorSet); + payload.* = .{ + .fields = .{}, + .decl = undefined, // populated below + }; + try payload.fields.ensureCapacity(&new_decl_arena.allocator, inst.positionals.fields.len); + + for (inst.positionals.fields) |field_name| { + const entry = try mod.getErrorValue(field_name); + if (payload.fields.fetchPutAssumeCapacity(entry.key, entry.value)) |prev| { + return mod.fail(scope, inst.base.src, "duplicate error: '{}'", .{field_name}); + } + } + // TODO create name in format "error:line:column" + const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{ + .ty = Type.initTag(.type), + .val = Value.initPayload(&payload.base), + }); + payload.decl = new_decl; + return mod.analyzeDeclRef(scope, inst.base.src, new_decl); +} + +fn analyzeInstMergeErrorSets(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement merge_error_sets", .{}); +} + fn analyzeInstEnumLiteral(mod: *Module, scope: *Scope, inst: *zir.Inst.EnumLiteral) InnerError!*Inst { const payload = try scope.arena().create(Value.Payload.Bytes); payload.* = .{ @@ -858,8 +912,72 @@ fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr ); } }, - else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}), + .Pointer => { + const ptr_child = elem_ty.elemType(); + switch (ptr_child.zigTypeTag()) { + .Array => { + if (mem.eql(u8, field_name, "len")) { + const len_payload = try scope.arena().create(Value.Payload.Int_u64); + len_payload.* = .{ .int = ptr_child.arrayLen() }; + + const ref_payload = try scope.arena().create(Value.Payload.RefVal); + ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) }; + + return mod.constInst(scope, fieldptr.base.src, .{ + .ty = Type.initTag(.single_const_pointer_to_comptime_int), + .val = Value.initPayload(&ref_payload.base), + }); + } else { + return mod.fail( + scope, + fieldptr.positionals.field_name.src, + "no member named '{}' in '{}'", + .{ field_name, elem_ty }, + ); + } + }, + else => {}, + } + }, + .Type => { + _ = try mod.resolveConstValue(scope, object_ptr); + const result = try mod.analyzeDeref(scope, fieldptr.base.src, object_ptr, object_ptr.src); + const val = result.value().?; + const child_type = try val.toType(scope.arena()); + switch (child_type.zigTypeTag()) { + .ErrorSet => { + // TODO resolve inferred error sets + const entry = if (val.cast(Value.Payload.ErrorSet)) |payload| + (payload.fields.getEntry(field_name) orelse + return mod.fail(scope, fieldptr.base.src, "no error named '{}' in '{}'", .{ field_name, child_type })).* + else try mod.getErrorValue(field_name); + + const error_payload = try scope.arena().create(Value.Payload.Error); + error_payload.* = .{ + .name = entry.key, + .value = entry.value, + }; + + const ref_payload = try scope.arena().create(Value.Payload.RefVal); + ref_payload.* = .{ .val = Value.initPayload(&error_payload.base) }; + + const result_type = if (child_type.tag() == .anyerror) blk: { + const result_payload = try scope.arena().create(Type.Payload.ErrorSetSingle); + result_payload.* = .{ .name = entry.key }; + break :blk Type.initPayload(&result_payload.base); + } else child_type; + + return mod.constInst(scope, fieldptr.base.src, .{ + .ty = try mod.simplePtrType(scope, fieldptr.base.src, result_type, false, .One), + .val = Value.initPayload(&ref_payload.base), + }); + }, + else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{child_type}), + } + }, + else => {}, } + return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}); } fn analyzeInstIntCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { @@ -983,6 +1101,10 @@ fn analyzeInstBitwise(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerE return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitwise", .{}); } +fn analyzeInstBitNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitNot", .{}); +} + fn analyzeInstArrayCat(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { return mod.fail(scope, inst.base.src, "TODO implement analyzeInstArrayCat", .{}); } @@ -1348,7 +1470,7 @@ fn analyzeInstPtrType(mod: *Module, scope: *Scope, inst: *zir.Inst.PtrType) Inne if (host_size != 0 and bit_offset >= host_size * 8) return mod.fail(scope, inst.base.src, "bit offset starts after end of host integer", .{}); - + const sentinel = if (inst.kw_args.sentinel) |some| (try resolveInstConst(mod, scope, some)).val else diff --git a/src/analyze.cpp b/src/analyze.cpp index 7b71d7166b..acdbf3e933 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2586,7 +2586,6 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { return ErrorNone; AstNode *decl_node = enum_type->data.enumeration.decl_node; - assert(decl_node->type == NodeTypeContainerDecl); if (enum_type->data.enumeration.resolve_loop_flag) { if (enum_type->data.enumeration.resolve_status != ResolveStatusInvalid) { @@ -2600,15 +2599,20 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { enum_type->data.enumeration.resolve_loop_flag = true; - assert(!enum_type->data.enumeration.fields); - uint32_t field_count = (uint32_t)decl_node->data.container_decl.fields.length; - if (field_count == 0) { - add_node_error(g, decl_node, buf_sprintf("enums must have 1 or more fields")); + uint32_t field_count; + if (decl_node->type == NodeTypeContainerDecl) { + assert(!enum_type->data.enumeration.fields); + field_count = (uint32_t)decl_node->data.container_decl.fields.length; + if (field_count == 0) { + add_node_error(g, decl_node, buf_sprintf("enums must have 1 or more fields")); - enum_type->data.enumeration.src_field_count = field_count; - enum_type->data.enumeration.fields = nullptr; - enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; - return ErrorSemanticAnalyzeFail; + enum_type->data.enumeration.src_field_count = field_count; + enum_type->data.enumeration.fields = nullptr; + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + return ErrorSemanticAnalyzeFail; + } + } else { + field_count = enum_type->data.enumeration.src_field_count; } Scope *scope = &enum_type->data.enumeration.decls_scope->base; @@ -2624,8 +2628,16 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { enum_type->abi_size = tag_int_type->abi_size; enum_type->abi_align = tag_int_type->abi_align; - if (decl_node->data.container_decl.init_arg_expr != nullptr) { - ZigType *wanted_tag_int_type = analyze_type_expr(g, scope, decl_node->data.container_decl.init_arg_expr); + ZigType *wanted_tag_int_type = nullptr; + if (decl_node->type == NodeTypeContainerDecl) { + if (decl_node->data.container_decl.init_arg_expr != nullptr) { + wanted_tag_int_type = analyze_type_expr(g, scope, decl_node->data.container_decl.init_arg_expr); + } + } else { + wanted_tag_int_type = enum_type->data.enumeration.tag_int_type; + } + + if (wanted_tag_int_type != nullptr) { if (type_is_invalid(wanted_tag_int_type)) { enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; } else if (wanted_tag_int_type->id != ZigTypeIdInt && @@ -2654,7 +2666,6 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { } } - enum_type->data.enumeration.non_exhaustive = false; enum_type->data.enumeration.tag_int_type = tag_int_type; enum_type->size_in_bits = tag_int_type->size_in_bits; enum_type->abi_size = tag_int_type->abi_size; @@ -2663,121 +2674,131 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { BigInt bi_one; bigint_init_unsigned(&bi_one, 1); - AstNode *last_field_node = decl_node->data.container_decl.fields.at(field_count - 1); - if (buf_eql_str(last_field_node->data.struct_field.name, "_")) { - field_count -= 1; - if (field_count > 1 && log2_u64(field_count) == enum_type->size_in_bits) { - add_node_error(g, last_field_node, buf_sprintf("non-exhaustive enum specifies every value")); - enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + if (decl_node->type == NodeTypeContainerDecl) { + AstNode *last_field_node = decl_node->data.container_decl.fields.at(field_count - 1); + if (buf_eql_str(last_field_node->data.struct_field.name, "_")) { + if (last_field_node->data.struct_field.value != nullptr) { + add_node_error(g, last_field_node, buf_sprintf("value assigned to '_' field of non-exhaustive enum")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + } + if (decl_node->data.container_decl.init_arg_expr == nullptr) { + add_node_error(g, decl_node, buf_sprintf("non-exhaustive enum must specify size")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + } + enum_type->data.enumeration.non_exhaustive = true; + } else { + enum_type->data.enumeration.non_exhaustive = false; } - if (decl_node->data.container_decl.init_arg_expr == nullptr) { - add_node_error(g, last_field_node, buf_sprintf("non-exhaustive enum must specify size")); - enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; - } - if (last_field_node->data.struct_field.value != nullptr) { - add_node_error(g, last_field_node, buf_sprintf("value assigned to '_' field of non-exhaustive enum")); - enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; - } - enum_type->data.enumeration.non_exhaustive = true; } - enum_type->data.enumeration.src_field_count = field_count; - enum_type->data.enumeration.fields = heap::c_allocator.allocate(field_count); - enum_type->data.enumeration.fields_by_name.init(field_count); - - HashMap occupied_tag_values = {}; - occupied_tag_values.init(field_count); - - TypeEnumField *last_enum_field = nullptr; - - for (uint32_t field_i = 0; field_i < field_count; field_i += 1) { - AstNode *field_node = decl_node->data.container_decl.fields.at(field_i); - TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_i]; - type_enum_field->name = field_node->data.struct_field.name; - type_enum_field->decl_index = field_i; - type_enum_field->decl_node = field_node; - - if (field_node->data.struct_field.type != nullptr) { - ErrorMsg *msg = add_node_error(g, field_node->data.struct_field.type, - buf_sprintf("structs and unions, not enums, support field types")); - add_error_note(g, msg, decl_node, - buf_sprintf("consider 'union(enum)' here")); - } else if (field_node->data.struct_field.align_expr != nullptr) { - ErrorMsg *msg = add_node_error(g, field_node->data.struct_field.align_expr, - buf_sprintf("structs and unions, not enums, support field alignment")); - add_error_note(g, msg, decl_node, - buf_sprintf("consider 'union(enum)' here")); - } - - if (buf_eql_str(type_enum_field->name, "_")) { - add_node_error(g, field_node, buf_sprintf("'_' field of non-exhaustive enum must be last")); + if (enum_type->data.enumeration.non_exhaustive) { + field_count -= 1; + if (field_count > 1 && log2_u64(field_count) == enum_type->size_in_bits) { + add_node_error(g, decl_node, buf_sprintf("non-exhaustive enum specifies every value")); enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; } + } - auto field_entry = enum_type->data.enumeration.fields_by_name.put_unique(type_enum_field->name, type_enum_field); - if (field_entry != nullptr) { - ErrorMsg *msg = add_node_error(g, field_node, - buf_sprintf("duplicate enum field: '%s'", buf_ptr(type_enum_field->name))); - add_error_note(g, msg, field_entry->value->decl_node, buf_sprintf("other field here")); - enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; - continue; - } + if (decl_node->type == NodeTypeContainerDecl) { + enum_type->data.enumeration.src_field_count = field_count; + enum_type->data.enumeration.fields = heap::c_allocator.allocate(field_count); + enum_type->data.enumeration.fields_by_name.init(field_count); - AstNode *tag_value = field_node->data.struct_field.value; + HashMap occupied_tag_values = {}; + occupied_tag_values.init(field_count); - if (tag_value != nullptr) { - // A user-specified value is available - ZigValue *result = analyze_const_value(g, scope, tag_value, tag_int_type, - nullptr, UndefBad); - if (type_is_invalid(result->type)) { + TypeEnumField *last_enum_field = nullptr; + + for (uint32_t field_i = 0; field_i < field_count; field_i += 1) { + AstNode *field_node = decl_node->data.container_decl.fields.at(field_i); + TypeEnumField *type_enum_field = &enum_type->data.enumeration.fields[field_i]; + type_enum_field->name = field_node->data.struct_field.name; + type_enum_field->decl_index = field_i; + type_enum_field->decl_node = field_node; + + if (field_node->data.struct_field.type != nullptr) { + ErrorMsg *msg = add_node_error(g, field_node->data.struct_field.type, + buf_sprintf("structs and unions, not enums, support field types")); + add_error_note(g, msg, decl_node, + buf_sprintf("consider 'union(enum)' here")); + } else if (field_node->data.struct_field.align_expr != nullptr) { + ErrorMsg *msg = add_node_error(g, field_node->data.struct_field.align_expr, + buf_sprintf("structs and unions, not enums, support field alignment")); + add_error_note(g, msg, decl_node, + buf_sprintf("consider 'union(enum)' here")); + } + + if (buf_eql_str(type_enum_field->name, "_")) { + add_node_error(g, field_node, buf_sprintf("'_' field of non-exhaustive enum must be last")); + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + } + + auto field_entry = enum_type->data.enumeration.fields_by_name.put_unique(type_enum_field->name, type_enum_field); + if (field_entry != nullptr) { + ErrorMsg *msg = add_node_error(g, field_node, + buf_sprintf("duplicate enum field: '%s'", buf_ptr(type_enum_field->name))); + add_error_note(g, msg, field_entry->value->decl_node, buf_sprintf("other field here")); enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; continue; } - assert(result->special != ConstValSpecialRuntime); - assert(result->type->id == ZigTypeIdInt || result->type->id == ZigTypeIdComptimeInt); + AstNode *tag_value = field_node->data.struct_field.value; - bigint_init_bigint(&type_enum_field->value, &result->data.x_bigint); - } else { - // No value was explicitly specified: allocate the last value + 1 - // or, if this is the first element, zero - if (last_enum_field != nullptr) { - bigint_add(&type_enum_field->value, &last_enum_field->value, &bi_one); + if (tag_value != nullptr) { + // A user-specified value is available + ZigValue *result = analyze_const_value(g, scope, tag_value, tag_int_type, + nullptr, UndefBad); + if (type_is_invalid(result->type)) { + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + continue; + } + + assert(result->special != ConstValSpecialRuntime); + assert(result->type->id == ZigTypeIdInt || result->type->id == ZigTypeIdComptimeInt); + + bigint_init_bigint(&type_enum_field->value, &result->data.x_bigint); } else { - bigint_init_unsigned(&type_enum_field->value, 0); + // No value was explicitly specified: allocate the last value + 1 + // or, if this is the first element, zero + if (last_enum_field != nullptr) { + bigint_add(&type_enum_field->value, &last_enum_field->value, &bi_one); + } else { + bigint_init_unsigned(&type_enum_field->value, 0); + } + + // Make sure we can represent this number with tag_int_type + if (!bigint_fits_in_bits(&type_enum_field->value, + tag_int_type->size_in_bits, + tag_int_type->data.integral.is_signed)) { + enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; + + Buf *val_buf = buf_alloc(); + bigint_append_buf(val_buf, &type_enum_field->value, 10); + add_node_error(g, field_node, + buf_sprintf("enumeration value %s too large for type '%s'", + buf_ptr(val_buf), buf_ptr(&tag_int_type->name))); + + break; + } } - // Make sure we can represent this number with tag_int_type - if (!bigint_fits_in_bits(&type_enum_field->value, - tag_int_type->size_in_bits, - tag_int_type->data.integral.is_signed)) { + // Make sure the value is unique + auto entry = occupied_tag_values.put_unique(type_enum_field->value, field_node); + if (entry != nullptr && enum_type->data.enumeration.layout != ContainerLayoutExtern) { enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; Buf *val_buf = buf_alloc(); bigint_append_buf(val_buf, &type_enum_field->value, 10); - add_node_error(g, field_node, - buf_sprintf("enumeration value %s too large for type '%s'", - buf_ptr(val_buf), buf_ptr(&tag_int_type->name))); - break; + ErrorMsg *msg = add_node_error(g, field_node, + buf_sprintf("enum tag value %s already taken", buf_ptr(val_buf))); + add_error_note(g, msg, entry->value, + buf_sprintf("other occurrence here")); } + + last_enum_field = type_enum_field; } - - // Make sure the value is unique - auto entry = occupied_tag_values.put_unique(type_enum_field->value, field_node); - if (entry != nullptr && enum_type->data.enumeration.layout != ContainerLayoutExtern) { - enum_type->data.enumeration.resolve_status = ResolveStatusInvalid; - - Buf *val_buf = buf_alloc(); - bigint_append_buf(val_buf, &type_enum_field->value, 10); - - ErrorMsg *msg = add_node_error(g, field_node, - buf_sprintf("enum tag value %s already taken", buf_ptr(val_buf))); - add_error_note(g, msg, entry->value, - buf_sprintf("other occurrence here")); - } - - last_enum_field = type_enum_field; + occupied_tag_values.deinit(); } if (enum_type->data.enumeration.resolve_status == ResolveStatusInvalid) @@ -2786,8 +2807,6 @@ static Error resolve_enum_zero_bits(CodeGen *g, ZigType *enum_type) { enum_type->data.enumeration.resolve_loop_flag = false; enum_type->data.enumeration.resolve_status = ResolveStatusSizeKnown; - occupied_tag_values.deinit(); - return ErrorNone; } diff --git a/src/ir.cpp b/src/ir.cpp index b6e6fcaf5e..914ae0f442 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -2147,6 +2147,7 @@ static IrInstSrc *ir_build_const_undefined(IrBuilderSrc *irb, Scope *scope, AstN IrInstSrcConst *const_instruction = ir_create_instruction(irb, scope, source_node); ir_instruction_append(irb->current_basic_block, &const_instruction->base); const_instruction->value = irb->codegen->intern.for_undefined(); + const_instruction->value->special = ConstValSpecialUndef; return &const_instruction->base; } @@ -14917,6 +14918,9 @@ static IrInstGen *ir_analyze_struct_literal_to_struct(IrAnalyze *ira, IrInst* so field_val->parent.data.p_struct.struct_val = const_result->value; field_val->parent.data.p_struct.field_index = dst_field->src_index; field_values[dst_field->src_index] = field_val; + if (field_val->type->id == ZigTypeIdUndefined && dst_field->type_entry->id != ZigTypeIdUndefined) { + field_values[dst_field->src_index]->special = ConstValSpecialUndef; + } } else { is_comptime = false; } @@ -15649,7 +15653,7 @@ static IrInstGen *ir_analyze_cast(IrAnalyze *ira, IrInst *source_instr, wanted_type->data.array.len == field_count) { return ir_analyze_struct_literal_to_array(ira, source_instr, value, wanted_type); - } else if (wanted_type->id == ZigTypeIdStruct && + } else if (wanted_type->id == ZigTypeIdStruct && !is_slice(wanted_type) && (!is_array_init || field_count == 0)) { return ir_analyze_struct_literal_to_struct(ira, source_instr, value, wanted_type); @@ -20692,8 +20696,13 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, if ((return_type->id == ZigTypeIdErrorUnion || return_type->id == ZigTypeIdErrorSet) && expected_return_type->id != ZigTypeIdErrorUnion && expected_return_type->id != ZigTypeIdErrorSet) { - add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg, - ira->explicit_return_type_source_node, buf_create_from_str("function cannot return an error")); + if (call_result_loc->id == ResultLocIdReturn) { + add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg, + ira->explicit_return_type_source_node, buf_sprintf("function cannot return an error")); + } else { + add_error_note(ira->codegen, ira->new_irb.exec->first_err_trace_msg, result_loc->base.source_node, + buf_sprintf("cannot store an error in type '%s'", buf_ptr(&expected_return_type->name))); + } } return ira->codegen->invalid_inst_gen; } @@ -22302,6 +22311,7 @@ static IrInstGen *ir_analyze_container_member_access_inner(IrAnalyze *ira, static void memoize_field_init_val(CodeGen *codegen, ZigType *container_type, TypeStructField *field) { if (field->init_val != nullptr) return; + if (field->decl_node == nullptr) return; if (field->decl_node->type != NodeTypeStructField) return; AstNode *init_node = field->decl_node->data.struct_field.value; if (init_node == nullptr) return; @@ -25495,9 +25505,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy error_val->special = ConstValSpecialStatic; error_val->type = type_info_error_type; - ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 2); - inner_fields[1]->special = ConstValSpecialStatic; - inner_fields[1]->type = ira->codegen->builtin_types.entry_num_lit_int; + ZigValue **inner_fields = alloc_const_vals_ptrs(ira->codegen, 1); ZigValue *name = nullptr; if (error->cached_error_name_val != nullptr) @@ -25505,7 +25513,6 @@ static Error ir_make_type_info_value(IrAnalyze *ira, IrInst* source_instr, ZigTy if (name == nullptr) name = create_const_str_lit(ira->codegen, &error->name)->data.x_ptr.data.ref.pointee; init_const_slice(ira->codegen, inner_fields[0], name, 0, buf_len(&error->name), true); - bigint_init_unsigned(&inner_fields[1]->data.x_bigint, error->value); error_val->data.x_struct.fields = inner_fields; error_val->parent.id = ConstParentIdArray; @@ -26020,6 +26027,9 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI assert(payload->special == ConstValSpecialStatic); assert(payload->type == type_info_pointer_type); ZigValue *size_value = get_const_field(ira, source_instr->source_node, payload, "size", 0); + if (size_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + assert(size_value->type == ir_type_info_get_type(ira, "Size", type_info_pointer_type)); BuiltinPtrSize size_enum_index = (BuiltinPtrSize)bigint_as_u32(&size_value->data.x_enum_tag); PtrLen ptr_len = size_enum_index_to_ptr_len(size_enum_index); @@ -26103,13 +26113,21 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI assert(payload->special == ConstValSpecialStatic); assert(payload->type == ir_type_info_get_type(ira, "Optional", nullptr)); ZigType *child_type = get_const_field_meta_type(ira, source_instr->source_node, payload, "child", 0); + if (type_is_invalid(child_type)) + return ira->codegen->invalid_inst_gen->value->type; return get_optional_type(ira->codegen, child_type); } case ZigTypeIdErrorUnion: { assert(payload->special == ConstValSpecialStatic); assert(payload->type == ir_type_info_get_type(ira, "ErrorUnion", nullptr)); ZigType *err_set_type = get_const_field_meta_type(ira, source_instr->source_node, payload, "error_set", 0); + if (type_is_invalid(err_set_type)) + return ira->codegen->invalid_inst_gen->value->type; + ZigType *payload_type = get_const_field_meta_type(ira, source_instr->source_node, payload, "payload", 1); + if (type_is_invalid(payload_type)) + return ira->codegen->invalid_inst_gen->value->type; + return get_error_union_type(ira->codegen, err_set_type, payload_type); } case ZigTypeIdOpaque: { @@ -26123,8 +26141,10 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI assert(payload->special == ConstValSpecialStatic); assert(payload->type == ir_type_info_get_type(ira, "Vector", nullptr)); BigInt *len = get_const_field_lit_int(ira, source_instr->source_node, payload, "len", 0); + if (len == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + ZigType *child_type = get_const_field_meta_type(ira, source_instr->source_node, payload, "child", 1); - Error err; if ((err = ir_validate_vector_elem_type(ira, source_instr->source_node, child_type))) { return ira->codegen->invalid_inst_gen->value->type; } @@ -26134,6 +26154,9 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI assert(payload->special == ConstValSpecialStatic); assert(payload->type == ir_type_info_get_type(ira, "AnyFrame", nullptr)); ZigType *child_type = get_const_field_meta_type_optional(ira, source_instr->source_node, payload, "child", 0); + if (child_type != nullptr && type_is_invalid(child_type)) + return ira->codegen->invalid_inst_gen->value->type; + return get_any_frame_type(ira->codegen, child_type); } case ZigTypeIdEnumLiteral: @@ -26142,6 +26165,9 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI assert(payload->special == ConstValSpecialStatic); assert(payload->type == ir_type_info_get_type(ira, "Frame", nullptr)); ZigValue *function = get_const_field(ira, source_instr->source_node, payload, "function", 0); + if (function == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + assert(function->type->id == ZigTypeIdFn); ZigFn *fn = function->data.x_ptr.data.fn.fn_entry; return get_fn_frame_type(ira->codegen, fn); @@ -26176,7 +26202,6 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI assert(error->type == ir_type_info_get_type(ira, "Error", nullptr)); ErrorTableEntry *err_entry = heap::c_allocator.create(); err_entry->decl_node = source_instr->source_node; - Error err; if ((err = get_const_field_buf(ira, source_instr->source_node, error, "name", 0, &err_entry->name))) return ira->codegen->invalid_inst_gen->value->type; auto existing_entry = ira->codegen->error_table.put_unique(&err_entry->name, err_entry); @@ -26203,11 +26228,15 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI assert(payload->type == ir_type_info_get_type(ira, "Struct", nullptr)); ZigValue *layout_value = get_const_field(ira, source_instr->source_node, payload, "layout", 0); + if (layout_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; assert(layout_value->special == ConstValSpecialStatic); assert(layout_value->type == ir_type_info_get_type(ira, "ContainerLayout", nullptr)); ContainerLayout layout = (ContainerLayout)bigint_as_u32(&layout_value->data.x_enum_tag); ZigValue *fields_value = get_const_field(ira, source_instr->source_node, payload, "fields", 1); + if (fields_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; assert(fields_value->special == ConstValSpecialStatic); assert(is_slice(fields_value->type)); ZigValue *fields_ptr = fields_value->data.x_struct.fields[slice_ptr_index]; @@ -26215,6 +26244,8 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI size_t fields_len = bigint_as_usize(&fields_len_value->data.x_bigint); ZigValue *decls_value = get_const_field(ira, source_instr->source_node, payload, "decls", 2); + if (decls_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; assert(decls_value->special == ConstValSpecialStatic); assert(is_slice(decls_value->type)); ZigValue *decls_len_value = decls_value->data.x_struct.fields[slice_len_index]; @@ -26225,7 +26256,8 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI } bool is_tuple; - get_const_field_bool(ira, source_instr->source_node, payload, "is_tuple", 3, &is_tuple); + if ((err = get_const_field_bool(ira, source_instr->source_node, payload, "is_tuple", 3, &is_tuple))) + return ira->codegen->invalid_inst_gen->value->type; ZigType *entry = new_type_table_entry(ZigTypeIdStruct); buf_init_from_buf(&entry->name, @@ -26253,6 +26285,8 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI return ira->codegen->invalid_inst_gen->value->type; field->decl_node = source_instr->source_node; ZigValue *type_value = get_const_field(ira, source_instr->source_node, field_value, "field_type", 1); + if (type_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; field->type_val = type_value; field->type_entry = type_value->data.x_type; if (entry->data.structure.fields_by_name.put_unique(field->name, field) != nullptr) { @@ -26260,6 +26294,8 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI return ira->codegen->invalid_inst_gen->value->type; } ZigValue *default_value = get_const_field(ira, source_instr->source_node, field_value, "default_value", 2); + if (default_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; if (default_value->type->id == ZigTypeIdNull) { field->init_val = nullptr; } else if (default_value->type->id == ZigTypeIdOptional && default_value->type->data.maybe.child_type == field->type_entry) { @@ -26277,7 +26313,87 @@ static ZigType *type_info_to_type(IrAnalyze *ira, IrInst *source_instr, ZigTypeI return entry; } - case ZigTypeIdEnum: + case ZigTypeIdEnum: { + assert(payload->special == ConstValSpecialStatic); + assert(payload->type == ir_type_info_get_type(ira, "Enum", nullptr)); + + ZigValue *layout_value = get_const_field(ira, source_instr->source_node, payload, "layout", 0); + if (layout_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + + assert(layout_value->special == ConstValSpecialStatic); + assert(layout_value->type == ir_type_info_get_type(ira, "ContainerLayout", nullptr)); + ContainerLayout layout = (ContainerLayout)bigint_as_u32(&layout_value->data.x_enum_tag); + + ZigType *tag_type = get_const_field_meta_type(ira, source_instr->source_node, payload, "tag_type", 1); + + ZigValue *fields_value = get_const_field(ira, source_instr->source_node, payload, "fields", 2); + if (fields_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + + assert(fields_value->special == ConstValSpecialStatic); + assert(is_slice(fields_value->type)); + ZigValue *fields_ptr = fields_value->data.x_struct.fields[slice_ptr_index]; + ZigValue *fields_len_value = fields_value->data.x_struct.fields[slice_len_index]; + size_t fields_len = bigint_as_usize(&fields_len_value->data.x_bigint); + + ZigValue *decls_value = get_const_field(ira, source_instr->source_node, payload, "decls", 3); + if (decls_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + + assert(decls_value->special == ConstValSpecialStatic); + assert(is_slice(decls_value->type)); + ZigValue *decls_len_value = decls_value->data.x_struct.fields[slice_len_index]; + size_t decls_len = bigint_as_usize(&decls_len_value->data.x_bigint); + if (decls_len != 0) { + ir_add_error(ira, source_instr, buf_create_from_str("TypeInfo.Enum.decls must be empty for @Type")); + return ira->codegen->invalid_inst_gen->value->type; + } + + Error err; + bool is_exhaustive; + if ((err = get_const_field_bool(ira, source_instr->source_node, payload, "is_exhaustive", 4, &is_exhaustive))) + return ira->codegen->invalid_inst_gen->value->type; + + ZigType *entry = new_type_table_entry(ZigTypeIdEnum); + buf_init_from_buf(&entry->name, + get_anon_type_name(ira->codegen, ira->old_irb.exec, "enum", source_instr->scope, source_instr->source_node, &entry->name)); + entry->data.enumeration.decl_node = source_instr->source_node; + entry->data.enumeration.tag_int_type = tag_type; + entry->data.enumeration.decls_scope = create_decls_scope( + ira->codegen, source_instr->source_node, source_instr->scope, entry, get_scope_import(source_instr->scope), &entry->name); + entry->data.enumeration.fields = heap::c_allocator.allocate(fields_len); + entry->data.enumeration.fields_by_name.init(fields_len); + entry->data.enumeration.src_field_count = fields_len; + entry->data.enumeration.layout = layout; + entry->data.enumeration.non_exhaustive = !is_exhaustive; + + assert(fields_ptr->data.x_ptr.special == ConstPtrSpecialBaseArray); + assert(fields_ptr->data.x_ptr.data.base_array.elem_index == 0); + ZigValue *fields_arr = fields_ptr->data.x_ptr.data.base_array.array_val; + assert(fields_arr->special == ConstValSpecialStatic); + assert(fields_arr->data.x_array.special == ConstArraySpecialNone); + for (size_t i = 0; i < fields_len; i++) { + ZigValue *field_value = &fields_arr->data.x_array.data.s_none.elements[i]; + assert(field_value->type == ir_type_info_get_type(ira, "EnumField", nullptr)); + TypeEnumField *field = &entry->data.enumeration.fields[i]; + field->name = buf_alloc(); + if ((err = get_const_field_buf(ira, source_instr->source_node, field_value, "name", 0, field->name))) + return ira->codegen->invalid_inst_gen->value->type; + field->decl_index = i; + field->decl_node = source_instr->source_node; + if (entry->data.enumeration.fields_by_name.put_unique(field->name, field) != nullptr) { + ir_add_error(ira, source_instr, buf_sprintf("duplicate enum field '%s'", buf_ptr(field->name))); + return ira->codegen->invalid_inst_gen->value->type; + } + BigInt *field_int_value = get_const_field_lit_int(ira, source_instr->source_node, field_value, "value", 1); + if (field_int_value == nullptr) + return ira->codegen->invalid_inst_gen->value->type; + field->value = *field_int_value; + } + + return entry; + } case ZigTypeIdUnion: ir_add_error(ira, source_instr, buf_sprintf( "TODO implement @Type for 'TypeInfo.%s': see https://github.com/ziglang/zig/issues/2907", type_id_name(tagTypeId))); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index ca75b2fa9c..fe11763ea1 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,41 @@ const tests = @import("tests.zig"); const std = @import("std"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add("@Type with undefined", + \\comptime { + \\ _ = @Type(.{ .Array = .{ .len = 0, .child = u8, .sentinel = undefined } }); + \\} + \\comptime { + \\ _ = @Type(.{ + \\ .Struct = .{ + \\ .fields = undefined, + \\ .decls = undefined, + \\ .is_tuple = false, + \\ .layout = .Auto, + \\ }, + \\ }); + \\} + , &[_][]const u8{ + "tmp.zig:2:16: error: use of undefined value here causes undefined behavior", + "tmp.zig:5:16: error: use of undefined value here causes undefined behavior", + }); + + cases.add("struct with declarations unavailable for @Type", + \\export fn entry() void { + \\ _ = @Type(@typeInfo(struct { const foo = 1; })); + \\} + , &[_][]const u8{ + "tmp.zig:2:15: error: TypeInfo.Struct.decls must be empty for @Type", + }); + + cases.add("enum with declarations unavailable for @Type", + \\export fn entry() void { + \\ _ = @Type(@typeInfo(enum { foo, const bar = 1; })); + \\} + , &[_][]const u8{ + "tmp.zig:2:15: error: TypeInfo.Enum.decls must be empty for @Type", + }); + cases.addTest("reject extern variables with initializers", \\extern var foo: int = 2; , &[_][]const u8{ @@ -123,16 +158,22 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\export fn baz() void { \\ try bar(); \\} - \\export fn quux() u32 { + \\export fn qux() u32 { \\ return bar(); \\} + \\export fn quux() u32 { + \\ var buf: u32 = 0; + \\ buf = bar(); + \\} , &[_][]const u8{ "tmp.zig:2:17: error: expected type 'u32', found 'error{Ohno}'", "tmp.zig:1:17: note: function cannot return an error", "tmp.zig:8:5: error: expected type 'void', found '@TypeOf(bar).ReturnType.ErrorSet'", "tmp.zig:7:17: note: function cannot return an error", "tmp.zig:11:15: error: expected type 'u32', found '@TypeOf(bar).ReturnType.ErrorSet!u32'", - "tmp.zig:10:18: note: function cannot return an error", + "tmp.zig:10:17: note: function cannot return an error", + "tmp.zig:15:14: error: expected type 'u32', found '@TypeOf(bar).ReturnType.ErrorSet!u32'", + "tmp.zig:14:5: note: cannot store an error in type 'u32'", }); cases.addTest("int/float conversion to comptime_int/float", @@ -598,8 +639,8 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { \\ _ = C; \\} , &[_][]const u8{ - "tmp.zig:4:5: error: non-exhaustive enum must specify size", - "error: value assigned to '_' field of non-exhaustive enum", + "tmp.zig:4:5: error: value assigned to '_' field of non-exhaustive enum", + "error: non-exhaustive enum must specify size", "error: non-exhaustive enum specifies every value", "error: '_' field of non-exhaustive enum must be last", }); @@ -1400,15 +1441,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { , &[_][]const u8{ "tmp.zig:3:36: error: expected type 'std.builtin.TypeInfo', found 'std.builtin.Int'", }); - - cases.add("struct with declarations unavailable for @Type", - \\export fn entry() void { - \\ _ = @Type(@typeInfo(struct { const foo = 1; })); - \\} - , &[_][]const u8{ - "tmp.zig:2:15: error: TypeInfo.Struct.decls must be empty for @Type", - }); - cases.add("wrong type for argument tuple to @asyncCall", \\export fn entry1() void { \\ var frame: @Frame(foo) = undefined; diff --git a/test/stage1/behavior/type.zig b/test/stage1/behavior/type.zig index 8ebf670279..949576fd04 100644 --- a/test/stage1/behavior/type.zig +++ b/test/stage1/behavior/type.zig @@ -280,3 +280,37 @@ test "Type.Struct" { testing.expectEqual(@as(usize, 0), infoC.decls.len); testing.expectEqual(@as(bool, false), infoC.is_tuple); } + +test "Type.Enum" { + const Foo = @Type(.{ + .Enum = .{ + .layout = .Auto, + .tag_type = u8, + .fields = &[_]TypeInfo.EnumField{ + .{ .name = "a", .value = 1 }, + .{ .name = "b", .value = 5 }, + }, + .decls = &[_]TypeInfo.Declaration{}, + .is_exhaustive = true, + }, + }); + testing.expectEqual(true, @typeInfo(Foo).Enum.is_exhaustive); + testing.expectEqual(@as(u8, 1), @enumToInt(Foo.a)); + testing.expectEqual(@as(u8, 5), @enumToInt(Foo.b)); + const Bar = @Type(.{ + .Enum = .{ + .layout = .Extern, + .tag_type = u32, + .fields = &[_]TypeInfo.EnumField{ + .{ .name = "a", .value = 1 }, + .{ .name = "b", .value = 5 }, + }, + .decls = &[_]TypeInfo.Declaration{}, + .is_exhaustive = false, + }, + }); + testing.expectEqual(false, @typeInfo(Bar).Enum.is_exhaustive); + testing.expectEqual(@as(u32, 1), @enumToInt(Bar.a)); + testing.expectEqual(@as(u32, 5), @enumToInt(Bar.b)); + testing.expectEqual(@as(u32, 6), @enumToInt(@intToEnum(Bar, 6))); +} diff --git a/test/stage1/behavior/type_info.zig b/test/stage1/behavior/type_info.zig index ab26558c04..d0da8815f7 100644 --- a/test/stage1/behavior/type_info.zig +++ b/test/stage1/behavior/type_info.zig @@ -153,7 +153,6 @@ fn testErrorSet() void { expect(error_set_info == .ErrorSet); expect(error_set_info.ErrorSet.?.len == 3); expect(mem.eql(u8, error_set_info.ErrorSet.?[0].name, "First")); - expect(error_set_info.ErrorSet.?[2].value == @errorToInt(TestErrorSet.Third)); const error_union_info = @typeInfo(TestErrorSet!usize); expect(error_union_info == .ErrorUnion); diff --git a/test/stage2/spu-ii.zig b/test/stage2/spu-ii.zig new file mode 100644 index 0000000000..1316f19d0d --- /dev/null +++ b/test/stage2/spu-ii.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const TestContext = @import("../../src-self-hosted/test.zig").TestContext; + +const spu = std.zig.CrossTarget{ + .cpu_arch = .spu_2, + .os_tag = .freestanding, +}; + +pub fn addCases(ctx: *TestContext) !void { + { + var case = ctx.exe("SPU-II Basic Test", spu); + case.addCompareOutput( + \\fn killEmulator() noreturn { + \\ asm volatile ("undefined0"); + \\ unreachable; + \\} + \\ + \\export fn _start() noreturn { + \\ killEmulator(); + \\} + , ""); + } +} diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 11b1713181..fee8886ddb 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -18,6 +18,11 @@ const linux_riscv64 = std.zig.CrossTarget{ .os_tag = .linux, }; +const linux_arm = std.zig.CrossTarget{ + .cpu_arch = .arm, + .os_tag = .linux, +}; + const wasi = std.zig.CrossTarget{ .cpu_arch = .wasm32, .os_tag = .wasi, @@ -26,6 +31,8 @@ const wasi = std.zig.CrossTarget{ pub fn addCases(ctx: *TestContext) !void { try @import("zir.zig").addCases(ctx); try @import("cbe.zig").addCases(ctx); + try @import("spu-ii.zig").addCases(ctx); + { var case = ctx.exe("hello world with updates", linux_x64); @@ -179,6 +186,41 @@ pub fn addCases(ctx: *TestContext) !void { ); } + { + var case = ctx.exe("hello world", linux_arm); + // Regular old hello world + case.addCompareOutput( + \\export fn _start() noreturn { + \\ print(); + \\ exit(); + \\} + \\ + \\fn print() void { + \\ asm volatile ("svc #0" + \\ : + \\ : [number] "{r7}" (4), + \\ [arg1] "{r0}" (1), + \\ [arg2] "{r1}" (@ptrToInt("Hello, World!\n")), + \\ [arg3] "{r2}" (14) + \\ : "memory" + \\ ); + \\ return; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("svc #0" + \\ : + \\ : [number] "{r7}" (1), + \\ [arg1] "{r0}" (0) + \\ : "memory" + \\ ); + \\ unreachable; + \\} + , + "Hello, World!\n", + ); + } + { var case = ctx.exe("adding numbers at comptime", linux_x64); case.addCompareOutput( @@ -600,6 +642,58 @@ pub fn addCases(ctx: *TestContext) !void { "", ); + // Spilling registers to the stack. + case.addCompareOutput( + \\export fn _start() noreturn { + \\ assert(add(3, 4) == 791); + \\ + \\ exit(); + \\} + \\ + \\fn add(a: u32, b: u32) u32 { + \\ const x: u32 = blk: { + \\ const c = a + b; // 7 + \\ const d = a + c; // 10 + \\ const e = d + b; // 14 + \\ const f = d + e; // 24 + \\ const g = e + f; // 38 + \\ const h = f + g; // 62 + \\ const i = g + h; // 100 + \\ const j = i + d; // 110 + \\ const k = i + j; // 210 + \\ const l = k + c; // 217 + \\ const m = l + d; // 227 + \\ const n = m + e; // 241 + \\ const o = n + f; // 265 + \\ const p = o + g; // 303 + \\ const q = p + h; // 365 + \\ const r = q + i; // 465 + \\ const s = r + j; // 575 + \\ const t = s + k; // 785 + \\ break :blk t; + \\ }; + \\ const y = x + a; // 788 + \\ const z = y + a; // 791 + \\ return z; + \\} + \\ + \\pub fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + // Character literals and multiline strings. case.addCompareOutput( \\export fn _start() noreturn { diff --git a/tools/process_headers.zig b/tools/process_headers.zig index 918802f65d..72b3fe8942 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -15,6 +15,7 @@ const Arch = std.Target.Cpu.Arch; const Abi = std.Target.Abi; const OsTag = std.Target.Os.Tag; const assert = std.debug.assert; +const Sha256 = std.crypto.hash.sha2.Sha256; const LibCTarget = struct { name: []const u8, @@ -313,7 +314,7 @@ pub fn main() !void { var max_bytes_saved: usize = 0; var total_bytes: usize = 0; - var hasher = std.crypto.hash.sha2.Sha256.init(.{}); + var hasher = Sha256.init(.{}); for (libc_targets) |libc_target| { const dest_target = DestTarget{ @@ -359,7 +360,7 @@ pub fn main() !void { const trimmed = std.mem.trim(u8, raw_bytes, " \r\n\t"); total_bytes += raw_bytes.len; const hash = try allocator.alloc(u8, 32); - hasher.reset(); + hasher = Sha256.init(.{}); hasher.update(rel_path); hasher.update(trimmed); hasher.final(hash); diff --git a/tools/update_glibc.zig b/tools/update_glibc.zig index b6805962dc..a8aeef0799 100644 --- a/tools/update_glibc.zig +++ b/tools/update_glibc.zig @@ -148,12 +148,12 @@ pub fn main() !void { for (abi_lists) |*abi_list| { const target_funcs_gop = try target_functions.getOrPut(@ptrToInt(abi_list)); if (!target_funcs_gop.found_existing) { - target_funcs_gop.kv.value = FunctionSet{ + target_funcs_gop.entry.value = FunctionSet{ .list = std.ArrayList(VersionedFn).init(allocator), .fn_vers_list = FnVersionList.init(allocator), }; } - const fn_set = &target_funcs_gop.kv.value.list; + const fn_set = &target_funcs_gop.entry.value.list; for (lib_names) |lib_name, lib_name_index| { const lib_prefix = if (std.mem.eql(u8, lib_name, "ld")) "" else "lib"; @@ -203,11 +203,11 @@ pub fn main() !void { _ = try global_ver_set.put(ver, undefined); const gop = try global_fn_set.getOrPut(name); if (gop.found_existing) { - if (!std.mem.eql(u8, gop.kv.value.lib, "c")) { - gop.kv.value.lib = lib_name; + if (!std.mem.eql(u8, gop.entry.value.lib, "c")) { + gop.entry.value.lib = lib_name; } } else { - gop.kv.value = Function{ + gop.entry.value = Function{ .name = name, .lib = lib_name, .index = undefined, @@ -224,14 +224,14 @@ pub fn main() !void { const global_fn_list = blk: { var list = std.ArrayList([]const u8).init(allocator); var it = global_fn_set.iterator(); - while (it.next()) |kv| try list.append(kv.key); + while (it.next()) |entry| try list.append(entry.key); std.sort.sort([]const u8, list.span(), {}, strCmpLessThan); break :blk list.span(); }; const global_ver_list = blk: { var list = std.ArrayList([]const u8).init(allocator); var it = global_ver_set.iterator(); - while (it.next()) |kv| try list.append(kv.key); + while (it.next()) |entry| try list.append(entry.key); std.sort.sort([]const u8, list.span(), {}, versionLessThan); break :blk list.span(); }; @@ -254,9 +254,9 @@ pub fn main() !void { var buffered = std.io.bufferedOutStream(fns_txt_file.outStream()); const fns_txt = buffered.outStream(); for (global_fn_list) |name, i| { - const kv = global_fn_set.get(name).?; - kv.value.index = i; - try fns_txt.print("{} {}\n", .{ name, kv.value.lib }); + const entry = global_fn_set.getEntry(name).?; + entry.value.index = i; + try fns_txt.print("{} {}\n", .{ name, entry.value.lib }); } try buffered.flush(); } @@ -264,16 +264,16 @@ pub fn main() !void { // Now the mapping of version and function to integer index is complete. // Here we create a mapping of function name to list of versions. for (abi_lists) |*abi_list, abi_index| { - const kv = target_functions.get(@ptrToInt(abi_list)).?; - const fn_vers_list = &kv.value.fn_vers_list; - for (kv.value.list.span()) |*ver_fn| { + const entry = target_functions.getEntry(@ptrToInt(abi_list)).?; + const fn_vers_list = &entry.value.fn_vers_list; + for (entry.value.list.span()) |*ver_fn| { const gop = try fn_vers_list.getOrPut(ver_fn.name); if (!gop.found_existing) { - gop.kv.value = std.ArrayList(usize).init(allocator); + gop.entry.value = std.ArrayList(usize).init(allocator); } - const ver_index = global_ver_set.get(ver_fn.ver).?.value; - if (std.mem.indexOfScalar(usize, gop.kv.value.span(), ver_index) == null) { - try gop.kv.value.append(ver_index); + const ver_index = global_ver_set.getEntry(ver_fn.ver).?.value; + if (std.mem.indexOfScalar(usize, gop.entry.value.span(), ver_index) == null) { + try gop.entry.value.append(ver_index); } } } @@ -287,7 +287,7 @@ pub fn main() !void { // first iterate over the abi lists for (abi_lists) |*abi_list, abi_index| { - const fn_vers_list = &target_functions.get(@ptrToInt(abi_list)).?.value.fn_vers_list; + const fn_vers_list = &target_functions.getEntry(@ptrToInt(abi_list)).?.value.fn_vers_list; for (abi_list.targets) |target, it_i| { if (it_i != 0) try abilist_txt.writeByte(' '); try abilist_txt.print("{}-linux-{}", .{ @tagName(target.arch), @tagName(target.abi) }); @@ -295,11 +295,11 @@ pub fn main() !void { try abilist_txt.writeByte('\n'); // next, each line implicitly corresponds to a function for (global_fn_list) |name| { - const kv = fn_vers_list.get(name) orelse { + const entry = fn_vers_list.getEntry(name) orelse { try abilist_txt.writeByte('\n'); continue; }; - for (kv.value.span()) |ver_index, it_i| { + for (entry.value.span()) |ver_index, it_i| { if (it_i != 0) try abilist_txt.writeByte(' '); try abilist_txt.print("{d}", .{ver_index}); }